Repository: gfx-rs/wgpu Branch: trunk Commit: 0ca8ba04acbc Files: 2164 Total size: 17.7 MB Directory structure: gitextract_6vnxf1s_/ ├── .cargo/ │ └── config.toml ├── .claude/ │ └── skills/ │ ├── cts-triage/ │ │ └── SKILL.md │ └── webgpu-specs/ │ ├── SKILL.md │ └── download.sh ├── .config/ │ └── nextest.toml ├── .deny.toml ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ ├── feature_request.md │ │ └── other.md │ ├── actions/ │ │ ├── install-agility-sdk/ │ │ │ └── action.yml │ │ ├── install-dxc/ │ │ │ └── action.yml │ │ ├── install-mesa/ │ │ │ └── action.yml │ │ ├── install-vulkan-sdk/ │ │ │ └── action.yml │ │ └── install-warp/ │ │ └── action.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── changelog.yml │ ├── ci.yml │ ├── cts.yml │ ├── docs.yml │ ├── generate.yml │ ├── lazy.yml │ ├── publish.yml │ └── shaders.yml ├── .gitignore ├── .prettierignore ├── AGENTS.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── GOVERNANCE.md ├── LICENSE.APACHE ├── LICENSE.MIT ├── README.md ├── SECURITY.md ├── benches/ │ ├── Cargo.toml │ ├── README.md │ ├── benches/ │ │ └── wgpu-benchmark/ │ │ ├── bind_groups.rs │ │ ├── computepass-bindless.wgsl │ │ ├── computepass.rs │ │ ├── computepass.wgsl │ │ ├── main.rs │ │ ├── renderpass-bindless.wgsl │ │ ├── renderpass.rs │ │ ├── renderpass.wgsl │ │ ├── resource_creation.rs │ │ └── shader.rs │ └── src/ │ ├── context.rs │ ├── file.rs │ ├── iter.rs │ ├── lib.rs │ └── print.rs ├── clippy.toml ├── codecov.yml ├── cts_runner/ │ ├── Cargo.toml │ ├── README.md │ ├── examples/ │ │ └── hello-compute.js │ ├── fail.lst │ ├── revision.txt │ ├── skip.lst │ ├── src/ │ │ ├── bootstrap.js │ │ └── main.rs │ ├── test.lst │ └── tests/ │ └── integration.rs ├── deno_webgpu/ │ ├── 00_init.js │ ├── 01_webgpu.js │ ├── 02_surface.js │ ├── Cargo.toml │ ├── LICENSE.md │ ├── README.md │ ├── adapter.rs │ ├── bind_group.rs │ ├── bind_group_layout.rs │ ├── buffer.rs │ ├── byow.rs │ ├── command_buffer.rs │ ├── command_encoder.rs │ ├── compute_pass.rs │ ├── compute_pipeline.rs │ ├── device.rs │ ├── error.rs │ ├── lib.rs │ ├── pipeline_layout.rs │ ├── query_set.rs │ ├── queue.rs │ ├── render_bundle.rs │ ├── render_pass.rs │ ├── render_pipeline.rs │ ├── rustfmt.toml │ ├── sampler.rs │ ├── shader.rs │ ├── surface.rs │ ├── texture.rs │ └── webidl.rs ├── docs/ │ ├── api-specs/ │ │ ├── cooperative_matrix.md │ │ ├── mesh_shading.md │ │ └── ray_tracing.md │ ├── big-picture.xml │ ├── broadcast_license.nu │ ├── release-checklist.md │ ├── review-checklist.md │ └── testing.md ├── examples/ │ ├── README.md │ ├── bug-repro/ │ │ └── 01_texture_atomic_bug/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── main.rs │ │ └── shader.wgsl │ ├── features/ │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── big_compute_buffers/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ ├── shader.wgsl │ │ │ │ └── tests.rs │ │ │ ├── boids/ │ │ │ │ ├── README.md │ │ │ │ ├── compute.wgsl │ │ │ │ ├── draw.wgsl │ │ │ │ └── mod.rs │ │ │ ├── bunnymark/ │ │ │ │ ├── README.md │ │ │ │ └── mod.rs │ │ │ ├── conservative_raster/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ ├── triangle_and_lines.wgsl │ │ │ │ └── upscale.wgsl │ │ │ ├── cooperative_matrix/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ ├── shader.wgsl │ │ │ │ ├── shader_f16_16x16.wgsl │ │ │ │ └── tests.rs │ │ │ ├── cube/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── framework.rs │ │ │ ├── hello_synchronization/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ ├── shaders.wgsl │ │ │ │ └── tests.rs │ │ │ ├── hello_triangle/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── hello_windows/ │ │ │ │ ├── README.md │ │ │ │ └── mod.rs │ │ │ ├── hello_workgroups/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── lib.rs │ │ │ ├── main.rs │ │ │ ├── mesh_shader/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ ├── shader.hlsl │ │ │ │ ├── shader.metal │ │ │ │ └── shader.wgsl │ │ │ ├── mipmap/ │ │ │ │ ├── README.md │ │ │ │ ├── blit.wgsl │ │ │ │ ├── draw.wgsl │ │ │ │ └── mod.rs │ │ │ ├── msaa_line/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── multiple_render_targets/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── multiview/ │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── ray_cube_compute/ │ │ │ │ ├── README.md │ │ │ │ ├── blit.wgsl │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── ray_cube_fragment/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── ray_cube_normals/ │ │ │ │ ├── README.md │ │ │ │ ├── blit.wgsl │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── ray_scene/ │ │ │ │ ├── cube.mtl │ │ │ │ ├── cube.obj │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── ray_shadows/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── ray_traced_triangle/ │ │ │ │ ├── README.md │ │ │ │ ├── blit.wgsl │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── render_to_texture/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── render_with_compute/ │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── repeated_compute/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── shadow/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── skybox/ │ │ │ │ ├── README.md │ │ │ │ ├── images/ │ │ │ │ │ ├── astc.ktx2 │ │ │ │ │ ├── bc7.ktx2 │ │ │ │ │ ├── etc2.ktx2 │ │ │ │ │ ├── generation.bash │ │ │ │ │ └── rgba8.ktx2 │ │ │ │ ├── mod.rs │ │ │ │ ├── models/ │ │ │ │ │ ├── rustacean-3d.mtl │ │ │ │ │ └── rustacean-3d.obj │ │ │ │ └── shader.wgsl │ │ │ ├── srgb_blend/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── stencil_triangles/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── storage_texture/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── texture_arrays/ │ │ │ │ ├── README.md │ │ │ │ ├── indexing.wgsl │ │ │ │ ├── mod.rs │ │ │ │ └── non_uniform_indexing.wgsl │ │ │ ├── timestamp_queries/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── uniform_values/ │ │ │ │ ├── README.md │ │ │ │ ├── mod.rs │ │ │ │ └── shader.wgsl │ │ │ ├── utils.rs │ │ │ └── water/ │ │ │ ├── README.md │ │ │ ├── mod.rs │ │ │ ├── point_gen.rs │ │ │ ├── terrain.wgsl │ │ │ └── water.wgsl │ │ └── web-static/ │ │ └── index.html │ └── standalone/ │ ├── 01_hello_compute/ │ │ ├── Cargo.toml │ │ ├── cargo-generate.toml │ │ └── src/ │ │ ├── main.rs │ │ └── shader.wgsl │ ├── 02_hello_window/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ └── custom_backend/ │ ├── Cargo.toml │ └── src/ │ ├── custom.rs │ └── main.rs ├── lock-analyzer/ │ ├── Cargo.toml │ └── src/ │ └── main.rs ├── naga/ │ ├── .cargo/ │ │ └── config.toml │ ├── .gitattributes │ ├── .gitignore │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── LICENSE.APACHE │ ├── LICENSE.MIT │ ├── README.md │ ├── build.rs │ ├── fuzz/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── fuzz_targets/ │ │ ├── glsl_parser.rs │ │ ├── ir.rs │ │ ├── spv_parser.rs │ │ └── wgsl_parser.rs │ ├── hlsl-snapshots/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── src/ │ │ ├── arena/ │ │ │ ├── handle.rs │ │ │ ├── handle_set.rs │ │ │ ├── handlevec.rs │ │ │ ├── mod.rs │ │ │ ├── range.rs │ │ │ └── unique_arena.rs │ │ ├── back/ │ │ │ ├── continue_forward.rs │ │ │ ├── dot/ │ │ │ │ └── mod.rs │ │ │ ├── glsl/ │ │ │ │ ├── conv.rs │ │ │ │ ├── features.rs │ │ │ │ ├── keywords.rs │ │ │ │ ├── mod.rs │ │ │ │ └── writer.rs │ │ │ ├── hlsl/ │ │ │ │ ├── conv.rs │ │ │ │ ├── help.rs │ │ │ │ ├── keywords.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── ray.rs │ │ │ │ ├── storage.rs │ │ │ │ └── writer.rs │ │ │ ├── mod.rs │ │ │ ├── msl/ │ │ │ │ ├── keywords.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── sampler.rs │ │ │ │ └── writer.rs │ │ │ ├── pipeline_constants.rs │ │ │ ├── spv/ │ │ │ │ ├── block.rs │ │ │ │ ├── f16_polyfill.rs │ │ │ │ ├── helpers.rs │ │ │ │ ├── image.rs │ │ │ │ ├── index.rs │ │ │ │ ├── instructions.rs │ │ │ │ ├── layout.rs │ │ │ │ ├── mesh_shader.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── ray/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── query.rs │ │ │ │ ├── reclaimable.rs │ │ │ │ ├── selection.rs │ │ │ │ ├── subgroup.rs │ │ │ │ └── writer.rs │ │ │ └── wgsl/ │ │ │ ├── mod.rs │ │ │ ├── polyfill/ │ │ │ │ ├── inverse/ │ │ │ │ │ ├── inverse_2x2_f16.wgsl │ │ │ │ │ ├── inverse_2x2_f32.wgsl │ │ │ │ │ ├── inverse_3x3_f16.wgsl │ │ │ │ │ ├── inverse_3x3_f32.wgsl │ │ │ │ │ ├── inverse_4x4_f16.wgsl │ │ │ │ │ └── inverse_4x4_f32.wgsl │ │ │ │ └── mod.rs │ │ │ └── writer.rs │ │ ├── common/ │ │ │ ├── diagnostic_debug.rs │ │ │ ├── diagnostic_display.rs │ │ │ ├── mod.rs │ │ │ ├── predeclared.rs │ │ │ └── wgsl/ │ │ │ ├── diagnostics.rs │ │ │ ├── mod.rs │ │ │ ├── to_wgsl.rs │ │ │ └── types.rs │ │ ├── compact/ │ │ │ ├── expressions.rs │ │ │ ├── functions.rs │ │ │ ├── handle_set_map.rs │ │ │ ├── mod.rs │ │ │ ├── statements.rs │ │ │ └── types.rs │ │ ├── diagnostic_filter.rs │ │ ├── error.rs │ │ ├── front/ │ │ │ ├── atomic_upgrade.rs │ │ │ ├── glsl/ │ │ │ │ ├── ast.rs │ │ │ │ ├── builtins.rs │ │ │ │ ├── context.rs │ │ │ │ ├── error.rs │ │ │ │ ├── functions.rs │ │ │ │ ├── lex.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── offset.rs │ │ │ │ ├── parser/ │ │ │ │ │ ├── declarations.rs │ │ │ │ │ ├── expressions.rs │ │ │ │ │ ├── functions.rs │ │ │ │ │ └── types.rs │ │ │ │ ├── parser.rs │ │ │ │ ├── parser_tests.rs │ │ │ │ ├── token.rs │ │ │ │ ├── types.rs │ │ │ │ └── variables.rs │ │ │ ├── interpolator.rs │ │ │ ├── mod.rs │ │ │ ├── spv/ │ │ │ │ ├── convert.rs │ │ │ │ ├── error.rs │ │ │ │ ├── function.rs │ │ │ │ ├── image.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── next_block.rs │ │ │ │ └── null.rs │ │ │ ├── type_gen.rs │ │ │ └── wgsl/ │ │ │ ├── error.rs │ │ │ ├── index.rs │ │ │ ├── lower/ │ │ │ │ ├── construction.rs │ │ │ │ ├── conversion.rs │ │ │ │ ├── mod.rs │ │ │ │ └── template_list.rs │ │ │ ├── mod.rs │ │ │ ├── parse/ │ │ │ │ ├── ast.rs │ │ │ │ ├── conv.rs │ │ │ │ ├── directive/ │ │ │ │ │ ├── enable_extension.rs │ │ │ │ │ └── language_extension.rs │ │ │ │ ├── directive.rs │ │ │ │ ├── lexer.rs │ │ │ │ ├── mod.rs │ │ │ │ └── number.rs │ │ │ └── tests.rs │ │ ├── ir/ │ │ │ ├── block.rs │ │ │ └── mod.rs │ │ ├── keywords/ │ │ │ ├── mod.rs │ │ │ └── wgsl.rs │ │ ├── lib.rs │ │ ├── non_max_u32.rs │ │ ├── proc/ │ │ │ ├── constant_evaluator.rs │ │ │ ├── emitter.rs │ │ │ ├── index.rs │ │ │ ├── keyword_set.rs │ │ │ ├── layouter.rs │ │ │ ├── mod.rs │ │ │ ├── namer.rs │ │ │ ├── overloads/ │ │ │ │ ├── any_overload_set.rs │ │ │ │ ├── constructor_set.rs │ │ │ │ ├── list.rs │ │ │ │ ├── mathfunction.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── one_bits_iter.rs │ │ │ │ ├── regular.rs │ │ │ │ ├── rule.rs │ │ │ │ ├── scalar_set.rs │ │ │ │ └── utils.rs │ │ │ ├── terminator.rs │ │ │ ├── type_methods.rs │ │ │ └── typifier.rs │ │ ├── racy_lock.rs │ │ ├── span.rs │ │ └── valid/ │ │ ├── analyzer.rs │ │ ├── compose.rs │ │ ├── expression.rs │ │ ├── function.rs │ │ ├── handles.rs │ │ ├── interface.rs │ │ ├── mod.rs │ │ └── type.rs │ ├── tests/ │ │ ├── in/ │ │ │ ├── glsl/ │ │ │ │ ├── 210-bevy-2d-shader.frag │ │ │ │ ├── 210-bevy-2d-shader.vert │ │ │ │ ├── 210-bevy-shader.vert │ │ │ │ ├── 246-collatz.comp │ │ │ │ ├── 277-casting.frag │ │ │ │ ├── 280-matrix-cast.frag │ │ │ │ ├── 484-preprocessor-if.frag │ │ │ │ ├── 5246-dual-iteration.frag │ │ │ │ ├── 800-out-of-bounds-panic.toml │ │ │ │ ├── 800-out-of-bounds-panic.vert │ │ │ │ ├── 896-push-constant.frag │ │ │ │ ├── 896-push-constant.toml │ │ │ │ ├── 900-implicit-conversions.frag │ │ │ │ ├── 901-lhs-field-select.frag │ │ │ │ ├── 931-constant-emitting.frag │ │ │ │ ├── 932-for-loop-if.frag │ │ │ │ ├── anonymous-entry-point-type.frag │ │ │ │ ├── bevy-pbr.frag │ │ │ │ ├── bevy-pbr.vert │ │ │ │ ├── bits.frag │ │ │ │ ├── bits.toml │ │ │ │ ├── bool-select.frag │ │ │ │ ├── buffer.frag │ │ │ │ ├── clamp-splat.vert │ │ │ │ ├── const-global-swizzle.frag │ │ │ │ ├── constant-array-size.frag │ │ │ │ ├── declarations.frag │ │ │ │ ├── double-math-functions.frag │ │ │ │ ├── double-math-functions.toml │ │ │ │ ├── dual-source-blending.frag │ │ │ │ ├── dual-source-blending.toml │ │ │ │ ├── expressions.frag │ │ │ │ ├── f16-glsl.comp │ │ │ │ ├── f16-glsl.toml │ │ │ │ ├── fma.frag │ │ │ │ ├── functions_call.frag │ │ │ │ ├── global-constant-array.frag │ │ │ │ ├── images.frag │ │ │ │ ├── inverse-polyfill.frag │ │ │ │ ├── local-var-init-in-loop.comp │ │ │ │ ├── long-form-matrix.frag │ │ │ │ ├── math-functions.frag │ │ │ │ ├── multipart-for-loop.frag │ │ │ │ ├── prepostfix.frag │ │ │ │ ├── quad_glsl.frag │ │ │ │ ├── quad_glsl.vert │ │ │ │ ├── sampler-functions.frag │ │ │ │ ├── samplers.frag │ │ │ │ ├── spec-constant.frag │ │ │ │ ├── spec-constant.toml │ │ │ │ ├── statements.frag │ │ │ │ ├── variations.frag │ │ │ │ ├── variations.toml │ │ │ │ ├── vector-functions.frag │ │ │ │ └── vector-functions.toml │ │ │ ├── spv/ │ │ │ │ ├── 8151-barrier-reorder.spvasm │ │ │ │ ├── atomic_compare_exchange.spvasm │ │ │ │ ├── atomic_exchange.spvasm │ │ │ │ ├── atomic_global_struct_field_vertex.spvasm │ │ │ │ ├── atomic_i_add_sub.spvasm │ │ │ │ ├── atomic_i_decrement.spvasm │ │ │ │ ├── atomic_i_increment.spvasm │ │ │ │ ├── atomic_load_and_store.spvasm │ │ │ │ ├── barrier.spvasm │ │ │ │ ├── barrier.toml │ │ │ │ ├── binding-arrays.dynamic.spvasm │ │ │ │ ├── binding-arrays.dynamic.toml │ │ │ │ ├── binding-arrays.runtime.slang │ │ │ │ ├── binding-arrays.runtime.spvasm │ │ │ │ ├── binding-arrays.runtime.toml │ │ │ │ ├── binding-arrays.static.spvasm │ │ │ │ ├── binding-arrays.static.toml │ │ │ │ ├── builtin-accessed-outside-entrypoint.spvasm │ │ │ │ ├── builtin-accessed-outside-entrypoint.toml │ │ │ │ ├── degrees.spvasm │ │ │ │ ├── degrees.toml │ │ │ │ ├── do-while.spvasm │ │ │ │ ├── do-while.toml │ │ │ │ ├── dual-source-blending.spvasm │ │ │ │ ├── dual-source-blending.toml │ │ │ │ ├── empty-global-name.spvasm │ │ │ │ ├── empty-global-name.toml │ │ │ │ ├── f16-spv.comp │ │ │ │ ├── f16-spv.spvasm │ │ │ │ ├── f16-spv.toml │ │ │ │ ├── fetch_depth.spvasm │ │ │ │ ├── fetch_depth.toml │ │ │ │ ├── gather-cmp.slang │ │ │ │ ├── gather-cmp.spvasm │ │ │ │ ├── gather-cmp.toml │ │ │ │ ├── gather.slang │ │ │ │ ├── gather.spvasm │ │ │ │ ├── gather.toml │ │ │ │ ├── inv-hyperbolic-trig-functions.spvasm │ │ │ │ ├── inv-hyperbolic-trig-functions.toml │ │ │ │ ├── load-ms-texture.slang │ │ │ │ ├── load-ms-texture.spvasm │ │ │ │ ├── load-ms-texture.toml │ │ │ │ ├── non-semantic-debug.spvasm │ │ │ │ ├── per-vertex.spvasm │ │ │ │ ├── per-vertex.toml │ │ │ │ ├── quad-vert.spvasm │ │ │ │ ├── quad-vert.toml │ │ │ │ ├── shadow.spvasm │ │ │ │ ├── shadow.toml │ │ │ │ ├── spec-constants-issue-5598.spvasm │ │ │ │ ├── spec-constants-issue-5598.toml │ │ │ │ ├── spec-constants.spvasm │ │ │ │ ├── spec-constants.toml │ │ │ │ ├── spec-constants.vert │ │ │ │ ├── subgroup-barrier.spvasm │ │ │ │ ├── subgroup-barrier.toml │ │ │ │ ├── subgroup-operations-s.spvasm │ │ │ │ ├── subgroup-operations-s.toml │ │ │ │ ├── unnamed-gl-per-vertex.spvasm │ │ │ │ └── unnamed-gl-per-vertex.toml │ │ │ └── wgsl/ │ │ │ ├── 6220-break-from-loop.toml │ │ │ ├── 6220-break-from-loop.wgsl │ │ │ ├── 6438-conflicting-idents.wgsl │ │ │ ├── 6772-unpack-expr-accesses.wgsl │ │ │ ├── 7048-multiple-dynamic-1.toml │ │ │ ├── 7048-multiple-dynamic-1.wgsl │ │ │ ├── 7048-multiple-dynamic-2.toml │ │ │ ├── 7048-multiple-dynamic-2.wgsl │ │ │ ├── 7048-multiple-dynamic-3.toml │ │ │ ├── 7048-multiple-dynamic-3.wgsl │ │ │ ├── 7995-unicode-idents.wgsl │ │ │ ├── 8820-multiple-local-invocation-index-id.wgsl │ │ │ ├── 9105-primitive-index-ordering.toml │ │ │ ├── 9105-primitive-index-ordering.wgsl │ │ │ ├── abstract-types-atomic.toml │ │ │ ├── abstract-types-atomic.wgsl │ │ │ ├── abstract-types-builtins.toml │ │ │ ├── abstract-types-builtins.wgsl │ │ │ ├── abstract-types-const.toml │ │ │ ├── abstract-types-const.wgsl │ │ │ ├── abstract-types-function-calls.toml │ │ │ ├── abstract-types-function-calls.wgsl │ │ │ ├── abstract-types-let.toml │ │ │ ├── abstract-types-let.wgsl │ │ │ ├── abstract-types-operators.toml │ │ │ ├── abstract-types-operators.wgsl │ │ │ ├── abstract-types-return.wgsl │ │ │ ├── abstract-types-texture.toml │ │ │ ├── abstract-types-texture.wgsl │ │ │ ├── abstract-types-var.toml │ │ │ ├── abstract-types-var.wgsl │ │ │ ├── access.toml │ │ │ ├── access.wgsl │ │ │ ├── aliased-ray-query.toml │ │ │ ├── aliased-ray-query.wgsl │ │ │ ├── array-in-ctor.wgsl │ │ │ ├── array-in-function-return-type.wgsl │ │ │ ├── atomicCompareExchange-int64.toml │ │ │ ├── atomicCompareExchange-int64.wgsl │ │ │ ├── atomicCompareExchange.toml │ │ │ ├── atomicCompareExchange.wgsl │ │ │ ├── atomicOps-float32.toml │ │ │ ├── atomicOps-float32.wgsl │ │ │ ├── atomicOps-int64-min-max.toml │ │ │ ├── atomicOps-int64-min-max.wgsl │ │ │ ├── atomicOps-int64.toml │ │ │ ├── atomicOps-int64.wgsl │ │ │ ├── atomicOps.wgsl │ │ │ ├── atomicTexture-int64.toml │ │ │ ├── atomicTexture-int64.wgsl │ │ │ ├── atomicTexture.toml │ │ │ ├── atomicTexture.wgsl │ │ │ ├── barycentrics.toml │ │ │ ├── barycentrics.wgsl │ │ │ ├── binding-arrays.toml │ │ │ ├── binding-arrays.wgsl │ │ │ ├── binding-buffer-arrays.toml │ │ │ ├── binding-buffer-arrays.wgsl │ │ │ ├── bitcast.wgsl │ │ │ ├── bits-optimized-msl.toml │ │ │ ├── bits-optimized-msl.wgsl │ │ │ ├── bits.toml │ │ │ ├── bits.wgsl │ │ │ ├── bits_downlevel.toml │ │ │ ├── bits_downlevel.wgsl │ │ │ ├── bits_downlevel_webgl.toml │ │ │ ├── bits_downlevel_webgl.wgsl │ │ │ ├── boids.toml │ │ │ ├── boids.wgsl │ │ │ ├── bounds-check-dynamic-buffer.toml │ │ │ ├── bounds-check-dynamic-buffer.wgsl │ │ │ ├── bounds-check-image-restrict-depth.toml │ │ │ ├── bounds-check-image-restrict-depth.wgsl │ │ │ ├── bounds-check-image-restrict.toml │ │ │ ├── bounds-check-image-restrict.wgsl │ │ │ ├── bounds-check-image-rzsw-depth.toml │ │ │ ├── bounds-check-image-rzsw-depth.wgsl │ │ │ ├── bounds-check-image-rzsw.toml │ │ │ ├── bounds-check-image-rzsw.wgsl │ │ │ ├── bounds-check-restrict.toml │ │ │ ├── bounds-check-restrict.wgsl │ │ │ ├── bounds-check-zero-atomic.toml │ │ │ ├── bounds-check-zero-atomic.wgsl │ │ │ ├── bounds-check-zero.toml │ │ │ ├── bounds-check-zero.wgsl │ │ │ ├── break-if.wgsl │ │ │ ├── clip-distances.toml │ │ │ ├── clip-distances.wgsl │ │ │ ├── collatz.toml │ │ │ ├── collatz.wgsl │ │ │ ├── const-exprs.wgsl │ │ │ ├── const_assert.toml │ │ │ ├── const_assert.wgsl │ │ │ ├── constructors.wgsl │ │ │ ├── control-flow.toml │ │ │ ├── control-flow.wgsl │ │ │ ├── conversion-float-to-int-no-f64.toml │ │ │ ├── conversion-float-to-int-no-f64.wgsl │ │ │ ├── conversion-float-to-int.toml │ │ │ ├── conversion-float-to-int.wgsl │ │ │ ├── conversions.wgsl │ │ │ ├── cooperative-matrix.toml │ │ │ ├── cooperative-matrix.wgsl │ │ │ ├── cross.wgsl │ │ │ ├── cubeArrayShadow.toml │ │ │ ├── cubeArrayShadow.wgsl │ │ │ ├── debug-symbol-large-source.toml │ │ │ ├── debug-symbol-large-source.wgsl │ │ │ ├── debug-symbol-simple.toml │ │ │ ├── debug-symbol-simple.wgsl │ │ │ ├── debug-symbol-terrain.toml │ │ │ ├── debug-symbol-terrain.wgsl │ │ │ ├── diagnostic-filter.toml │ │ │ ├── diagnostic-filter.wgsl │ │ │ ├── draw-index.toml │ │ │ ├── draw-index.wgsl │ │ │ ├── dualsource.toml │ │ │ ├── dualsource.wgsl │ │ │ ├── early-depth-test-conservative.toml │ │ │ ├── early-depth-test-conservative.wgsl │ │ │ ├── early-depth-test-force.toml │ │ │ ├── early-depth-test-force.wgsl │ │ │ ├── empty-if.toml │ │ │ ├── empty-if.wgsl │ │ │ ├── empty.wgsl │ │ │ ├── extra.toml │ │ │ ├── extra.wgsl │ │ │ ├── f16-native.toml │ │ │ ├── f16-native.wgsl │ │ │ ├── f16-polyfill.toml │ │ │ ├── f16-polyfill.wgsl │ │ │ ├── f16.toml │ │ │ ├── f16.wgsl │ │ │ ├── f64.toml │ │ │ ├── f64.wgsl │ │ │ ├── force_point_size_vertex_shader_webgl.toml │ │ │ ├── force_point_size_vertex_shader_webgl.wgsl │ │ │ ├── fragment-output.wgsl │ │ │ ├── functions-optimized-by-capability.toml │ │ │ ├── functions-optimized-by-capability.wgsl │ │ │ ├── functions-optimized-by-version.toml │ │ │ ├── functions-optimized-by-version.wgsl │ │ │ ├── functions-unoptimized.toml │ │ │ ├── functions-unoptimized.wgsl │ │ │ ├── functions-webgl.toml │ │ │ ├── functions-webgl.wgsl │ │ │ ├── functions.wgsl │ │ │ ├── globals.wgsl │ │ │ ├── hlsl-keyword.toml │ │ │ ├── hlsl-keyword.wgsl │ │ │ ├── image.toml │ │ │ ├── image.wgsl │ │ │ ├── index-by-value.toml │ │ │ ├── index-by-value.wgsl │ │ │ ├── int64.toml │ │ │ ├── int64.wgsl │ │ │ ├── interface.toml │ │ │ ├── interface.wgsl │ │ │ ├── interpolate.toml │ │ │ ├── interpolate.wgsl │ │ │ ├── interpolate_compat.toml │ │ │ ├── interpolate_compat.wgsl │ │ │ ├── invariant.toml │ │ │ ├── invariant.wgsl │ │ │ ├── lexical-scopes.toml │ │ │ ├── lexical-scopes.wgsl │ │ │ ├── local-const.toml │ │ │ ├── local-const.wgsl │ │ │ ├── mat_cx2.toml │ │ │ ├── mat_cx2.wgsl │ │ │ ├── mat_cx3.toml │ │ │ ├── mat_cx3.wgsl │ │ │ ├── math-functions.toml │ │ │ ├── math-functions.wgsl │ │ │ ├── memory-decorations-coherent.toml │ │ │ ├── memory-decorations-coherent.wgsl │ │ │ ├── memory-decorations.toml │ │ │ ├── memory-decorations.wgsl │ │ │ ├── mesh-shader-empty.toml │ │ │ ├── mesh-shader-empty.wgsl │ │ │ ├── mesh-shader-lines.toml │ │ │ ├── mesh-shader-lines.wgsl │ │ │ ├── mesh-shader-points.toml │ │ │ ├── mesh-shader-points.wgsl │ │ │ ├── mesh-shader.toml │ │ │ ├── mesh-shader.wgsl │ │ │ ├── module-scope.toml │ │ │ ├── module-scope.wgsl │ │ │ ├── msl-varyings.toml │ │ │ ├── msl-varyings.wgsl │ │ │ ├── msl-vpt-formats-x1.toml │ │ │ ├── msl-vpt-formats-x1.wgsl │ │ │ ├── msl-vpt-formats-x2.toml │ │ │ ├── msl-vpt-formats-x2.wgsl │ │ │ ├── msl-vpt-formats-x3.toml │ │ │ ├── msl-vpt-formats-x3.wgsl │ │ │ ├── msl-vpt-formats-x4.toml │ │ │ ├── msl-vpt-formats-x4.wgsl │ │ │ ├── msl-vpt.toml │ │ │ ├── msl-vpt.wgsl │ │ │ ├── multiview.toml │ │ │ ├── multiview.wgsl │ │ │ ├── multiview_webgl.toml │ │ │ ├── multiview_webgl.wgsl │ │ │ ├── must-use.toml │ │ │ ├── must-use.wgsl │ │ │ ├── operators.wgsl │ │ │ ├── overrides-atomicCompareExchangeWeak.toml │ │ │ ├── overrides-atomicCompareExchangeWeak.wgsl │ │ │ ├── overrides-ray-query.toml │ │ │ ├── overrides-ray-query.wgsl │ │ │ ├── overrides.toml │ │ │ ├── overrides.wgsl │ │ │ ├── padding.toml │ │ │ ├── padding.wgsl │ │ │ ├── per-vertex.toml │ │ │ ├── per-vertex.wgsl │ │ │ ├── phony_assignment.wgsl │ │ │ ├── pointer-function-arg-restrict.toml │ │ │ ├── pointer-function-arg-restrict.wgsl │ │ │ ├── pointer-function-arg-rzsw.toml │ │ │ ├── pointer-function-arg-rzsw.wgsl │ │ │ ├── pointer-function-arg.toml │ │ │ ├── pointer-function-arg.wgsl │ │ │ ├── pointers.toml │ │ │ ├── pointers.wgsl │ │ │ ├── policy-mix.toml │ │ │ ├── policy-mix.wgsl │ │ │ ├── primitive-index-mesh.toml │ │ │ ├── primitive-index-mesh.wgsl │ │ │ ├── primitive-index.toml │ │ │ ├── primitive-index.wgsl │ │ │ ├── push-constants.toml │ │ │ ├── push-constants.wgsl │ │ │ ├── quad.toml │ │ │ ├── quad.wgsl │ │ │ ├── ray-query-no-init-tracking.toml │ │ │ ├── ray-query-no-init-tracking.wgsl │ │ │ ├── ray-query.toml │ │ │ ├── ray-query.wgsl │ │ │ ├── ray-tracing-pipeline.toml │ │ │ ├── ray-tracing-pipeline.wgsl │ │ │ ├── resource-binding-map.toml │ │ │ ├── resource-binding-map.wgsl │ │ │ ├── sample-cube-array-depth-lod.toml │ │ │ ├── sample-cube-array-depth-lod.wgsl │ │ │ ├── select.wgsl │ │ │ ├── separate-entry-points.toml │ │ │ ├── separate-entry-points.wgsl │ │ │ ├── shadow.toml │ │ │ ├── shadow.wgsl │ │ │ ├── skybox.toml │ │ │ ├── skybox.wgsl │ │ │ ├── sprite.toml │ │ │ ├── sprite.wgsl │ │ │ ├── standard.wgsl │ │ │ ├── storage-textures.toml │ │ │ ├── storage-textures.wgsl │ │ │ ├── struct-layout.wgsl │ │ │ ├── subgroup-barrier.toml │ │ │ ├── subgroup-barrier.wgsl │ │ │ ├── subgroup-operations.toml │ │ │ ├── subgroup-operations.wgsl │ │ │ ├── template-list-ge.toml │ │ │ ├── template-list-ge.wgsl │ │ │ ├── template-list-trailing-comma.toml │ │ │ ├── template-list-trailing-comma.wgsl │ │ │ ├── texture-arg.toml │ │ │ ├── texture-arg.wgsl │ │ │ ├── texture-external.toml │ │ │ ├── texture-external.wgsl │ │ │ ├── type-alias.toml │ │ │ ├── type-alias.wgsl │ │ │ ├── type-inference.wgsl │ │ │ ├── types_with_comments.toml │ │ │ ├── types_with_comments.wgsl │ │ │ ├── unconsumed_vertex_outputs_frag.toml │ │ │ ├── unconsumed_vertex_outputs_frag.wgsl │ │ │ ├── unconsumed_vertex_outputs_vert.toml │ │ │ ├── unconsumed_vertex_outputs_vert.wgsl │ │ │ ├── use-gl-ext-over-grad-workaround-if-instructed.toml │ │ │ ├── use-gl-ext-over-grad-workaround-if-instructed.wgsl │ │ │ ├── workgroup-uniform-load-atomic.wgsl │ │ │ ├── workgroup-uniform-load.wgsl │ │ │ ├── workgroup-var-init.toml │ │ │ └── workgroup-var-init.wgsl │ │ ├── naga/ │ │ │ ├── example_wgsl.rs │ │ │ ├── main.rs │ │ │ ├── snapshots.rs │ │ │ ├── spirv_capabilities.rs │ │ │ ├── validation.rs │ │ │ └── wgsl_errors.rs │ │ └── out/ │ │ ├── analysis/ │ │ │ ├── spv-shadow.info.ron │ │ │ ├── wgsl-access.info.ron │ │ │ ├── wgsl-collatz.info.ron │ │ │ ├── wgsl-overrides.info.ron │ │ │ └── wgsl-storage-textures.info.ron │ │ ├── dot/ │ │ │ └── wgsl-quad.dot │ │ ├── glsl/ │ │ │ ├── glsl-variations.frag.main.Fragment.glsl │ │ │ ├── spv-barrier.main.Compute.glsl │ │ │ ├── spv-do-while.main.Fragment.glsl │ │ │ ├── spv-quad-vert.main.Vertex.glsl │ │ │ ├── spv-spec-constants-issue-5598.fragment.Fragment.glsl │ │ │ ├── spv-spec-constants-issue-5598.vertex.Vertex.glsl │ │ │ ├── spv-subgroup-barrier.main.Compute.glsl │ │ │ ├── spv-subgroup-operations-s.main.Compute.glsl │ │ │ ├── spv-unnamed-gl-per-vertex.main.Vertex.glsl │ │ │ ├── wgsl-6438-conflicting-idents.fs.Fragment.glsl │ │ │ ├── wgsl-6438-conflicting-idents.vs.Vertex.glsl │ │ │ ├── wgsl-6772-unpack-expr-accesses.main.Compute.glsl │ │ │ ├── wgsl-7995-unicode-idents.main.Compute.glsl │ │ │ ├── wgsl-8820-multiple-local-invocation-index-id.compute1.Compute.glsl │ │ │ ├── wgsl-abstract-types-builtins.f.Compute.glsl │ │ │ ├── wgsl-abstract-types-function-calls.main.Compute.glsl │ │ │ ├── wgsl-abstract-types-let.main.Compute.glsl │ │ │ ├── wgsl-abstract-types-operators.main.Compute.glsl │ │ │ ├── wgsl-abstract-types-return.main.Compute.glsl │ │ │ ├── wgsl-abstract-types-var.main.Compute.glsl │ │ │ ├── wgsl-access.foo_compute.Compute.glsl │ │ │ ├── wgsl-access.foo_frag.Fragment.glsl │ │ │ ├── wgsl-access.foo_vert.Vertex.glsl │ │ │ ├── wgsl-array-in-ctor.cs_main.Compute.glsl │ │ │ ├── wgsl-array-in-function-return-type.main.Fragment.glsl │ │ │ ├── wgsl-atomicCompareExchange.test_atomic_compare_exchange_i32.Compute.glsl │ │ │ ├── wgsl-atomicCompareExchange.test_atomic_compare_exchange_u32.Compute.glsl │ │ │ ├── wgsl-atomicOps.cs_main.Compute.glsl │ │ │ ├── wgsl-atomicTexture.cs_main.Compute.glsl │ │ │ ├── wgsl-barycentrics.fs_main.Fragment.glsl │ │ │ ├── wgsl-barycentrics.fs_main_no_perspective.Fragment.glsl │ │ │ ├── wgsl-bitcast.main.Compute.glsl │ │ │ ├── wgsl-bits.main.Compute.glsl │ │ │ ├── wgsl-bits_downlevel.main.Fragment.glsl │ │ │ ├── wgsl-bits_downlevel_webgl.main.Fragment.glsl │ │ │ ├── wgsl-boids.main.Compute.glsl │ │ │ ├── wgsl-bounds-check-image-restrict.fragment_shader.Fragment.glsl │ │ │ ├── wgsl-bounds-check-image-rzsw.fragment_shader.Fragment.glsl │ │ │ ├── wgsl-break-if.main.Compute.glsl │ │ │ ├── wgsl-clip-distances.main.Vertex.glsl │ │ │ ├── wgsl-const-exprs.main.Compute.glsl │ │ │ ├── wgsl-constructors.main.Compute.glsl │ │ │ ├── wgsl-control-flow.main.Compute.glsl │ │ │ ├── wgsl-conversions.main.Compute.glsl │ │ │ ├── wgsl-cross.main.Compute.glsl │ │ │ ├── wgsl-cubeArrayShadow.fragment.Fragment.glsl │ │ │ ├── wgsl-dualsource.main.Fragment.glsl │ │ │ ├── wgsl-early-depth-test-conservative.main.Fragment.glsl │ │ │ ├── wgsl-early-depth-test-force.main.Fragment.glsl │ │ │ ├── wgsl-empty-if.comp.Compute.glsl │ │ │ ├── wgsl-empty.main.Compute.glsl │ │ │ ├── wgsl-f64.main.Compute.glsl │ │ │ ├── wgsl-force_point_size_vertex_shader_webgl.fs_main.Fragment.glsl │ │ │ ├── wgsl-force_point_size_vertex_shader_webgl.vs_main.Vertex.glsl │ │ │ ├── wgsl-fragment-output.main_vec2scalar.Fragment.glsl │ │ │ ├── wgsl-fragment-output.main_vec4vec3.Fragment.glsl │ │ │ ├── wgsl-functions-webgl.main.Fragment.glsl │ │ │ ├── wgsl-functions.main.Compute.glsl │ │ │ ├── wgsl-globals.main.Compute.glsl │ │ │ ├── wgsl-image.gather.Fragment.glsl │ │ │ ├── wgsl-image.levels_queries.Vertex.glsl │ │ │ ├── wgsl-image.main.Compute.glsl │ │ │ ├── wgsl-image.queries.Vertex.glsl │ │ │ ├── wgsl-image.texture_sample.Fragment.glsl │ │ │ ├── wgsl-image.texture_sample_comparison.Fragment.glsl │ │ │ ├── wgsl-interpolate_compat.frag_main.Fragment.glsl │ │ │ ├── wgsl-interpolate_compat.vert_main.Vertex.glsl │ │ │ ├── wgsl-invariant.fs.Fragment.glsl │ │ │ ├── wgsl-invariant.vs.Vertex.glsl │ │ │ ├── wgsl-math-functions.main.Fragment.glsl │ │ │ ├── wgsl-memory-decorations-coherent.main.Compute.glsl │ │ │ ├── wgsl-memory-decorations.main.Compute.glsl │ │ │ ├── wgsl-multiview.main.Fragment.glsl │ │ │ ├── wgsl-multiview_webgl.main.Fragment.glsl │ │ │ ├── wgsl-operators.main.Compute.glsl │ │ │ ├── wgsl-overrides.main.Compute.glsl │ │ │ ├── wgsl-padding.vertex.Vertex.glsl │ │ │ ├── wgsl-phony_assignment.main.Compute.glsl │ │ │ ├── wgsl-pointer-function-arg.main.Compute.glsl │ │ │ ├── wgsl-primitive-index-mesh.func.Fragment.glsl │ │ │ ├── wgsl-primitive-index.func.Fragment.glsl │ │ │ ├── wgsl-push-constants.main.Fragment.glsl │ │ │ ├── wgsl-push-constants.vert_main.Vertex.glsl │ │ │ ├── wgsl-quad.frag_main.Fragment.glsl │ │ │ ├── wgsl-quad.fs_extra.Fragment.glsl │ │ │ ├── wgsl-quad.vert_main.Vertex.glsl │ │ │ ├── wgsl-sample-cube-array-depth-lod.main.Fragment.glsl │ │ │ ├── wgsl-select.main.Compute.glsl │ │ │ ├── wgsl-separate-entry-points.compute.Compute.glsl │ │ │ ├── wgsl-separate-entry-points.fragment.Fragment.glsl │ │ │ ├── wgsl-shadow.fs_main.Fragment.glsl │ │ │ ├── wgsl-shadow.fs_main_without_storage.Fragment.glsl │ │ │ ├── wgsl-shadow.vs_main.Vertex.glsl │ │ │ ├── wgsl-skybox.fs_main.Fragment.glsl │ │ │ ├── wgsl-skybox.vs_main.Vertex.glsl │ │ │ ├── wgsl-standard.derivatives.Fragment.glsl │ │ │ ├── wgsl-struct-layout.needs_padding_comp.Compute.glsl │ │ │ ├── wgsl-struct-layout.needs_padding_frag.Fragment.glsl │ │ │ ├── wgsl-struct-layout.needs_padding_vert.Vertex.glsl │ │ │ ├── wgsl-struct-layout.no_padding_comp.Compute.glsl │ │ │ ├── wgsl-struct-layout.no_padding_frag.Fragment.glsl │ │ │ ├── wgsl-struct-layout.no_padding_vert.Vertex.glsl │ │ │ ├── wgsl-subgroup-operations.main.Compute.glsl │ │ │ ├── wgsl-texture-arg.main.Fragment.glsl │ │ │ ├── wgsl-type-inference.main.Compute.glsl │ │ │ ├── wgsl-use-gl-ext-over-grad-workaround-if-instructed.main.Fragment.glsl │ │ │ ├── wgsl-workgroup-uniform-load-atomic.test_atomic_workgroup_uniform_load.Compute.glsl │ │ │ ├── wgsl-workgroup-uniform-load.test_workgroupUniformLoad.Compute.glsl │ │ │ └── wgsl-workgroup-var-init.main.Compute.glsl │ │ ├── hlsl/ │ │ │ ├── spv-barrier.hlsl │ │ │ ├── spv-barrier.ron │ │ │ ├── spv-do-while.hlsl │ │ │ ├── spv-do-while.ron │ │ │ ├── spv-empty-global-name.hlsl │ │ │ ├── spv-empty-global-name.ron │ │ │ ├── spv-fetch_depth.hlsl │ │ │ ├── spv-fetch_depth.ron │ │ │ ├── spv-inv-hyperbolic-trig-functions.hlsl │ │ │ ├── spv-inv-hyperbolic-trig-functions.ron │ │ │ ├── spv-quad-vert.hlsl │ │ │ ├── spv-quad-vert.ron │ │ │ ├── spv-subgroup-operations-s.hlsl │ │ │ ├── spv-subgroup-operations-s.ron │ │ │ ├── spv-unnamed-gl-per-vertex.hlsl │ │ │ ├── spv-unnamed-gl-per-vertex.ron │ │ │ ├── wgsl-6438-conflicting-idents.hlsl │ │ │ ├── wgsl-6438-conflicting-idents.ron │ │ │ ├── wgsl-6772-unpack-expr-accesses.hlsl │ │ │ ├── wgsl-6772-unpack-expr-accesses.ron │ │ │ ├── wgsl-7995-unicode-idents.hlsl │ │ │ ├── wgsl-7995-unicode-idents.ron │ │ │ ├── wgsl-8820-multiple-local-invocation-index-id.hlsl │ │ │ ├── wgsl-8820-multiple-local-invocation-index-id.ron │ │ │ ├── wgsl-9105-primitive-index-ordering.hlsl │ │ │ ├── wgsl-9105-primitive-index-ordering.ron │ │ │ ├── wgsl-abstract-types-return.hlsl │ │ │ ├── wgsl-abstract-types-return.ron │ │ │ ├── wgsl-access.hlsl │ │ │ ├── wgsl-access.ron │ │ │ ├── wgsl-aliased-ray-query.hlsl │ │ │ ├── wgsl-aliased-ray-query.ron │ │ │ ├── wgsl-array-in-ctor.hlsl │ │ │ ├── wgsl-array-in-ctor.ron │ │ │ ├── wgsl-array-in-function-return-type.hlsl │ │ │ ├── wgsl-array-in-function-return-type.ron │ │ │ ├── wgsl-atomicCompareExchange-int64.hlsl │ │ │ ├── wgsl-atomicCompareExchange-int64.ron │ │ │ ├── wgsl-atomicCompareExchange.hlsl │ │ │ ├── wgsl-atomicCompareExchange.ron │ │ │ ├── wgsl-atomicOps-int64-min-max.hlsl │ │ │ ├── wgsl-atomicOps-int64-min-max.ron │ │ │ ├── wgsl-atomicOps-int64.hlsl │ │ │ ├── wgsl-atomicOps-int64.ron │ │ │ ├── wgsl-atomicOps.hlsl │ │ │ ├── wgsl-atomicOps.ron │ │ │ ├── wgsl-atomicTexture-int64.hlsl │ │ │ ├── wgsl-atomicTexture-int64.ron │ │ │ ├── wgsl-atomicTexture.hlsl │ │ │ ├── wgsl-atomicTexture.ron │ │ │ ├── wgsl-barycentrics.hlsl │ │ │ ├── wgsl-barycentrics.ron │ │ │ ├── wgsl-binding-arrays.hlsl │ │ │ ├── wgsl-binding-arrays.ron │ │ │ ├── wgsl-bitcast.hlsl │ │ │ ├── wgsl-bitcast.ron │ │ │ ├── wgsl-bits.hlsl │ │ │ ├── wgsl-bits.ron │ │ │ ├── wgsl-boids.hlsl │ │ │ ├── wgsl-boids.ron │ │ │ ├── wgsl-bounds-check-dynamic-buffer.hlsl │ │ │ ├── wgsl-bounds-check-dynamic-buffer.ron │ │ │ ├── wgsl-break-if.hlsl │ │ │ ├── wgsl-break-if.ron │ │ │ ├── wgsl-collatz.hlsl │ │ │ ├── wgsl-collatz.ron │ │ │ ├── wgsl-const-exprs.hlsl │ │ │ ├── wgsl-const-exprs.ron │ │ │ ├── wgsl-constructors.hlsl │ │ │ ├── wgsl-constructors.ron │ │ │ ├── wgsl-control-flow.hlsl │ │ │ ├── wgsl-control-flow.ron │ │ │ ├── wgsl-conversion-float-to-int.hlsl │ │ │ ├── wgsl-conversion-float-to-int.ron │ │ │ ├── wgsl-conversions.hlsl │ │ │ ├── wgsl-conversions.ron │ │ │ ├── wgsl-cross.hlsl │ │ │ ├── wgsl-cross.ron │ │ │ ├── wgsl-dualsource.hlsl │ │ │ ├── wgsl-dualsource.ron │ │ │ ├── wgsl-empty-if.hlsl │ │ │ ├── wgsl-empty-if.ron │ │ │ ├── wgsl-empty.hlsl │ │ │ ├── wgsl-empty.ron │ │ │ ├── wgsl-f16.hlsl │ │ │ ├── wgsl-f16.ron │ │ │ ├── wgsl-f64.hlsl │ │ │ ├── wgsl-f64.ron │ │ │ ├── wgsl-fragment-output.hlsl │ │ │ ├── wgsl-fragment-output.ron │ │ │ ├── wgsl-functions-optimized-by-version.hlsl │ │ │ ├── wgsl-functions-optimized-by-version.ron │ │ │ ├── wgsl-functions-unoptimized.hlsl │ │ │ ├── wgsl-functions-unoptimized.ron │ │ │ ├── wgsl-functions.hlsl │ │ │ ├── wgsl-functions.ron │ │ │ ├── wgsl-globals.hlsl │ │ │ ├── wgsl-globals.ron │ │ │ ├── wgsl-hlsl-keyword.hlsl │ │ │ ├── wgsl-hlsl-keyword.ron │ │ │ ├── wgsl-image.hlsl │ │ │ ├── wgsl-image.ron │ │ │ ├── wgsl-int64.hlsl │ │ │ ├── wgsl-int64.ron │ │ │ ├── wgsl-interface.hlsl │ │ │ ├── wgsl-interface.ron │ │ │ ├── wgsl-interpolate.hlsl │ │ │ ├── wgsl-interpolate.ron │ │ │ ├── wgsl-interpolate_compat.hlsl │ │ │ ├── wgsl-interpolate_compat.ron │ │ │ ├── wgsl-mat_cx2.hlsl │ │ │ ├── wgsl-mat_cx2.ron │ │ │ ├── wgsl-mat_cx3.hlsl │ │ │ ├── wgsl-mat_cx3.ron │ │ │ ├── wgsl-math-functions.hlsl │ │ │ ├── wgsl-math-functions.ron │ │ │ ├── wgsl-memory-decorations-coherent.hlsl │ │ │ ├── wgsl-memory-decorations-coherent.ron │ │ │ ├── wgsl-multiview.hlsl │ │ │ ├── wgsl-multiview.ron │ │ │ ├── wgsl-operators.hlsl │ │ │ ├── wgsl-operators.ron │ │ │ ├── wgsl-overrides.hlsl │ │ │ ├── wgsl-overrides.ron │ │ │ ├── wgsl-padding.hlsl │ │ │ ├── wgsl-padding.ron │ │ │ ├── wgsl-phony_assignment.hlsl │ │ │ ├── wgsl-phony_assignment.ron │ │ │ ├── wgsl-pointer-function-arg.hlsl │ │ │ ├── wgsl-pointer-function-arg.ron │ │ │ ├── wgsl-primitive-index.hlsl │ │ │ ├── wgsl-primitive-index.ron │ │ │ ├── wgsl-push-constants.hlsl │ │ │ ├── wgsl-push-constants.ron │ │ │ ├── wgsl-quad.hlsl │ │ │ ├── wgsl-quad.ron │ │ │ ├── wgsl-ray-query-no-init-tracking.hlsl │ │ │ ├── wgsl-ray-query-no-init-tracking.ron │ │ │ ├── wgsl-ray-query.hlsl │ │ │ ├── wgsl-ray-query.ron │ │ │ ├── wgsl-select.hlsl │ │ │ ├── wgsl-select.ron │ │ │ ├── wgsl-shadow.hlsl │ │ │ ├── wgsl-shadow.ron │ │ │ ├── wgsl-skybox.hlsl │ │ │ ├── wgsl-skybox.ron │ │ │ ├── wgsl-standard.hlsl │ │ │ ├── wgsl-standard.ron │ │ │ ├── wgsl-storage-textures.hlsl │ │ │ ├── wgsl-storage-textures.ron │ │ │ ├── wgsl-struct-layout.hlsl │ │ │ ├── wgsl-struct-layout.ron │ │ │ ├── wgsl-subgroup-operations.hlsl │ │ │ ├── wgsl-subgroup-operations.ron │ │ │ ├── wgsl-texture-arg.hlsl │ │ │ ├── wgsl-texture-arg.ron │ │ │ ├── wgsl-texture-external.hlsl │ │ │ ├── wgsl-texture-external.ron │ │ │ ├── wgsl-type-inference.hlsl │ │ │ ├── wgsl-type-inference.ron │ │ │ ├── wgsl-unconsumed_vertex_outputs_frag.hlsl │ │ │ ├── wgsl-unconsumed_vertex_outputs_frag.ron │ │ │ ├── wgsl-unconsumed_vertex_outputs_vert.hlsl │ │ │ ├── wgsl-unconsumed_vertex_outputs_vert.ron │ │ │ ├── wgsl-workgroup-uniform-load-atomic.hlsl │ │ │ ├── wgsl-workgroup-uniform-load-atomic.ron │ │ │ ├── wgsl-workgroup-uniform-load.hlsl │ │ │ ├── wgsl-workgroup-uniform-load.ron │ │ │ ├── wgsl-workgroup-var-init.hlsl │ │ │ └── wgsl-workgroup-var-init.ron │ │ ├── ir/ │ │ │ ├── spv-fetch_depth.compact.ron │ │ │ ├── spv-fetch_depth.ron │ │ │ ├── spv-shadow.compact.ron │ │ │ ├── spv-shadow.ron │ │ │ ├── spv-spec-constants.compact.ron │ │ │ ├── spv-spec-constants.ron │ │ │ ├── wgsl-access.compact.ron │ │ │ ├── wgsl-access.ron │ │ │ ├── wgsl-collatz.compact.ron │ │ │ ├── wgsl-collatz.ron │ │ │ ├── wgsl-const_assert.compact.ron │ │ │ ├── wgsl-const_assert.ron │ │ │ ├── wgsl-diagnostic-filter.compact.ron │ │ │ ├── wgsl-diagnostic-filter.ron │ │ │ ├── wgsl-index-by-value.compact.ron │ │ │ ├── wgsl-index-by-value.ron │ │ │ ├── wgsl-local-const.compact.ron │ │ │ ├── wgsl-local-const.ron │ │ │ ├── wgsl-must-use.compact.ron │ │ │ ├── wgsl-must-use.ron │ │ │ ├── wgsl-overrides-atomicCompareExchangeWeak.compact.ron │ │ │ ├── wgsl-overrides-atomicCompareExchangeWeak.ron │ │ │ ├── wgsl-overrides-ray-query.compact.ron │ │ │ ├── wgsl-overrides-ray-query.ron │ │ │ ├── wgsl-overrides.compact.ron │ │ │ ├── wgsl-overrides.ron │ │ │ ├── wgsl-storage-textures.compact.ron │ │ │ ├── wgsl-storage-textures.ron │ │ │ ├── wgsl-template-list-trailing-comma.compact.ron │ │ │ ├── wgsl-template-list-trailing-comma.ron │ │ │ ├── wgsl-texture-external.compact.ron │ │ │ ├── wgsl-texture-external.ron │ │ │ ├── wgsl-types_with_comments.compact.ron │ │ │ └── wgsl-types_with_comments.ron │ │ ├── msl/ │ │ │ ├── spv-barrier.metal │ │ │ ├── spv-do-while.metal │ │ │ ├── spv-empty-global-name.metal │ │ │ ├── spv-fetch_depth.metal │ │ │ ├── spv-quad-vert.metal │ │ │ ├── spv-subgroup-barrier.metal │ │ │ ├── spv-subgroup-operations-s.metal │ │ │ ├── spv-unnamed-gl-per-vertex.metal │ │ │ ├── wgsl-6438-conflicting-idents.metal │ │ │ ├── wgsl-6772-unpack-expr-accesses.metal │ │ │ ├── wgsl-7995-unicode-idents.metal │ │ │ ├── wgsl-8820-multiple-local-invocation-index-id.metal │ │ │ ├── wgsl-abstract-types-builtins.metal │ │ │ ├── wgsl-abstract-types-const.metal │ │ │ ├── wgsl-abstract-types-function-calls.metal │ │ │ ├── wgsl-abstract-types-let.metal │ │ │ ├── wgsl-abstract-types-operators.metal │ │ │ ├── wgsl-abstract-types-return.metal │ │ │ ├── wgsl-abstract-types-texture.metal │ │ │ ├── wgsl-abstract-types-var.metal │ │ │ ├── wgsl-access.metal │ │ │ ├── wgsl-aliased-ray-query.metal │ │ │ ├── wgsl-array-in-ctor.metal │ │ │ ├── wgsl-array-in-function-return-type.metal │ │ │ ├── wgsl-atomicCompareExchange.metal │ │ │ ├── wgsl-atomicOps-float32.metal │ │ │ ├── wgsl-atomicOps-int64-min-max.metal │ │ │ ├── wgsl-atomicOps.metal │ │ │ ├── wgsl-atomicTexture-int64.metal │ │ │ ├── wgsl-atomicTexture.metal │ │ │ ├── wgsl-barycentrics.metal │ │ │ ├── wgsl-binding-arrays.metal │ │ │ ├── wgsl-bitcast.metal │ │ │ ├── wgsl-bits-optimized-msl.metal │ │ │ ├── wgsl-bits.metal │ │ │ ├── wgsl-boids.metal │ │ │ ├── wgsl-bounds-check-image-restrict-depth.metal │ │ │ ├── wgsl-bounds-check-image-restrict.metal │ │ │ ├── wgsl-bounds-check-image-rzsw-depth.metal │ │ │ ├── wgsl-bounds-check-image-rzsw.metal │ │ │ ├── wgsl-bounds-check-restrict.metal │ │ │ ├── wgsl-bounds-check-zero-atomic.metal │ │ │ ├── wgsl-bounds-check-zero.metal │ │ │ ├── wgsl-break-if.metal │ │ │ ├── wgsl-collatz.metal │ │ │ ├── wgsl-const-exprs.metal │ │ │ ├── wgsl-constructors.metal │ │ │ ├── wgsl-control-flow.metal │ │ │ ├── wgsl-conversion-float-to-int-no-f64.metal │ │ │ ├── wgsl-conversions.metal │ │ │ ├── wgsl-cooperative-matrix.metal │ │ │ ├── wgsl-cross.metal │ │ │ ├── wgsl-dualsource.metal │ │ │ ├── wgsl-empty-if.metal │ │ │ ├── wgsl-empty.metal │ │ │ ├── wgsl-extra.metal │ │ │ ├── wgsl-f16.metal │ │ │ ├── wgsl-fragment-output.metal │ │ │ ├── wgsl-functions-optimized-by-version.metal │ │ │ ├── wgsl-functions-unoptimized.metal │ │ │ ├── wgsl-functions.metal │ │ │ ├── wgsl-globals.metal │ │ │ ├── wgsl-image.metal │ │ │ ├── wgsl-int64.metal │ │ │ ├── wgsl-interface.metal │ │ │ ├── wgsl-interpolate.metal │ │ │ ├── wgsl-interpolate_compat.metal │ │ │ ├── wgsl-math-functions.metal │ │ │ ├── wgsl-memory-decorations-coherent.metal │ │ │ ├── wgsl-msl-varyings.metal │ │ │ ├── wgsl-msl-vpt-formats-x1.metal │ │ │ ├── wgsl-msl-vpt-formats-x2.metal │ │ │ ├── wgsl-msl-vpt-formats-x3.metal │ │ │ ├── wgsl-msl-vpt-formats-x4.metal │ │ │ ├── wgsl-msl-vpt.metal │ │ │ ├── wgsl-multiview.metal │ │ │ ├── wgsl-operators.metal │ │ │ ├── wgsl-overrides-atomicCompareExchangeWeak.metal │ │ │ ├── wgsl-overrides-ray-query.metal │ │ │ ├── wgsl-overrides.metal │ │ │ ├── wgsl-padding.metal │ │ │ ├── wgsl-phony_assignment.metal │ │ │ ├── wgsl-pointer-function-arg-restrict.metal │ │ │ ├── wgsl-pointer-function-arg-rzsw.metal │ │ │ ├── wgsl-pointer-function-arg.metal │ │ │ ├── wgsl-policy-mix.metal │ │ │ ├── wgsl-primitive-index.metal │ │ │ ├── wgsl-quad.metal │ │ │ ├── wgsl-ray-query-no-init-tracking.metal │ │ │ ├── wgsl-ray-query.metal │ │ │ ├── wgsl-resource-binding-map.metal │ │ │ ├── wgsl-select.metal │ │ │ ├── wgsl-shadow.metal │ │ │ ├── wgsl-skybox.metal │ │ │ ├── wgsl-standard.metal │ │ │ ├── wgsl-storage-textures.metal │ │ │ ├── wgsl-struct-layout.metal │ │ │ ├── wgsl-subgroup-barrier.metal │ │ │ ├── wgsl-subgroup-operations.metal │ │ │ ├── wgsl-texture-arg.metal │ │ │ ├── wgsl-texture-external.metal │ │ │ ├── wgsl-type-inference.metal │ │ │ ├── wgsl-workgroup-uniform-load-atomic.metal │ │ │ ├── wgsl-workgroup-uniform-load.metal │ │ │ └── wgsl-workgroup-var-init.metal │ │ ├── spv/ │ │ │ ├── spv-barrier.spvasm │ │ │ ├── spv-fetch_depth.spvasm │ │ │ ├── spv-per-vertex.spvasm │ │ │ ├── spv-subgroup-barrier.spvasm │ │ │ ├── wgsl-6220-break-from-loop.spvasm │ │ │ ├── wgsl-6438-conflicting-idents.spvasm │ │ │ ├── wgsl-6772-unpack-expr-accesses.spvasm │ │ │ ├── wgsl-7048-multiple-dynamic-1.spvasm │ │ │ ├── wgsl-7048-multiple-dynamic-2.spvasm │ │ │ ├── wgsl-7048-multiple-dynamic-3.spvasm │ │ │ ├── wgsl-7995-unicode-idents.spvasm │ │ │ ├── wgsl-8820-multiple-local-invocation-index-id.spvasm │ │ │ ├── wgsl-abstract-types-builtins.spvasm │ │ │ ├── wgsl-abstract-types-const.spvasm │ │ │ ├── wgsl-abstract-types-function-calls.spvasm │ │ │ ├── wgsl-abstract-types-let.spvasm │ │ │ ├── wgsl-abstract-types-operators.spvasm │ │ │ ├── wgsl-abstract-types-return.spvasm │ │ │ ├── wgsl-abstract-types-var.spvasm │ │ │ ├── wgsl-access.spvasm │ │ │ ├── wgsl-aliased-ray-query.spvasm │ │ │ ├── wgsl-array-in-ctor.spvasm │ │ │ ├── wgsl-array-in-function-return-type.spvasm │ │ │ ├── wgsl-atomicCompareExchange-int64.spvasm │ │ │ ├── wgsl-atomicCompareExchange.spvasm │ │ │ ├── wgsl-atomicOps-float32.spvasm │ │ │ ├── wgsl-atomicOps-int64-min-max.spvasm │ │ │ ├── wgsl-atomicOps-int64.spvasm │ │ │ ├── wgsl-atomicOps.spvasm │ │ │ ├── wgsl-atomicTexture-int64.spvasm │ │ │ ├── wgsl-atomicTexture.spvasm │ │ │ ├── wgsl-barycentrics.spvasm │ │ │ ├── wgsl-binding-arrays.spvasm │ │ │ ├── wgsl-binding-buffer-arrays.spvasm │ │ │ ├── wgsl-bitcast.spvasm │ │ │ ├── wgsl-bits.spvasm │ │ │ ├── wgsl-boids.spvasm │ │ │ ├── wgsl-bounds-check-image-restrict-depth.spvasm │ │ │ ├── wgsl-bounds-check-image-restrict.spvasm │ │ │ ├── wgsl-bounds-check-image-rzsw-depth.spvasm │ │ │ ├── wgsl-bounds-check-image-rzsw.spvasm │ │ │ ├── wgsl-bounds-check-restrict.spvasm │ │ │ ├── wgsl-bounds-check-zero.spvasm │ │ │ ├── wgsl-break-if.spvasm │ │ │ ├── wgsl-clip-distances.spvasm │ │ │ ├── wgsl-collatz.spvasm │ │ │ ├── wgsl-const-exprs.spvasm │ │ │ ├── wgsl-constructors.spvasm │ │ │ ├── wgsl-control-flow.spvasm │ │ │ ├── wgsl-conversion-float-to-int.spvasm │ │ │ ├── wgsl-conversions.spvasm │ │ │ ├── wgsl-cooperative-matrix.spvasm │ │ │ ├── wgsl-cross.spvasm │ │ │ ├── wgsl-debug-symbol-large-source.spvasm │ │ │ ├── wgsl-debug-symbol-simple.spvasm │ │ │ ├── wgsl-debug-symbol-terrain.spvasm │ │ │ ├── wgsl-draw-index.spvasm │ │ │ ├── wgsl-dualsource.spvasm │ │ │ ├── wgsl-early-depth-test-conservative.spvasm │ │ │ ├── wgsl-early-depth-test-force.spvasm │ │ │ ├── wgsl-empty-if.spvasm │ │ │ ├── wgsl-empty.spvasm │ │ │ ├── wgsl-extra.spvasm │ │ │ ├── wgsl-f16-native.spvasm │ │ │ ├── wgsl-f16-polyfill.spvasm │ │ │ ├── wgsl-f16.spvasm │ │ │ ├── wgsl-f64.spvasm │ │ │ ├── wgsl-fragment-output.spvasm │ │ │ ├── wgsl-functions-optimized-by-capability.spvasm │ │ │ ├── wgsl-functions-optimized-by-version.spvasm │ │ │ ├── wgsl-functions-unoptimized.spvasm │ │ │ ├── wgsl-functions.spvasm │ │ │ ├── wgsl-globals.spvasm │ │ │ ├── wgsl-image.spvasm │ │ │ ├── wgsl-index-by-value.spvasm │ │ │ ├── wgsl-int64.spvasm │ │ │ ├── wgsl-interface.compute.spvasm │ │ │ ├── wgsl-interface.fragment.spvasm │ │ │ ├── wgsl-interface.vertex.spvasm │ │ │ ├── wgsl-interface.vertex_two_structs.spvasm │ │ │ ├── wgsl-interpolate.spvasm │ │ │ ├── wgsl-interpolate_compat.spvasm │ │ │ ├── wgsl-mat_cx2.spvasm │ │ │ ├── wgsl-mat_cx3.spvasm │ │ │ ├── wgsl-math-functions.spvasm │ │ │ ├── wgsl-memory-decorations-coherent.spvasm │ │ │ ├── wgsl-memory-decorations.spvasm │ │ │ ├── wgsl-mesh-shader-empty.spvasm │ │ │ ├── wgsl-mesh-shader-lines.spvasm │ │ │ ├── wgsl-mesh-shader-points.spvasm │ │ │ ├── wgsl-mesh-shader.spvasm │ │ │ ├── wgsl-multiview.spvasm │ │ │ ├── wgsl-operators.spvasm │ │ │ ├── wgsl-overrides-atomicCompareExchangeWeak.f.spvasm │ │ │ ├── wgsl-overrides-ray-query.main.spvasm │ │ │ ├── wgsl-overrides.main.spvasm │ │ │ ├── wgsl-padding.spvasm │ │ │ ├── wgsl-per-vertex.spvasm │ │ │ ├── wgsl-phony_assignment.spvasm │ │ │ ├── wgsl-pointers.spvasm │ │ │ ├── wgsl-policy-mix.spvasm │ │ │ ├── wgsl-primitive-index.spvasm │ │ │ ├── wgsl-quad.spvasm │ │ │ ├── wgsl-ray-query-no-init-tracking.spvasm │ │ │ ├── wgsl-ray-query.spvasm │ │ │ ├── wgsl-select.spvasm │ │ │ ├── wgsl-separate-entry-points.compute.spvasm │ │ │ ├── wgsl-separate-entry-points.fragment.spvasm │ │ │ ├── wgsl-shadow.spvasm │ │ │ ├── wgsl-skybox.spvasm │ │ │ ├── wgsl-sprite.spvasm │ │ │ ├── wgsl-standard.spvasm │ │ │ ├── wgsl-storage-textures.spvasm │ │ │ ├── wgsl-struct-layout.spvasm │ │ │ ├── wgsl-subgroup-barrier.spvasm │ │ │ ├── wgsl-subgroup-operations.spvasm │ │ │ ├── wgsl-texture-arg.spvasm │ │ │ ├── wgsl-type-inference.spvasm │ │ │ ├── wgsl-workgroup-uniform-load-atomic.spvasm │ │ │ ├── wgsl-workgroup-uniform-load.spvasm │ │ │ └── wgsl-workgroup-var-init.spvasm │ │ └── wgsl/ │ │ ├── glsl-210-bevy-2d-shader.frag.wgsl │ │ ├── glsl-210-bevy-2d-shader.vert.wgsl │ │ ├── glsl-210-bevy-shader.vert.wgsl │ │ ├── glsl-246-collatz.comp.wgsl │ │ ├── glsl-277-casting.frag.wgsl │ │ ├── glsl-280-matrix-cast.frag.wgsl │ │ ├── glsl-484-preprocessor-if.frag.wgsl │ │ ├── glsl-5246-dual-iteration.frag.wgsl │ │ ├── glsl-800-out-of-bounds-panic.vert.wgsl │ │ ├── glsl-896-push-constant.frag.wgsl │ │ ├── glsl-900-implicit-conversions.frag.wgsl │ │ ├── glsl-901-lhs-field-select.frag.wgsl │ │ ├── glsl-931-constant-emitting.frag.wgsl │ │ ├── glsl-932-for-loop-if.frag.wgsl │ │ ├── glsl-anonymous-entry-point-type.frag.wgsl │ │ ├── glsl-bevy-pbr.frag.wgsl │ │ ├── glsl-bevy-pbr.vert.wgsl │ │ ├── glsl-bits.frag.wgsl │ │ ├── glsl-bool-select.frag.wgsl │ │ ├── glsl-buffer.frag.wgsl │ │ ├── glsl-clamp-splat.vert.wgsl │ │ ├── glsl-const-global-swizzle.frag.wgsl │ │ ├── glsl-constant-array-size.frag.wgsl │ │ ├── glsl-declarations.frag.wgsl │ │ ├── glsl-double-math-functions.frag.wgsl │ │ ├── glsl-dual-source-blending.frag.wgsl │ │ ├── glsl-expressions.frag.wgsl │ │ ├── glsl-f16-glsl.comp.wgsl │ │ ├── glsl-fma.frag.wgsl │ │ ├── glsl-functions_call.frag.wgsl │ │ ├── glsl-global-constant-array.frag.wgsl │ │ ├── glsl-images.frag.wgsl │ │ ├── glsl-inverse-polyfill.frag.wgsl │ │ ├── glsl-local-var-init-in-loop.comp.wgsl │ │ ├── glsl-long-form-matrix.frag.wgsl │ │ ├── glsl-math-functions.frag.wgsl │ │ ├── glsl-multipart-for-loop.frag.wgsl │ │ ├── glsl-prepostfix.frag.wgsl │ │ ├── glsl-quad_glsl.frag.wgsl │ │ ├── glsl-quad_glsl.vert.wgsl │ │ ├── glsl-sampler-functions.frag.wgsl │ │ ├── glsl-samplers.frag.wgsl │ │ ├── glsl-spec-constant.frag.wgsl │ │ ├── glsl-statements.frag.wgsl │ │ ├── glsl-vector-functions.frag.wgsl │ │ ├── spv-8151-barrier-reorder.wgsl │ │ ├── spv-atomic_compare_exchange.wgsl │ │ ├── spv-atomic_exchange.wgsl │ │ ├── spv-atomic_global_struct_field_vertex.wgsl │ │ ├── spv-atomic_i_add_sub.wgsl │ │ ├── spv-atomic_i_decrement.wgsl │ │ ├── spv-atomic_i_increment.wgsl │ │ ├── spv-atomic_load_and_store.wgsl │ │ ├── spv-barrier.wgsl │ │ ├── spv-binding-arrays.dynamic.wgsl │ │ ├── spv-binding-arrays.runtime.wgsl │ │ ├── spv-binding-arrays.static.wgsl │ │ ├── spv-builtin-accessed-outside-entrypoint.wgsl │ │ ├── spv-do-while.wgsl │ │ ├── spv-dual-source-blending.wgsl │ │ ├── spv-empty-global-name.wgsl │ │ ├── spv-f16-spv.wgsl │ │ ├── spv-fetch_depth.wgsl │ │ ├── spv-gather-cmp.wgsl │ │ ├── spv-gather.wgsl │ │ ├── spv-inv-hyperbolic-trig-functions.wgsl │ │ ├── spv-load-ms-texture.wgsl │ │ ├── spv-non-semantic-debug.wgsl │ │ ├── spv-per-vertex.wgsl │ │ ├── spv-quad-vert.wgsl │ │ ├── spv-subgroup-barrier.wgsl │ │ ├── spv-subgroup-operations-s.wgsl │ │ ├── spv-unnamed-gl-per-vertex.wgsl │ │ ├── wgsl-6438-conflicting-idents.wgsl │ │ ├── wgsl-6772-unpack-expr-accesses.wgsl │ │ ├── wgsl-7995-unicode-idents.wgsl │ │ ├── wgsl-8820-multiple-local-invocation-index-id.wgsl │ │ ├── wgsl-abstract-types-atomic.wgsl │ │ ├── wgsl-abstract-types-builtins.wgsl │ │ ├── wgsl-abstract-types-const.wgsl │ │ ├── wgsl-abstract-types-function-calls.wgsl │ │ ├── wgsl-abstract-types-let.wgsl │ │ ├── wgsl-abstract-types-operators.wgsl │ │ ├── wgsl-abstract-types-return.wgsl │ │ ├── wgsl-abstract-types-var.wgsl │ │ ├── wgsl-access.wgsl │ │ ├── wgsl-array-in-ctor.wgsl │ │ ├── wgsl-array-in-function-return-type.wgsl │ │ ├── wgsl-atomicCompareExchange-int64.wgsl │ │ ├── wgsl-atomicCompareExchange.wgsl │ │ ├── wgsl-atomicOps-float32.wgsl │ │ ├── wgsl-atomicOps-int64-min-max.wgsl │ │ ├── wgsl-atomicOps-int64.wgsl │ │ ├── wgsl-atomicOps.wgsl │ │ ├── wgsl-atomicTexture-int64.wgsl │ │ ├── wgsl-atomicTexture.wgsl │ │ ├── wgsl-barycentrics.wgsl │ │ ├── wgsl-binding-arrays.wgsl │ │ ├── wgsl-binding-buffer-arrays.wgsl │ │ ├── wgsl-bitcast.wgsl │ │ ├── wgsl-bits.wgsl │ │ ├── wgsl-boids.wgsl │ │ ├── wgsl-break-if.wgsl │ │ ├── wgsl-clip-distances.wgsl │ │ ├── wgsl-collatz.wgsl │ │ ├── wgsl-const-exprs.wgsl │ │ ├── wgsl-const_assert.wgsl │ │ ├── wgsl-constructors.wgsl │ │ ├── wgsl-control-flow.wgsl │ │ ├── wgsl-conversion-float-to-int.wgsl │ │ ├── wgsl-conversions.wgsl │ │ ├── wgsl-cooperative-matrix.wgsl │ │ ├── wgsl-cross.wgsl │ │ ├── wgsl-draw-index.wgsl │ │ ├── wgsl-dualsource.wgsl │ │ ├── wgsl-empty-if.wgsl │ │ ├── wgsl-empty.wgsl │ │ ├── wgsl-extra.wgsl │ │ ├── wgsl-f16.wgsl │ │ ├── wgsl-f64.wgsl │ │ ├── wgsl-fragment-output.wgsl │ │ ├── wgsl-functions.wgsl │ │ ├── wgsl-globals.wgsl │ │ ├── wgsl-image.wgsl │ │ ├── wgsl-int64.wgsl │ │ ├── wgsl-interface.wgsl │ │ ├── wgsl-interpolate.wgsl │ │ ├── wgsl-interpolate_compat.wgsl │ │ ├── wgsl-lexical-scopes.wgsl │ │ ├── wgsl-local-const.wgsl │ │ ├── wgsl-math-functions.wgsl │ │ ├── wgsl-memory-decorations-coherent.wgsl │ │ ├── wgsl-memory-decorations.wgsl │ │ ├── wgsl-mesh-shader-empty.wgsl │ │ ├── wgsl-mesh-shader-lines.wgsl │ │ ├── wgsl-mesh-shader-points.wgsl │ │ ├── wgsl-mesh-shader.wgsl │ │ ├── wgsl-module-scope.wgsl │ │ ├── wgsl-multiview.wgsl │ │ ├── wgsl-operators.wgsl │ │ ├── wgsl-padding.wgsl │ │ ├── wgsl-per-vertex.wgsl │ │ ├── wgsl-phony_assignment.wgsl │ │ ├── wgsl-pointer-function-arg.wgsl │ │ ├── wgsl-pointers.wgsl │ │ ├── wgsl-primitive-index.wgsl │ │ ├── wgsl-quad.wgsl │ │ ├── wgsl-ray-tracing-pipeline.wgsl │ │ ├── wgsl-select.wgsl │ │ ├── wgsl-shadow.wgsl │ │ ├── wgsl-skybox.wgsl │ │ ├── wgsl-standard.wgsl │ │ ├── wgsl-struct-layout.wgsl │ │ ├── wgsl-subgroup-barrier.wgsl │ │ ├── wgsl-subgroup-operations.wgsl │ │ ├── wgsl-texture-arg.wgsl │ │ ├── wgsl-texture-external.wgsl │ │ ├── wgsl-type-alias.wgsl │ │ ├── wgsl-type-inference.wgsl │ │ ├── wgsl-workgroup-uniform-load-atomic.wgsl │ │ ├── wgsl-workgroup-uniform-load.wgsl │ │ └── wgsl-workgroup-var-init.wgsl │ └── xtask/ │ ├── .gitignore │ ├── Cargo.toml │ └── src/ │ ├── cli.rs │ ├── fs.rs │ ├── glob.rs │ ├── jobserver.rs │ ├── main.rs │ ├── path.rs │ ├── process.rs │ └── validate.rs ├── naga-cli/ │ ├── Cargo.toml │ ├── LICENSE.APACHE │ ├── LICENSE.MIT │ └── src/ │ └── bin/ │ └── naga.rs ├── naga-test/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── player/ │ ├── Cargo.toml │ ├── README.md │ ├── src/ │ │ ├── bin/ │ │ │ └── play.rs │ │ └── lib.rs │ └── tests/ │ └── player/ │ ├── data/ │ │ ├── all.ron │ │ ├── bind-group.ron │ │ ├── buffer-copy.ron │ │ ├── clear-buffer-texture.ron │ │ ├── empty.wgsl │ │ ├── pipeline-statistics-query.ron │ │ ├── quad.ron │ │ ├── quad.wgsl │ │ ├── zero-init-buffer-for-binding.wgsl │ │ ├── zero-init-buffer.ron │ │ ├── zero-init-texture-binding.ron │ │ ├── zero-init-texture-binding.wgsl │ │ ├── zero-init-texture-copytobuffer.ron │ │ └── zero-init-texture-rendertarget.ron │ └── main.rs ├── renovate.json ├── rust-toolchain.toml ├── rustfmt.toml ├── taplo.toml ├── tests/ │ ├── Cargo.toml │ ├── src/ │ │ ├── config.rs │ │ ├── copy_texture_to_buffer.wgsl │ │ ├── expectations.rs │ │ ├── image.rs │ │ ├── init.rs │ │ ├── isolation.rs │ │ ├── lib.rs │ │ ├── native.rs │ │ ├── params.rs │ │ ├── poll.rs │ │ ├── report.rs │ │ └── run.rs │ └── tests/ │ ├── wgpu-compile/ │ │ ├── fail/ │ │ │ ├── cpass_lifetime.rs │ │ │ ├── cpass_lifetime.stderr │ │ │ ├── rpass_lifetime.rs │ │ │ └── rpass_lifetime.stderr │ │ └── main.rs │ ├── wgpu-dependency/ │ │ └── main.rs │ ├── wgpu-gpu/ │ │ ├── bgra8unorm_storage.rs │ │ ├── bind_group_layout_dedup.rs │ │ ├── bind_groups.rs │ │ ├── binding_array/ │ │ │ ├── buffers.rs │ │ │ ├── mod.rs │ │ │ ├── sampled_textures.rs │ │ │ ├── samplers.rs │ │ │ ├── storage_textures.rs │ │ │ └── tlas.rs │ │ ├── buffer.rs │ │ ├── buffer_copy.rs │ │ ├── buffer_usages.rs │ │ ├── clear_texture.rs │ │ ├── clip_distances.rs │ │ ├── cloneable_types.rs │ │ ├── compute_pass_ownership.rs │ │ ├── create_surface_error.rs │ │ ├── device.rs │ │ ├── dispatch_workgroups_indirect.rs │ │ ├── draw_index.rs │ │ ├── draw_indirect.rs │ │ ├── dual_source_blending.rs │ │ ├── encoder.rs │ │ ├── external_image_copy.rs │ │ ├── external_texture/ │ │ │ ├── dimensions.wgsl │ │ │ ├── load.wgsl │ │ │ ├── mod.rs │ │ │ └── sample.wgsl │ │ ├── float32_filterable.rs │ │ ├── image_atomics/ │ │ │ ├── image_32_atomics.wgsl │ │ │ ├── image_64_atomics.wgsl │ │ │ └── mod.rs │ │ ├── immediates.rs │ │ ├── instance.rs │ │ ├── life_cycle.rs │ │ ├── main.rs │ │ ├── mem_leaks.rs │ │ ├── mesh_shader/ │ │ │ ├── basic.hlsl │ │ │ ├── mod.rs │ │ │ ├── shader.metal │ │ │ └── shader.wgsl │ │ ├── multiview/ │ │ │ ├── mod.rs │ │ │ └── shader.wgsl │ │ ├── naga_capabilities.rs │ │ ├── occlusion_query/ │ │ │ ├── mod.rs │ │ │ └── shader.wgsl │ │ ├── oob_indexing.rs │ │ ├── oom.rs │ │ ├── pass_ops/ │ │ │ └── mod.rs │ │ ├── passthrough/ │ │ │ ├── mod.rs │ │ │ ├── shader.frag │ │ │ ├── shader.hlsl │ │ │ ├── shader.metal │ │ │ ├── shader.vert │ │ │ └── shader.wgsl │ │ ├── per_vertex/ │ │ │ ├── mod.rs │ │ │ └── per_vertex.wgsl │ │ ├── pipeline.rs │ │ ├── pipeline_cache.rs │ │ ├── planar_texture/ │ │ │ ├── mod.rs │ │ │ ├── planar_texture_rendering.wgsl │ │ │ └── planar_texture_sampling.wgsl │ │ ├── poll.rs │ │ ├── primitive_index.rs │ │ ├── query_set.rs │ │ ├── queue_transfer.rs │ │ ├── ray_tracing/ │ │ │ ├── as_build.rs │ │ │ ├── as_create.rs │ │ │ ├── as_use_after_free.rs │ │ │ ├── limits.rs │ │ │ ├── mod.rs │ │ │ ├── scene/ │ │ │ │ ├── mesh_gen.rs │ │ │ │ └── mod.rs │ │ │ ├── shader.rs │ │ │ └── shader.wgsl │ │ ├── regression/ │ │ │ ├── issue_3349.fs.wgsl │ │ │ ├── issue_3349.rs │ │ │ ├── issue_3349.vs.wgsl │ │ │ ├── issue_3457.rs │ │ │ ├── issue_3457.wgsl │ │ │ ├── issue_4024.rs │ │ │ ├── issue_4122.rs │ │ │ ├── issue_4485.rs │ │ │ ├── issue_4485.wgsl │ │ │ ├── issue_4514.rs │ │ │ ├── issue_4514.wgsl │ │ │ ├── issue_5553.rs │ │ │ ├── issue_5553.wgsl │ │ │ ├── issue_6317.rs │ │ │ ├── issue_6467.rs │ │ │ └── issue_6827.rs │ │ ├── render_pass_ownership.rs │ │ ├── render_target.rs │ │ ├── resource_descriptor_accessor.rs │ │ ├── resource_error.rs │ │ ├── samplers.rs │ │ ├── scissor_tests/ │ │ │ ├── mod.rs │ │ │ └── solid_white.wgsl │ │ ├── shader/ │ │ │ ├── array_size_overrides.rs │ │ │ ├── compilation_messages/ │ │ │ │ ├── error_shader.wgsl │ │ │ │ ├── mod.rs │ │ │ │ └── successful_shader.wgsl │ │ │ ├── data_builtins.rs │ │ │ ├── mod.rs │ │ │ ├── numeric_builtins.rs │ │ │ ├── shader_test.wgsl │ │ │ ├── struct_layout.rs │ │ │ ├── workgroup_size_overrides.rs │ │ │ ├── zero_init_workgroup_mem.rs │ │ │ └── zero_init_workgroup_mem.wgsl │ │ ├── shader_barycentric/ │ │ │ ├── barycentric.wgsl │ │ │ └── mod.rs │ │ ├── shader_primitive_index/ │ │ │ ├── mod.rs │ │ │ └── primitive_index.wgsl │ │ ├── shader_view_format/ │ │ │ ├── mod.rs │ │ │ └── view_format.wgsl │ │ ├── subgroup_operations/ │ │ │ ├── mod.rs │ │ │ └── shader.wgsl │ │ ├── texture_binding/ │ │ │ ├── mod.rs │ │ │ ├── shader.wgsl │ │ │ └── single_scalar.wgsl │ │ ├── texture_blit.rs │ │ ├── texture_bounds.rs │ │ ├── texture_view_creation.rs │ │ ├── timestamp_normalization/ │ │ │ ├── mod.rs │ │ │ ├── shift_right_u96.wgsl │ │ │ ├── u64_mul_u32.wgsl │ │ │ └── utils.rs │ │ ├── timestamp_query.rs │ │ ├── transfer.rs │ │ ├── transient.rs │ │ ├── transition_resources.rs │ │ ├── vertex_formats/ │ │ │ ├── draw.vert.wgsl │ │ │ └── mod.rs │ │ ├── vertex_indices/ │ │ │ ├── draw.vert.wgsl │ │ │ └── mod.rs │ │ ├── vertex_state.rs │ │ ├── write_texture.rs │ │ └── zero_init_texture_after_discard.rs │ ├── wgpu-validation/ │ │ ├── api/ │ │ │ ├── binding_arrays.rs │ │ │ ├── buffer.rs │ │ │ ├── buffer_mapping.rs │ │ │ ├── buffer_slice.rs │ │ │ ├── command_buffer_actions.rs │ │ │ ├── device.rs │ │ │ ├── encoding.rs │ │ │ ├── error_scopes.rs │ │ │ ├── experimental.rs │ │ │ ├── external_texture.rs │ │ │ ├── instance.rs │ │ │ ├── mod.rs │ │ │ ├── render_pipeline.rs │ │ │ └── texture.rs │ │ ├── main.rs │ │ ├── noop.rs │ │ └── util.rs │ └── wgpu_trace.rs ├── typos.toml ├── wgpu/ │ ├── Cargo.toml │ ├── LICENSE.APACHE │ ├── LICENSE.MIT │ ├── build.rs │ └── src/ │ ├── api/ │ │ ├── adapter.rs │ │ ├── bind_group.rs │ │ ├── bind_group_layout.rs │ │ ├── blas.rs │ │ ├── buffer.rs │ │ ├── command_buffer.rs │ │ ├── command_buffer_actions.rs │ │ ├── command_encoder.rs │ │ ├── common_pipeline.rs │ │ ├── compute_pass.rs │ │ ├── compute_pipeline.rs │ │ ├── device.rs │ │ ├── external_texture.rs │ │ ├── instance.rs │ │ ├── mod.rs │ │ ├── pipeline_cache.rs │ │ ├── pipeline_layout.rs │ │ ├── query_set.rs │ │ ├── queue.rs │ │ ├── render_bundle.rs │ │ ├── render_bundle_encoder.rs │ │ ├── render_pass.rs │ │ ├── render_pipeline.rs │ │ ├── sampler.rs │ │ ├── shader_module.rs │ │ ├── surface.rs │ │ ├── surface_texture.rs │ │ ├── texture.rs │ │ ├── texture_view.rs │ │ └── tlas.rs │ ├── backend/ │ │ ├── custom.rs │ │ ├── mod.rs │ │ ├── webgpu/ │ │ │ ├── defined_non_null_js_value.rs │ │ │ ├── ext_bindings.rs │ │ │ └── webgpu_sys/ │ │ │ ├── gen_Gpu.rs │ │ │ ├── gen_GpuAdapter.rs │ │ │ ├── gen_GpuAdapterInfo.rs │ │ │ ├── gen_GpuAddressMode.rs │ │ │ ├── gen_GpuAutoLayoutMode.rs │ │ │ ├── gen_GpuBindGroup.rs │ │ │ ├── gen_GpuBindGroupDescriptor.rs │ │ │ ├── gen_GpuBindGroupEntry.rs │ │ │ ├── gen_GpuBindGroupLayout.rs │ │ │ ├── gen_GpuBindGroupLayoutDescriptor.rs │ │ │ ├── gen_GpuBindGroupLayoutEntry.rs │ │ │ ├── gen_GpuBlendComponent.rs │ │ │ ├── gen_GpuBlendFactor.rs │ │ │ ├── gen_GpuBlendOperation.rs │ │ │ ├── gen_GpuBlendState.rs │ │ │ ├── gen_GpuBuffer.rs │ │ │ ├── gen_GpuBufferBinding.rs │ │ │ ├── gen_GpuBufferBindingLayout.rs │ │ │ ├── gen_GpuBufferBindingType.rs │ │ │ ├── gen_GpuBufferDescriptor.rs │ │ │ ├── gen_GpuBufferMapState.rs │ │ │ ├── gen_GpuCanvasAlphaMode.rs │ │ │ ├── gen_GpuCanvasConfiguration.rs │ │ │ ├── gen_GpuCanvasContext.rs │ │ │ ├── gen_GpuCanvasToneMapping.rs │ │ │ ├── gen_GpuCanvasToneMappingMode.rs │ │ │ ├── gen_GpuColorDict.rs │ │ │ ├── gen_GpuColorTargetState.rs │ │ │ ├── gen_GpuCommandBuffer.rs │ │ │ ├── gen_GpuCommandBufferDescriptor.rs │ │ │ ├── gen_GpuCommandEncoder.rs │ │ │ ├── gen_GpuCommandEncoderDescriptor.rs │ │ │ ├── gen_GpuCompareFunction.rs │ │ │ ├── gen_GpuCompilationInfo.rs │ │ │ ├── gen_GpuCompilationMessage.rs │ │ │ ├── gen_GpuCompilationMessageType.rs │ │ │ ├── gen_GpuComputePassDescriptor.rs │ │ │ ├── gen_GpuComputePassEncoder.rs │ │ │ ├── gen_GpuComputePassTimestampWrites.rs │ │ │ ├── gen_GpuComputePipeline.rs │ │ │ ├── gen_GpuComputePipelineDescriptor.rs │ │ │ ├── gen_GpuCopyExternalImageDestInfo.rs │ │ │ ├── gen_GpuCopyExternalImageSourceInfo.rs │ │ │ ├── gen_GpuCullMode.rs │ │ │ ├── gen_GpuDepthStencilState.rs │ │ │ ├── gen_GpuDevice.rs │ │ │ ├── gen_GpuDeviceDescriptor.rs │ │ │ ├── gen_GpuDeviceLostInfo.rs │ │ │ ├── gen_GpuDeviceLostReason.rs │ │ │ ├── gen_GpuError.rs │ │ │ ├── gen_GpuErrorFilter.rs │ │ │ ├── gen_GpuExtent3dDict.rs │ │ │ ├── gen_GpuExternalTexture.rs │ │ │ ├── gen_GpuExternalTextureBindingLayout.rs │ │ │ ├── gen_GpuExternalTextureDescriptor.rs │ │ │ ├── gen_GpuFeatureName.rs │ │ │ ├── gen_GpuFilterMode.rs │ │ │ ├── gen_GpuFragmentState.rs │ │ │ ├── gen_GpuFrontFace.rs │ │ │ ├── gen_GpuIndexFormat.rs │ │ │ ├── gen_GpuLoadOp.rs │ │ │ ├── gen_GpuMipmapFilterMode.rs │ │ │ ├── gen_GpuMultisampleState.rs │ │ │ ├── gen_GpuObjectDescriptorBase.rs │ │ │ ├── gen_GpuOrigin2dDict.rs │ │ │ ├── gen_GpuOrigin3dDict.rs │ │ │ ├── gen_GpuOutOfMemoryError.rs │ │ │ ├── gen_GpuPipelineDescriptorBase.rs │ │ │ ├── gen_GpuPipelineLayout.rs │ │ │ ├── gen_GpuPipelineLayoutDescriptor.rs │ │ │ ├── gen_GpuPowerPreference.rs │ │ │ ├── gen_GpuPrimitiveState.rs │ │ │ ├── gen_GpuPrimitiveTopology.rs │ │ │ ├── gen_GpuProgrammableStage.rs │ │ │ ├── gen_GpuQuerySet.rs │ │ │ ├── gen_GpuQuerySetDescriptor.rs │ │ │ ├── gen_GpuQueryType.rs │ │ │ ├── gen_GpuQueue.rs │ │ │ ├── gen_GpuQueueDescriptor.rs │ │ │ ├── gen_GpuRenderBundle.rs │ │ │ ├── gen_GpuRenderBundleDescriptor.rs │ │ │ ├── gen_GpuRenderBundleEncoder.rs │ │ │ ├── gen_GpuRenderBundleEncoderDescriptor.rs │ │ │ ├── gen_GpuRenderPassColorAttachment.rs │ │ │ ├── gen_GpuRenderPassDepthStencilAttachment.rs │ │ │ ├── gen_GpuRenderPassDescriptor.rs │ │ │ ├── gen_GpuRenderPassEncoder.rs │ │ │ ├── gen_GpuRenderPassTimestampWrites.rs │ │ │ ├── gen_GpuRenderPipeline.rs │ │ │ ├── gen_GpuRenderPipelineDescriptor.rs │ │ │ ├── gen_GpuRequestAdapterOptions.rs │ │ │ ├── gen_GpuSampler.rs │ │ │ ├── gen_GpuSamplerBindingLayout.rs │ │ │ ├── gen_GpuSamplerBindingType.rs │ │ │ ├── gen_GpuSamplerDescriptor.rs │ │ │ ├── gen_GpuShaderModule.rs │ │ │ ├── gen_GpuShaderModuleDescriptor.rs │ │ │ ├── gen_GpuStencilFaceState.rs │ │ │ ├── gen_GpuStencilOperation.rs │ │ │ ├── gen_GpuStorageTextureAccess.rs │ │ │ ├── gen_GpuStorageTextureBindingLayout.rs │ │ │ ├── gen_GpuStoreOp.rs │ │ │ ├── gen_GpuSupportedFeatures.rs │ │ │ ├── gen_GpuSupportedLimits.rs │ │ │ ├── gen_GpuTexelCopyBufferInfo.rs │ │ │ ├── gen_GpuTexelCopyBufferLayout.rs │ │ │ ├── gen_GpuTexelCopyTextureInfo.rs │ │ │ ├── gen_GpuTexture.rs │ │ │ ├── gen_GpuTextureAspect.rs │ │ │ ├── gen_GpuTextureBindingLayout.rs │ │ │ ├── gen_GpuTextureDescriptor.rs │ │ │ ├── gen_GpuTextureDimension.rs │ │ │ ├── gen_GpuTextureFormat.rs │ │ │ ├── gen_GpuTextureSampleType.rs │ │ │ ├── gen_GpuTextureView.rs │ │ │ ├── gen_GpuTextureViewDescriptor.rs │ │ │ ├── gen_GpuTextureViewDimension.rs │ │ │ ├── gen_GpuUncapturedErrorEvent.rs │ │ │ ├── gen_GpuUncapturedErrorEventInit.rs │ │ │ ├── gen_GpuValidationError.rs │ │ │ ├── gen_GpuVertexAttribute.rs │ │ │ ├── gen_GpuVertexBufferLayout.rs │ │ │ ├── gen_GpuVertexFormat.rs │ │ │ ├── gen_GpuVertexState.rs │ │ │ ├── gen_GpuVertexStepMode.rs │ │ │ ├── gen_WgslLanguageFeatures.rs │ │ │ ├── gen_gpu_map_mode.rs │ │ │ └── mod.rs │ │ ├── webgpu.rs │ │ ├── wgpu_core/ │ │ │ └── thread_id.rs │ │ └── wgpu_core.rs │ ├── cmp.rs │ ├── dispatch.rs │ ├── lib.rs │ ├── macros/ │ │ ├── be-aligned.spv │ │ ├── le-aligned.spv │ │ └── mod.rs │ └── util/ │ ├── belt.rs │ ├── blit.wgsl │ ├── device.rs │ ├── encoder.rs │ ├── init.rs │ ├── mod.rs │ ├── mutex.rs │ ├── panicking.rs │ ├── spirv.rs │ └── texture_blitter.rs ├── wgpu-core/ │ ├── Cargo.toml │ ├── LICENSE.APACHE │ ├── LICENSE.MIT │ ├── build.rs │ ├── platform-deps/ │ │ ├── apple/ │ │ │ ├── Cargo.toml │ │ │ ├── LICENSE.APACHE │ │ │ ├── LICENSE.MIT │ │ │ ├── README.md │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── emscripten/ │ │ │ ├── Cargo.toml │ │ │ ├── LICENSE.APACHE │ │ │ ├── LICENSE.MIT │ │ │ ├── README.md │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── wasm/ │ │ │ ├── Cargo.toml │ │ │ ├── LICENSE.APACHE │ │ │ ├── LICENSE.MIT │ │ │ ├── README.md │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── windows-linux-android/ │ │ ├── Cargo.toml │ │ ├── LICENSE.APACHE │ │ ├── LICENSE.MIT │ │ ├── README.md │ │ └── src/ │ │ └── lib.rs │ └── src/ │ ├── as_hal.rs │ ├── binding_model.rs │ ├── command/ │ │ ├── allocator.rs │ │ ├── bind.rs │ │ ├── bundle.rs │ │ ├── clear.rs │ │ ├── compute.rs │ │ ├── compute_command.rs │ │ ├── draw.rs │ │ ├── encoder.rs │ │ ├── encoder_command.rs │ │ ├── ffi.rs │ │ ├── memory_init.rs │ │ ├── mod.rs │ │ ├── pass.rs │ │ ├── query.rs │ │ ├── ray_tracing.rs │ │ ├── render.rs │ │ ├── render_command.rs │ │ ├── timestamp_writes.rs │ │ ├── transfer.rs │ │ └── transition_resources.rs │ ├── conv.rs │ ├── device/ │ │ ├── bgl.rs │ │ ├── global.rs │ │ ├── life.rs │ │ ├── mod.rs │ │ ├── queue.rs │ │ ├── ray_tracing.rs │ │ ├── resource.rs │ │ ├── trace/ │ │ │ ├── record.rs │ │ │ └── replay.rs │ │ └── trace.rs │ ├── error.rs │ ├── global.rs │ ├── hash_utils.rs │ ├── hub.rs │ ├── id.rs │ ├── identity.rs │ ├── indirect_validation/ │ │ ├── dispatch.rs │ │ ├── draw.rs │ │ ├── mod.rs │ │ ├── utils.rs │ │ └── validate_draw.wgsl │ ├── init_tracker/ │ │ ├── buffer.rs │ │ ├── mod.rs │ │ └── texture.rs │ ├── instance.rs │ ├── lib.rs │ ├── lock/ │ │ ├── mod.rs │ │ ├── observing.rs │ │ ├── rank.rs │ │ ├── ranked.rs │ │ └── vanilla.rs │ ├── pipeline.rs │ ├── pipeline_cache.rs │ ├── pool.rs │ ├── present.rs │ ├── ray_tracing.rs │ ├── registry.rs │ ├── resource.rs │ ├── scratch.rs │ ├── snatch.rs │ ├── storage.rs │ ├── timestamp_normalization/ │ │ ├── common.wgsl │ │ ├── mod.rs │ │ └── timestamp_normalization.wgsl │ ├── track/ │ │ ├── blas.rs │ │ ├── buffer.rs │ │ ├── metadata.rs │ │ ├── mod.rs │ │ ├── range.rs │ │ ├── stateless.rs │ │ └── texture.rs │ ├── validation/ │ │ └── shader_io_deductions.rs │ ├── validation.rs │ └── weak_vec.rs ├── wgpu-hal/ │ ├── Cargo.toml │ ├── LICENSE.APACHE │ ├── LICENSE.MIT │ ├── README.md │ ├── build.rs │ ├── examples/ │ │ ├── halmark/ │ │ │ ├── main.rs │ │ │ └── shader.wgsl │ │ ├── raw-gles.em.html │ │ ├── raw-gles.rs │ │ └── ray-traced-triangle/ │ │ ├── main.rs │ │ └── shader.wgsl │ └── src/ │ ├── auxil/ │ │ ├── dxgi/ │ │ │ ├── conv.rs │ │ │ ├── exception.rs │ │ │ ├── factory.rs │ │ │ ├── mod.rs │ │ │ ├── name.rs │ │ │ ├── result.rs │ │ │ └── time.rs │ │ ├── mod.rs │ │ └── renderdoc.rs │ ├── dx12/ │ │ ├── adapter.rs │ │ ├── command.rs │ │ ├── conv.rs │ │ ├── dcomp.rs │ │ ├── descriptor.rs │ │ ├── device.rs │ │ ├── device_creation.rs │ │ ├── instance.rs │ │ ├── mod.rs │ │ ├── pipeline_desc.rs │ │ ├── sampler.rs │ │ ├── shader_compilation.rs │ │ ├── suballocation.rs │ │ ├── types.rs │ │ └── view.rs │ ├── dynamic/ │ │ ├── adapter.rs │ │ ├── command.rs │ │ ├── device.rs │ │ ├── instance.rs │ │ ├── mod.rs │ │ ├── queue.rs │ │ └── surface.rs │ ├── gles/ │ │ ├── adapter.rs │ │ ├── command.rs │ │ ├── conv.rs │ │ ├── device.rs │ │ ├── egl.rs │ │ ├── emscripten.rs │ │ ├── fence.rs │ │ ├── mod.rs │ │ ├── queue.rs │ │ ├── shaders/ │ │ │ ├── clear.frag │ │ │ ├── clear.vert │ │ │ ├── srgb_present.frag │ │ │ └── srgb_present.vert │ │ ├── web.rs │ │ └── wgl.rs │ ├── lib.rs │ ├── metal/ │ │ ├── adapter.rs │ │ ├── command.rs │ │ ├── conv.rs │ │ ├── device.rs │ │ ├── library_from_metallib.rs │ │ ├── mod.rs │ │ ├── surface.rs │ │ └── time.rs │ ├── noop/ │ │ ├── buffer.rs │ │ ├── command.rs │ │ └── mod.rs │ ├── validation_canary.rs │ └── vulkan/ │ ├── adapter.rs │ ├── command.rs │ ├── conv.rs │ ├── device.rs │ ├── drm.rs │ ├── instance.rs │ ├── mod.rs │ ├── sampler.rs │ ├── semaphore_list.rs │ └── swapchain/ │ ├── mod.rs │ └── native.rs ├── wgpu-info/ │ ├── Cargo.toml │ ├── LICENSE.APACHE │ ├── LICENSE.MIT │ ├── README.md │ └── src/ │ ├── cli.rs │ ├── human.rs │ ├── main.rs │ ├── report.rs │ ├── tests.rs │ └── texture.rs ├── wgpu-macros/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── wgpu-naga-bridge/ │ ├── Cargo.toml │ ├── LICENSE.APACHE │ ├── LICENSE.MIT │ └── src/ │ └── lib.rs ├── wgpu-types/ │ ├── Cargo.toml │ ├── LICENSE.APACHE │ ├── LICENSE.MIT │ └── src/ │ ├── adapter.rs │ ├── assertions.rs │ ├── backend.rs │ ├── binding.rs │ ├── buffer.rs │ ├── cast_utils.rs │ ├── counters.rs │ ├── device.rs │ ├── env.rs │ ├── error.rs │ ├── features.rs │ ├── instance.rs │ ├── lib.rs │ ├── limits.rs │ ├── math.rs │ ├── origin_extent.rs │ ├── ray_tracing.rs │ ├── render.rs │ ├── send_sync.rs │ ├── shader.rs │ ├── surface.rs │ ├── texture/ │ │ ├── external_image.rs │ │ ├── external_texture.rs │ │ └── format.rs │ ├── texture.rs │ ├── tokens.rs │ ├── transfers.rs │ ├── vertex.rs │ └── write_only.rs └── xtask/ ├── Cargo.toml └── src/ ├── changelog.rs ├── cts.rs ├── install_agility_sdk.rs ├── install_warp.rs ├── main.rs ├── miri.rs ├── run_wasm.rs ├── test.rs ├── util.rs └── vendor_web_sys.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [alias] xtask = "run -p wgpu-xtask --" ================================================ FILE: .claude/skills/cts-triage/SKILL.md ================================================ --- name: cts-triage description: Run CTS test suites and investigate failures --- # Triage Process When working on a category of CTS tests, follow this systematic process to identify issues, prioritize fixes, and document findings. ## Step 0: Divide Into Manageable Chunks List all the tests matching a selector: ```bash cargo xtask cts -- --list 'webgpu:api,validation,*' 2>&1 | wc -l ``` If there is a reasonable number of tests matching the selector (less than a couple hundred or so, but this isn't a hard cutoff), then you can proceed with triage. Otherwise, make a list of more detailed wildcards that match fewer tests, verify that each wildcard matches a reasonable number of tests, and triage each wildcard separately. ## Step 1: Get Overall Statistics Run the full test suite for the category to understand the scope: ```bash cargo xtask cts 'webgpu:api,validation,category:*' 2>&1 | grep -E "(Summary|Passed|Failed)" | tail -5 ``` This gives you the pass rate and number of failures. Document this as your baseline. ## Step 2: Identify Test Subcategories Review the output from the running the CTS (with or without `--list`) to identify any subcategories that may exist within the suite being analyzed. Subcategories typically have an additional `:`- or `,`-delimited word in the test name. Running tests by subcategory may be more manageable than running with the entire suite at once or running individual tests. ## Step 3: Run Each Subcategory Test each subcategory individually to identify which ones are passing vs failing: ```bash cargo xtask cts 'webgpu:api,validation,category:subcategory:*' 2>&1 | grep -E "(Passed|Failed|Skipped)" | tail -3 ``` Track the pass rate for each subcategory. This helps you identify: - What's already working (don't break it!) - Where the failures are concentrated - Which issues affect multiple categories ## Step 4: Analyze Failure Patterns For failing subcategories, look at what tests are failing: ```bash cargo xtask cts 'webgpu:api,validation,category:subcategory:*' 2>&1 | grep "\[fail\]" | head -20 ``` Look for patterns: - **Format-specific failures**: May indicate missing format capability checks - **Parameter-specific failures**: Validation missing for specific parameter combinations ## Step 5: Examine Specific Failures Pick a representative failing test and run it individually to see the error: ```bash cargo xtask cts 'webgpu:api,validation,category:subcategory:specific_test' 2>&1 | tail -30 ``` Look for: - **"EXPECTATION FAILED: DID NOT REJECT"**: wgpu is accepting invalid input (validation gap) - **"Validation succeeded unexpectedly"**: Similar to above - **"Unexpected validation error occurred"**: wgpu is rejecting valid input - **Error message content**: Tells you what validation is triggering or missing ## Step 6: Check CTS Test Source To understand what the test expects, read the TypeScript source: ```bash grep -A 40 "test_name" cts/src/webgpu/api/validation/path/file.spec.ts ``` The test source shows: - What configurations are being tested - What the expected behavior is (pass/fail) - The validation rules from the WebGPU spec - Comments explaining the rationale ## Step 7: Categorize Issues Group failures into categories: **High Priority - Validation Gaps:** - wgpu accepts invalid configurations that should fail - Security or correctness implications - Example: Accepting wrong texture formats, missing aspect checks **Medium Priority - Spec Compliance:** - Edge cases not handled correctly - Optional field validation issues - Example: depthCompare optional field handling **Low Priority - Minor Gaps:** - Less common scenarios - Limited real-world impact - Example: Depth bias with non-triangle topologies **Known Issues - Skip:** - Known failure patterns (documented in AGENTS.md) - Track count but don't try to fix ## Step 8: Identify Root Causes For validation gaps, find where validation should happen: 1. **Search for existing validation:** ```bash grep -n "relevant_keyword" wgpu-core/src/device/resource.rs ``` 2. **Look for render/compute pipeline creation:** - Render pipeline: `wgpu-core/src/device/resource.rs` around `create_render_pipeline` - Compute pipeline: Similar location - Look for existing validation patterns you can follow 3. **Check for helper functions:** ```bash grep "fn is_" wgpu-types/src/texture/format.rs ``` 4. **Find error enums:** ```bash grep "pub enum.*Error" wgpu-core/src/pipeline.rs ``` ## Step 9: Implement Fixes When implementing fixes: 1. **Add error variants if needed** (in `wgpu-core/src/pipeline.rs`) 2. **Add helper methods** (in `wgpu-types` if checking properties) 3. **Add validation checks** (in `wgpu-core/src/device/resource.rs`) 4. **Test the fix** with specific failing tests 5. **Run full subcategory** to verify all related tests pass 6. **Check you didn't break passing tests** ## Step 10: Document Findings Create or update a triage document (e.g., `category_triage.md`). Do not write information about changes you have made to the triage document. Only capture the state of the tests and any investigation into open issues. ````markdown # Category CTS Tests - Triage Report **Overall Status:** XP/YF/ZS (%/%/%) ## Passing Sub-suites ✅ [List sub-suites that have no failures (all pass or skip)] ## Remaining Issues ⚠️ [List sub-suites that have failures and if it can be stated concisely, a summary of the issue] ## Issue Detail [List detail of any investigation into failures. Do not go into detail about passed suites, just list the failures.] ### 1. title, e.g. a distinguishing word from the test selector **Test selector:** `webgpu:api,validation,render_pipeline,depth_stencil_state:format:*` **What it tests:** [Description] **Example failure:** [a selector for a single failing test, e.g.:] ``` webgpu:api,validation,render_pipeline,depth_stencil_state:depthCompare_optional:isAsync=false;format="stencil8" ``` **Error:** [error message from the failing tests, e.g.:] ``` Unexpected validation error occurred: Depth/stencil state is invalid: Format Stencil8 does not have a depth aspect, but depth test/write is enabled ``` **Root cause:** [Your analysis of the root cause. Do not speculate. Only include the results of specific investigation you have done.] The validation is triggering incorrectly. When `depthCompare` is undefined/missing in JavaScript, it's getting translated to a default value that makes `is_depth_enabled()` return true, even for stencil-only formats. **Fix needed:** [Your proposed fix. Again, do not speculate. Only state the fix if it is obvious from the root cause analysis, or if you have done specific investigation into how to fix it.] ### 2. title [repeat as needed for additional issues] ```` ## Step 11: Update test.lst For fixed tests that are now passing, add them to `cts_runner/test.lst`: 1. **Use wildcards** to minimize lines: ``` webgpu:api,validation,category:subcategory:isAsync=false;* ``` 2. **Group related tests** when possible: - If multiple subcategories all pass, use a higher-level wildcard - Example: `webgpu:api,validation,category:*` if all subcategories pass 3. **Maintain alphabetical order** roughly in the file ## Step 12: Verify and Build Before finishing: ```bash # Format code cargo fmt # Check for errors cargo clippy --tests # Build to ensure no compilation errors cargo build # Run the tests you added to test.lst cargo xtask cts 'webgpu:api,validation,category:subcategory:isAsync=false;*' ``` # Common Patterns If the user asked to investigate a failure, and the cause of the failure is noted here with "do not attempt to fix", then stop and ask the user before attempting to fix. **Pattern: Format-specific failures** - Check if format validation is missing - Look for `is_depth_stencil_format()`, `is_color_format()` etc. - May need to add format capability checks **Pattern: Aspect-related failures** - Check if code validates format aspects (DEPTH, STENCIL, COLOR) - Use `hal::FormatAspects::from(format)` to check - Validate operations match available aspects **Pattern: Optional field failures** - May be WebGPU optional field semantics issue - Check if undefined in JS becomes a default value in Rust - May need to distinguish "not set" from "set to default" **Pattern: Atomics accepted incorrectly** - Naga allows referencing an atomic directly in an expression - Should only allow accessing via `atomicLoad`, `atomicStore`, etc. - Only investigate as necessary to confirm this is the issue. Do not attempt to fix. Refer user to https://github.com/gfx-rs/wgpu/issues/5474. **Pattern: Error reporting for destroyed resources** - Tests that check for validation errors when a destroyed resource is used. `wgpu` often reports these errors later than WebGPU requires, causing the tests to fail. - `wgpu` may report these errors earlier than it should, causing the test to fail with an unexpected validation error. - Look for: - Tests with `state="destroyed"` parameter - Tests checking that operations on destroyed buffers or textures should fail - Example failing tests: - `webgpu:api,validation,encoding,cmds,compute_pass:indirect_dispatch_buffer_state:` with `state="destroyed"` subcases - Only investigate as necessary to confirm this is the issue. Do not attempt to fix. Refer user to https://github.com/gfx-rs/wgpu/issues/7881. # Tips - **Start with high-impact fixes**: Validation gaps with security implications - **Look for existing patterns**: Other validation code shows the style - **Test incrementally**: Fix one category at a time, verify it works - **Document as you go**: Don't wait until the end to write the triage - **Ask for clarification**: If test expectations are unclear, check the WebGPU spec or test source - **Track your progress**: Update pass rates as you fix issues ================================================ FILE: .claude/skills/webgpu-specs/SKILL.md ================================================ --- name: webgpu-specs description: Download WebGPU and WGSL specifications for use as a reference allowed-tools: "Bash(sh .claude/skills/webgpu-specs/download.sh)" --- Run `sh .claude/skills/webgpu-specs/download.sh` to download the WebGPU and WGSL specifications if they are not present or if they have been updated. You do not need to change directory before running the script. After the specs are downloaded, you can search in `target/claude/webgpu-spec.bs` and `target/claude/wgsl-spec.bs` for relevant sections of the specification. When referencing the specifications, prefer to use named anchors rather than line numbers. For example, to reference the "Object Descriptors" section, which has the following header: ``` ### Object Descriptors ### {#object-descriptors} ``` Use the URL so the user can click to navigate directly to that section. For the WGSL specification, the base URL is . If necessary, read additional content from the file to find the header preceding the text you want to reference. You may provide line numbers as additional context, but always make every effort to provide the user with a clickable link. ================================================ FILE: .claude/skills/webgpu-specs/download.sh ================================================ #!/bin/sh set -e TARGET_DIR="$(cargo metadata --format-version 1 | jq -r ".target_directory")" WEBGPU="$TARGET_DIR/claude/webgpu-spec" WGSL="$TARGET_DIR/claude/wgsl-spec" mkdir -p "$TARGET_DIR/claude" if [ -f "$WEBGPU.etag" ]; then curl --etag-save "$WEBGPU.etag.new" --etag-compare "$WEBGPU.etag" -fsSL https://raw.githubusercontent.com/gpuweb/gpuweb/main/spec/index.bs -o "$WEBGPU.bs" [ -s "$WEBGPU.etag.new" ] && mv "$WEBGPU.etag.new" "$WEBGPU.etag" || rm "$WEBGPU.etag.new" else curl --etag-save "$WEBGPU.etag" https://raw.githubusercontent.com/gpuweb/gpuweb/main/spec/index.bs -o "$WEBGPU.bs" fi if [ -f "$WGSL.etag" ]; then curl --etag-save "$WGSL.etag.new" --etag-compare "$WGSL.etag" -fsSL https://raw.githubusercontent.com/gpuweb/gpuweb/main/wgsl/index.bs -o "$WGSL.bs" [ -s "$WGSL.etag.new" ] && mv "$WGSL.etag.new" "$WGSL.etag" || rm "$WGSL.etag.new" else curl --etag-save "$WGSL.etag" https://raw.githubusercontent.com/gpuweb/gpuweb/main/wgsl/index.bs -o "$WGSL.bs" fi ================================================ FILE: .config/nextest.toml ================================================ # None of our tests should take longer than 45s, and if they've gone 2x that, # terminate them to prevent infinite run-on. [profile.default] default-filter = "!test(~oom_test)" slow-timeout = { period = "45s", terminate-after = 2 } fail-fast = false retries = 0 [profile.default-miri] # Miri is very very slow, so give it a much longer timeout. slow-timeout = { period = "120s", terminate-after = 4 } # Use two threads for tests with "2 threads" in their name [[profile.default.overrides]] filter = 'test(~2_threads) | test(~2 threads)' threads-required = 2 # Use four threads for tests with "4 threads" in their name [[profile.default.overrides]] filter = 'test(~4_threads) | test(~4 threads)' threads-required = 4 # Use eight threads for tests with "8 threads" in their name [[profile.default.overrides]] filter = 'test(~8_threads) | test(~8 threads)' threads-required = 8 # # Workarounds for flaky tests # # https://github.com/gfx-rs/wgpu/issues/7200 [[profile.default.overrides]] filter = 'test(wgpu_gpu::image_atomics::image_32_atomics)' platform = 'cfg(target_vendor = "apple")' retries = 1 [[profile.default.overrides]] filter = 'test(compile_fail)' slow-timeout = { period = "3m", terminate-after = 2 } [[profile.default.overrides]] filter = 'test(~oom_test)' threads-required = "num-test-threads" # # Priorities for slow tests so that they run first, increasing overall test suite speed. # On software renderers, they can take 10-60 seconds. Compile fail can easily take 30+ # seconds as it has to compile wgpu. # [[profile.default.overrides]] priority = 1 filter = 'test(clear_texture) | test(clear_buffer_range_respected) | test(compile_fail) | test(test_api)' ================================================ FILE: .deny.toml ================================================ [bans] multiple-versions = "deny" skip-tree = [ { name = "rustc-hash", version = "1.1.0" }, # introduced by Deno, to be investigated { name = "petgraph", version = "0.6.5" }, # Winit 0.30 uses an older objc2 { name = "objc2-foundation", version = "0.2" }, # glutin and tracy-client-sys use windows-sys 0.52, pulling older windows-targets { name = "windows-targets", version = "0.52" }, # winit uses an old version via android-activity → jni { name = "windows-targets", version = "0.42" }, ] skip = [ # flume -> fastrand uses an old version only on wasm32 { name = "getrandom", version = "0.2.17" }, # the ecosystem is migrating from getrandom 0.3 to 0.4, so this captures many stragglers { name = "getrandom", version = "0.3.4" }, # Deno uses an old version { name = "bincode", version = "1.3.3" }, { name = "which", version = "6.0.3" }, # Winit uses an old version via android-activity → jni { name = "windows-sys", version = "0.45" }, # Winit uses an old version via calloop → rustix 0.38 { name = "linux-raw-sys", version = "0.4" }, { name = "rustix", version = "0.38" }, { name = "windows-sys", version = "0.59" }, # Winit uses an old version via android-activity { name = "thiserror", version = "1" }, { name = "thiserror-impl", version = "1" }, # glutin uses an old version { name = "windows-sys", version = "0.52" }, # getrandom 0.3 uses an old version { name = "r-efi", version = "5" }, # parking-lot uses an old version { name = "redox_syscall", version = "0.5.18" }, # deno uses an old version { name = "bit-vec", version = "0.8.0" }, { name = "bit-set", version = "0.8.0" }, ] wildcards = "deny" allow-wildcard-paths = true [advisories] ignore = [ # `paste` crate is no longer maintained https://rustsec.org/advisories/RUSTSEC-2024-0436 # It's a dependency of `metal` (which is to be replaced with `objc2-metal`), and a # transitive dependency of `deno`. https://github.com/gfx-rs/wgpu/issues/7873 "RUSTSEC-2024-0436", # `unic-*` crates are no longer maintained https://rustsec.org/advisories/RUSTSEC-2025-0100 # These are used via `deno`. https://github.com/gfx-rs/wgpu/issues/8393 "RUSTSEC-2025-0075", "RUSTSEC-2025-0080", "RUSTSEC-2025-0081", "RUSTSEC-2025-0098", "RUSTSEC-2025-0100", # `bincode` is no longer maintained https://rustsec.org/advisories/RUSTSEC-2025-0141 # We only use it directly for tests and tools. It is also used indirectly via deno. "RUSTSEC-2025-0141", ] [licenses] allow = [ "Apache-2.0", "Apache-2.0 WITH LLVM-exception", "BSD-2-Clause", "BSD-3-Clause", "CC0-1.0", "ISC", "MPL-2.0", "MIT", "MIT-0", "Unicode-3.0", "Zlib", ] private = { ignore = true } [sources] allow-git = [ # Waiting on releases; used in examples/tests only # Pending a release for https://github.com/rust-cli/env_logger/commit/143fa647ab33ed3acc9f160dfa3cb075cc62b5a3 "https://github.com/rust-cli/env_logger", # Pending merge/release for https://github.com/LukasKalbertodt/libtest-mimic/pull/58 "https://github.com/cwfitzgerald/libtest-mimic", ] unknown-registry = "deny" unknown-git = "deny" required-git-spec = "rev" [sources.allow-org] github = [] ================================================ FILE: .gitattributes ================================================ *.mtl binary *.obj binary wgpu/src/backend/webgpu/webgpu_sys/** linguist-generated=true ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Description** A clear and concise description of what the bug is. **Repro steps** Ideally, a runnable example we can check out. **Expected vs observed behavior** Clearly describe what you get, and how it goes across your expectations. **Extra materials** Screenshots to help explain your problem. Validation logs can be attached in case there are warnings and errors. Zip-compressed API traces and GPU captures can also land here. **Platform** Information about your OS, version of `wgpu`, your tech stack, etc. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Question about wgpu url: https://github.com/gfx-rs/wgpu/discussions/new/choose about: Any questions about how to use wgpu should go here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/other.md ================================================ --- name: Other about: Strange things you want to tell us title: '' labels: question assignees: '' --- ================================================ FILE: .github/actions/install-agility-sdk/action.yml ================================================ name: "Install Agility SDK" description: "Install the D3D12 Agility SDK and export its environment variables" runs: using: "composite" steps: - shell: bash run: | set -e cargo xtask install-agility-sdk >> "$GITHUB_ENV" echo WGPU_DX12_AGILITY_SDK_REQUIRE=1 >> "$GITHUB_ENV" ================================================ FILE: .github/actions/install-dxc/action.yml ================================================ name: "Install DXC" description: "Install DXC" runs: using: "composite" steps: - shell: bash run: | set -e export DXC_RELEASE="v1.8.2505.1" export DXC_FILENAME="dxc_2025_07_14.zip" curl.exe -L --retry 5 https://github.com/microsoft/DirectXShaderCompiler/releases/download/$DXC_RELEASE/$DXC_FILENAME -o dxc.zip 7z.exe e dxc.zip -odxc bin/x64/{dxc.exe,dxcompiler.dll} # We need to use cygpath to convert PWD to a windows path as we're using bash. cygpath --windows "$PWD/dxc" >> "$GITHUB_PATH" ================================================ FILE: .github/actions/install-mesa/action.yml ================================================ name: "Install Mesa" description: "Install Mesa" inputs: # Sourced from https://archive.mesa3d.org/. Bumping this requires # updating the mesa build in https://github.com/gfx-rs/ci-build and creating a new release. version: default: "25.2.7" # Corresponds to https://github.com/gfx-rs/ci-build/releases ci-binary-build: default: "build26" target-dir: description: "The directory into which to install the Mesa libraries." default: "target/llvm-cov-target/debug" runs: using: "composite" steps: - name: (Linux) Install Mesa if: runner.os == 'Linux' shell: bash env: MESA_VERSION: ${{ inputs.version }} CI_BINARY_BUILD: ${{ inputs.ci-binary-build }} run: | set -e curl -L --retry 5 https://github.com/gfx-rs/ci-build/releases/download/$CI_BINARY_BUILD/mesa-$MESA_VERSION-linux-x86_64.tar.xz -o mesa.tar.xz mkdir mesa tar xpf mesa.tar.xz -C mesa # The ICD provided by the mesa build is hardcoded to the build environment. # # We write out our own ICD file to point to the mesa vulkan cat <<- EOF > icd.json { "ICD": { "api_version": "1.1.255", "library_path": "$PWD/mesa/lib/x86_64-linux-gnu/libvulkan_lvp.so" }, "file_format_version": "1.0.0" } EOF echo "VK_DRIVER_FILES=$PWD/icd.json" >> "$GITHUB_ENV" echo "LD_LIBRARY_PATH=$PWD/mesa/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH" >> "$GITHUB_ENV" echo "LIBGL_DRIVERS_PATH=$PWD/mesa/lib/x86_64-linux-gnu/dri" >> "$GITHUB_ENV" - name: (Windows) Install Mesa if: runner.os == 'Windows' shell: bash env: MESA_VERSION: ${{ inputs.version }} CI_BINARY_BUILD: ${{ inputs.ci-binary-build }} run: | set -e curl.exe -L --retry 5 https://github.com/pal1000/mesa-dist-win/releases/download/$MESA_VERSION/mesa3d-$MESA_VERSION-release-msvc.7z -o mesa.7z 7z.exe e mesa.7z -omesa x64/{opengl32.dll,libgallium_wgl.dll,libglapi.dll,vulkan_lvp.dll,lvp_icd.x86_64.json} cp -v mesa/* ${{ inputs.target-dir }}/ cp -v mesa/* ${{ inputs.target-dir }}/deps # We need to use cygpath to convert PWD to a windows path as we're using bash. echo "VK_DRIVER_FILES=`cygpath --windows $PWD/mesa/lvp_icd.x86_64.json`" >> "$GITHUB_ENV" echo "GALLIUM_DRIVER=llvmpipe" >> "$GITHUB_ENV" ================================================ FILE: .github/actions/install-vulkan-sdk/action.yml ================================================ name: "Install Vulkan SDK" description: "Install Vulkan SDK" inputs: # Sourced from https://vulkan.lunarg.com/sdk/home#linux version: default: "1.4.328" full-version: default: "1.4.328.1" runs: using: "composite" steps: - name: (Linux) Install Vulkan SDK if: runner.os == 'Linux' shell: bash env: VULKAN_SDK_VERSION: ${{ inputs.version }} VULKAN_FULL_SDK_VERSION: ${{ inputs.full-version }} run: | set -e curl -L --retry 5 https://sdk.lunarg.com/sdk/download/${{ env.VULKAN_FULL_SDK_VERSION }}/linux/vulkansdk-linux-x86_64-${{ env.VULKAN_FULL_SDK_VERSION }}.tar.xz -o vulkan-sdk.tar.xz mkdir vulkan-sdk tar xpf vulkan-sdk.tar.xz -C vulkan-sdk mv ./vulkan-sdk/${{ env.VULKAN_FULL_SDK_VERSION }} $HOME/VulkanSDK echo "$HOME/VulkanSDK/x86_64/bin" >> "$GITHUB_PATH" echo "LD_LIBRARY_PATH=$HOME/VulkanSDK/x86_64/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" >> "$GITHUB_ENV" echo "VK_ADD_LAYER_PATH=$HOME/VulkanSDK/x86_64/share/vulkan/explicit_layer.d" >> "$GITHUB_ENV" - name: (Windows) Install Vulkan SDK if: runner.os == 'Windows' shell: bash env: VULKAN_SDK_VERSION: ${{ inputs.version }} VULKAN_FULL_SDK_VERSION: ${{ inputs.full-version }} run: | set -e curl.exe -L --retry 5 https://sdk.lunarg.com/sdk/download/${{ env.VULKAN_FULL_SDK_VERSION }}/windows/vulkansdk-windows-X64-${{ env.VULKAN_FULL_SDK_VERSION }}.exe -o vulkan-sdk-installer.exe ./vulkan-sdk-installer.exe --accept-licenses --default-answer --confirm-command install echo "C:/VulkanSDK/${{ env.VULKAN_FULL_SDK_VERSION }}/Bin" >> "$GITHUB_PATH" - name: (Mac) Install Vulkan SDK if: runner.os == 'macOS' shell: bash env: VULKAN_SDK_VERSION: ${{ inputs.version }} VULKAN_FULL_SDK_VERSION: ${{ inputs.full-version }} run: | set -e curl -L --retry 5 https://sdk.lunarg.com/sdk/download/${{ env.VULKAN_FULL_SDK_VERSION }}/mac/vulkansdk-macos-${{ env.VULKAN_FULL_SDK_VERSION }}.zip -o vulkan-sdk.zip unzip vulkan-sdk.zip -d vulkan-sdk ls -l vulkan-sdk sudo ./vulkan-sdk/vulkansdk-macOS-${{ env.VULKAN_FULL_SDK_VERSION }}.app/Contents/MacOS/vulkansdk-macOS-${{ env.VULKAN_FULL_SDK_VERSION }} --root "$HOME/VulkanSDK" --accept-licenses --default-answer --confirm-command install echo "$HOME/VulkanSDK/macOS/bin" >> "$GITHUB_PATH" ================================================ FILE: .github/actions/install-warp/action.yml ================================================ name: "Install WARP" description: "Install WARP" inputs: target-dir: description: "The directory into which to install the WARP DLL." required: true runs: using: "composite" steps: - shell: bash run: | set -e cargo xtask install-warp --target-dir ${{ inputs.target-dir }} ================================================ FILE: .github/pull_request_template.md ================================================ **Connections** _Link to the issues addressed by this PR, or dependent PRs in other repositories_ _When one pull request builds on another, please put "Depends on #NNNN" towards the top of its description. This helps maintainers notice that they shouldn't merge it until its ancestor has been approved. Don't use draft PR status to indicate this._ **Description** _Describe what problem this is solving, and how it's solved._ **Testing** _Explain how this change is tested._ **Squash or Rebase?** _If your pull request contains multiple commits, please indicate whether they need to be squashed into a single commit before they're merged, or if they're ready to rebase onto `trunk` as they stand. In the latter case, please ensure that each commit passes all CI tests, so that we can continue to bisect along `trunk` to isolate bugs._ **Checklist** - [ ] Run `cargo fmt`. - [ ] Run `taplo format`. - [ ] Run `cargo clippy --tests`. If applicable, add: - [ ] `--target wasm32-unknown-unknown` - [ ] Run `cargo xtask test` to run tests. - [ ] If this contains user-facing changes, add a `CHANGELOG.md` entry. ================================================ FILE: .github/workflows/changelog.yml ================================================ name: changelog on: pull_request: paths: - ".github/workflows/changelog.yml" - "CHANGELOG.md" - "xtask/**/*" types: - opened - synchronize - reopened - labeled - unlabeled env: # # Dependency versioning # # This is the MSRV used by all repository infrastructure. REPO_MSRV: "1.93" # # Environment variables # CARGO_INCREMENTAL: false CARGO_TERM_COLOR: always RUST_LOG: info RUST_BACKTRACE: "1" CACHE_SUFFIX: c # cache busting jobs: changelog: timeout-minutes: 2 name: Check changelog for errors runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v6 with: fetch-depth: 0 # NOTE: Keep label name(s) in sync. with `xtask`'s implementation. - name: Run `cargo xtask changelog …` run: | cargo xtask changelog "origin/${{ github.event.pull_request.base.ref }}" ${{ contains(github.event.pull_request.labels.*.name, 'changelog: released entry changed') && '--allow-released-changes' || '' }} ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches-ignore: [ # Renovate branches are always PRs, so they will be covered # by the pull_request event. "renovate/**", # Branches with the `gh-readonly-queue` prefix are used by the # merge queue, so they are already covered by the `merge_group` event. "gh-readonly-queue/**", ] pull_request: merge_group: env: # # Dependency versioning # # This is the MSRV used by all repository infrastructure. REPO_MSRV: "1.93" # This is the MSRV used by `wgpu` itself. WGPU_MSRV: "1.87" # This is the MSRV used by the `wgpu-core`, `wgpu-hal`, and `wgpu-types` crates, # to ensure that they can be used with firefox. CORE_MSRV: "1.87" # # Environment variables # CARGO_INCREMENTAL: false CARGO_TERM_COLOR: always WGPU_DX12_COMPILER: dxc RUST_LOG: debug,wasm_bindgen_wasm_interpreter=warn,wasm_bindgen_cli_support=warn,walrus=warn,naga=info RUST_BACKTRACE: full PKG_CONFIG_ALLOW_CROSS: 1 # allow android to work RUSTFLAGS: -D warnings RUSTDOCFLAGS: -D warnings WASM_BINDGEN_TEST_TIMEOUT: 300 # 5 minutes CACHE_SUFFIX: e # cache busting WGPU_CI: true # Every time a PR is pushed to, cancel any previous jobs. This # makes us behave nicer to github and get faster turnaround times # on PRs that are pushed to multiple times in rapid succession. concurrency: group: ${{github.workflow}}-${{github.ref}} cancel-in-progress: ${{github.event_name == 'pull_request'}} # We distinguish the following kinds of builds: # - native: build for the same target as we compile on # - web: build for the Web # - em: build for the Emscripten # For build time and size optimization we disable debug symbols # entirely on clippy jobs and reduce it to line-numbers # only for ones where we run tests. # # Additionally, we disable incremental builds entirely # as our caching system doesn't actually cache our crates. # It adds overhead to the build and another point of failure. jobs: check: # runtime is normally 2-8 minutes # # currently high due to documentation time problems on mac. # https://github.com/rust-lang/rust/issues/114891 timeout-minutes: 30 strategy: fail-fast: false matrix: include: # Windows - name: Windows x86_64 os: windows-2022 target: x86_64-pc-windows-msvc tier: 1 kind: native # Windows - name: Windows aarch64 os: windows-2022 target: aarch64-pc-windows-msvc tier: 2 kind: native # MacOS - name: MacOS x86_64 os: macos-14 target: x86_64-apple-darwin tier: 1 kind: native - name: MacOS aarch64 os: macos-14 target: aarch64-apple-darwin tier: 1 kind: native # IOS - name: IOS aarch64 os: macos-14 target: aarch64-apple-ios tier: 2 kind: native # VisionOS - name: VisionOS aarch64 os: macos-14 target: aarch64-apple-visionos kind: wgpu-only tier: 3 extra-flags: -Zbuild-std # Linux - name: Linux x86_64 os: ubuntu-24.04 target: x86_64-unknown-linux-gnu tier: 1 kind: native - name: Linux aarch64 os: ubuntu-24.04 target: aarch64-unknown-linux-gnu tier: 1 kind: native # FreeBSD - name: FreeBSD x86_64 os: ubuntu-24.04 target: x86_64-unknown-freebsd tier: 2 kind: wgpu-only # NetBSD - name: NetBSD x86_64 os: ubuntu-24.04 target: x86_64-unknown-netbsd tier: 2 kind: wgpu-only # Android - name: Android aarch64 os: ubuntu-24.04 target: aarch64-linux-android tier: 2 kind: native # Android - name: Android ARMv7 os: ubuntu-24.04 target: armv7-linux-androideabi tier: 2 kind: wgpu-only # Android - name: Android x86_64 os: ubuntu-24.04 target: x86_64-linux-android tier: 2 kind: wgpu-only # OpenHarmony - name: OpenHarmony aarch64 os: ubuntu-24.04 target: aarch64-unknown-linux-ohos tier: 2 kind: native # WebGPU/WebGL - name: WebAssembly os: ubuntu-24.04 target: wasm32-unknown-unknown tier: 2 kind: web - name: Emscripten os: ubuntu-24.04 target: wasm32-unknown-emscripten tier: 2 kind: wgpu-only - name: WebAssembly Core 1.0 os: ubuntu-24.04 target: wasm32v1-none tier: 2 kind: no_std # 32-bit PowerPC Linux # Included to test support for `portable-atomic` - name: Linux ppc32 os: ubuntu-24.04 target: powerpc-unknown-linux-gnu tier: 2 kind: wgpu-only name: Clippy ${{ matrix.name }} runs-on: ${{ matrix.os }} steps: - name: Checkout repo uses: actions/checkout@v6 - name: Install toolchain (repo MSRV - tier 1 or 2) if: matrix.tier == 1 || matrix.tier == 2 run: | rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component clippy rustup target add ${{ matrix.target }} --toolchain ${{ env.REPO_MSRV }} rustup override set ${{ env.REPO_MSRV }} cargo -V # In order to build on platforms that require a nightly toolchain, we install stable as expected, # add the rust-src component, then tell stable to consider itself nightly by setting RUSTC_BOOTSTRAP=1. # # This is not formally "correct" thing to do, but it saves significant maintainer burden. If we were to # use a proper nightly toolchain we would have to manually find a date that works. Even with a date that is # carefully coordinated with the version of stable we are using, there are often mismatches of clippy lints # between nightly and stable. This is a real mess. By using RUSTC_BOOTSTRAP=1, we get access to all the nice # nightly features without needing to go through the hassle of maintaining a nightly toolchain. # # RUSTC_BOOTSTRAP=1 is how the rust project builds itself when bootstrapping the compiler, so while not "stable" # it has been around for many years and don't anticipate it going away any time soon. - name: Install toolchain (repo MSRV - tier 3) if: matrix.tier == 3 run: | rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component clippy,rust-src echo "RUSTC_BOOTSTRAP=1" >> "$GITHUB_ENV" - name: Disable debug symbols shell: bash run: | mkdir -p .cargo cat <> .cargo/config.toml [profile.dev] debug = false EOF - name: Caching uses: Swatinem/rust-cache@v2 with: key: clippy-${{ matrix.target }}-${{ matrix.kind }}-${{ env.CACHE_SUFFIX }} - name: (Linux `aarch64`) Install `aarch64-linux-gnu` `g++` if: matrix.target == 'aarch64-unknown-linux-gnu' run: | set -e sudo apt-get update -y -qq sudo apt-get install g++-aarch64-linux-gnu - name: (Android) Add Android APK to `PATH` if: matrix.target == 'aarch64-linux-android' run: | # clang++ will be detected correctly by CC from path echo "$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin" >> $GITHUB_PATH # the android sdk doesn't use the conventional name for ar, so explicitly set it. echo "AR_aarch64_linux_android=llvm-ar" >> "$GITHUB_ENV" # Building for wasm32 requires a series of specific tests for the WebGPU backend. - name: Check web if: matrix.kind == 'web' shell: bash run: | set -e # build for WebGPU cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --features glsl,spirv,fragile-send-sync-non-atomic-wasm cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --features glsl,spirv cargo --locked doc --target ${{ matrix.target }} ${{ matrix.extra-flags }} --no-deps --features glsl,spirv # check with only the web feature cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --no-default-features --features=web # all features cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --all-features cargo --locked doc --target ${{ matrix.target }} ${{ matrix.extra-flags }} --no-deps --all-features # Building for platforms where the tests do not compile. - name: Check `wgpu` only if: matrix.kind == 'wgpu-only' shell: bash run: | set -e # check with no features cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu -p wgpu-hal --no-default-features # Don't check samples since we use winit in our samples which has dropped support for Emscripten. # Check with all features. cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-hal --all-features cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu --all-features # Building for no_std platforms. - name: Check `no_std` if: matrix.kind == 'no_std' shell: bash run: | set -e # check with no features cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-types --no-default-features cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p naga --no-default-features cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-hal --no-default-features cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu --no-default-features # Check with all compatible features cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-types --no-default-features --features strict_asserts,fragile-send-sync-non-atomic-wasm,serde,counters cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p naga --no-default-features --features dot-out,spv-in,spv-out cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-hal --no-default-features --features fragile-send-sync-non-atomic-wasm cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu --no-default-features --features serde # Building for native platforms with standard tests. - name: Check native if: matrix.kind == 'native' shell: bash run: | set -e # check with no features cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --no-default-features # Check with all features. cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --benches --all-features # Check with all features and profiling macro code. # If we don't check this then errors inside `profiling::scope!()` will not be caught. cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --benches --all-features --features test-build-with-profiling # build docs cargo --locked doc --target ${{ matrix.target }} ${{ matrix.extra-flags }} --all-features --no-deps - name: Check private item docs if: matrix.kind == 'native' shell: bash run: | set -e cargo --locked doc --target ${{ matrix.target }} ${{ matrix.extra-flags }} \ --package wgpu-core \ --package wgpu-hal \ --package naga \ --package wgpu \ --all-features --no-deps --document-private-items # We run minimal checks on the MSRV of the `wgpu` crate, ensuring that # its dependency tree does not cause issues for servo or other users. # # We don't test all platforms, just ones with different dependency stacks. check-wgpu-msrv: # runtime is normally 1-3 minutes timeout-minutes: 10 strategy: fail-fast: false matrix: include: # Windows - name: Windows x86_64 os: windows-2022 target: x86_64-pc-windows-msvc # MacOS - name: MacOS x86_64 os: macos-14 target: x86_64-apple-darwin # Linux - name: Linux x86_64 os: ubuntu-24.04 target: x86_64-unknown-linux-gnu # WebGPU - name: WebAssembly os: ubuntu-24.04 target: wasm32-unknown-unknown name: wgpu MSRV Check ${{ matrix.name }} runs-on: ${{ matrix.os }} steps: - name: Checkout repo uses: actions/checkout@v6 - name: Install core MSRV toolchain run: | rustup toolchain install ${{ env.WGPU_MSRV }} --no-self-update --profile=minimal --component clippy --target ${{ matrix.target }} rustup override set ${{ env.WGPU_MSRV }} cargo -V - name: Disable debug symbols shell: bash run: | mkdir -p .cargo cat <> .cargo/config.toml [profile.dev] debug = false EOF - name: Caching uses: Swatinem/rust-cache@v2 with: key: wgpu-msrv-check-${{ matrix.target }}-${{ env.CACHE_SUFFIX }} - name: Reduce MSRV on dependencies shell: bash run: | set -e # No needed operations currently. # If we find a dependency that needs to be downgraded to achieve # the MSRV, we can do it here with `cargo update -p --precise ` - name: Check native shell: bash run: | set -e # check `wgpu` with all features. cargo check --target ${{ matrix.target }} --all-features -p wgpu # We run minimal checks on the MSRV of the core crates, ensuring that # its dependency tree does not cause issues for firefox. # # We don't test all platforms, just ones with different dependency stacks. check-core-msrv: # runtime is normally 1-3 minutes timeout-minutes: 10 strategy: fail-fast: false matrix: include: # Windows - name: Windows x86_64 os: windows-2022 target: x86_64-pc-windows-msvc # MacOS - name: MacOS x86_64 os: macos-14 target: x86_64-apple-darwin # Linux - name: Linux x86_64 os: ubuntu-24.04 target: x86_64-unknown-linux-gnu name: Core MSRV Check ${{ matrix.name }} runs-on: ${{ matrix.os }} steps: - name: Checkout repo uses: actions/checkout@v6 - name: Install core MSRV toolchain run: | rustup toolchain install ${{ env.CORE_MSRV }} --no-self-update --profile=minimal --component clippy --target ${{ matrix.target }} rustup override set ${{ env.CORE_MSRV }} cargo -V - name: Disable debug symbols shell: bash run: | mkdir -p .cargo cat <> .cargo/config.toml [profile.dev] debug = false EOF - name: Caching uses: Swatinem/rust-cache@v2 with: key: core-msrv-check-${{ matrix.target }}-${{ env.CACHE_SUFFIX }} - name: Reduce MSRV on dependencies shell: bash run: | set -e # No needed operations currently. # If we find a dependency that needs to be downgraded to achieve # the MSRV, we can do it here with `cargo update -p --precise ` - name: Check native shell: bash run: | set -e # check `wgpu-core` with all features. This will also get `wgpu-hal` and `wgpu-types`. cargo check --target ${{ matrix.target }} --all-features -p wgpu-core # Check that the libraries build — but not that there are no warnings or that tests pass - # with `-Zdirect-minimal-versions` which lowers all dependencies from the workspace packages # to non-workspace packages to their minimum allowed version. check-minimal-versions: # runtime is normally 2 minutes timeout-minutes: 10 name: MSRV Minimal Versions runs-on: ubuntu-24.04 env: # Override flags to NOT include `-D warnings`, because warnings may be due to harmless problems in deps. # Also, allow unexpected_cfgs because it is very common and spammy when using old deps. RUSTFLAGS: -A unexpected_cfgs steps: - name: Checkout repo uses: actions/checkout@v6 - name: Install toolchain run: | rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal rustup override set ${{ env.REPO_MSRV }} cargo -V - name: Disable debug symbols shell: bash run: | mkdir -p .cargo echo """ [profile.dev] debug = false" >> .cargo/config.toml - name: Set minimal versions shell: bash run: | set -e cargo +${{ env.REPO_MSRV }} update -Zdirect-minimal-versions env: RUSTC_BOOTSTRAP: 1 - name: Run cargo check shell: bash run: | set -e cargo check --all-targets --all-features wasm-test: # runtime is normally 2 minutes timeout-minutes: 10 name: Test WebAssembly runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v6 - name: Install repo MSRV toolchain run: | rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component clippy --target wasm32-unknown-unknown rustup override set ${{ env.REPO_MSRV }} cargo -V - name: Install `wasm-pack` uses: taiki-e/install-action@v2 with: tool: wasm-pack - name: Execute tests run: | cd wgpu wasm-pack test --headless --chrome --no-default-features --features wgsl,webgl,web --workspace gpu-test: # runtime is normally 5-15 minutes timeout-minutes: 30 strategy: fail-fast: false matrix: include: # Windows - name: Windows x86_64 os: windows-2022 # Mac - name: Mac aarch64 os: macos-14 # Linux - name: Linux x86_64 os: ubuntu-24.04 name: Test ${{ matrix.name }} runs-on: ${{ matrix.os }} steps: - name: Checkout repo uses: actions/checkout@v6 - name: Install repo MSRV toolchain run: | rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal -c llvm-tools cargo -V - name: Install `cargo-nextest` and `cargo-llvm-cov` uses: taiki-e/install-action@v2 with: tool: cargo-nextest,cargo-llvm-cov - name: Debug symbols to line-tables-only shell: bash run: | mkdir -p .cargo cat <> .cargo/config.toml [profile.dev] debug = "line-tables-only" EOF # Cache step must go before warp and mesa install on windows as they write into the # target directory, and rust-cache will overwrite the entirety of the target directory. - name: Caching uses: Swatinem/rust-cache@v2 with: key: test-${{ matrix.os }}-${{ env.CACHE_SUFFIX }} workspaces: | . -> target - name: (Windows) Install DXC if: matrix.os == 'windows-2022' uses: ./.github/actions/install-dxc - name: (Windows) Install WARP if: matrix.os == 'windows-2022' uses: ./.github/actions/install-warp with: target-dir: "target/llvm-cov-target/debug" - name: (Windows) Install Agility SDK if: matrix.os == 'windows-2022' uses: ./.github/actions/install-agility-sdk - name: (Windows) Install Mesa if: matrix.os == 'windows-2022' uses: ./.github/actions/install-mesa - name: (Windows) Install Vulkan SDK if: matrix.os == 'windows-2022' uses: ./.github/actions/install-vulkan-sdk - name: (Mac) Install Vulkan SDK if: matrix.os == 'macos-14' uses: ./.github/actions/install-vulkan-sdk - name: (Linux) Install Vulkan SDK if: matrix.os == 'ubuntu-24.04' uses: ./.github/actions/install-vulkan-sdk - name: (Linux) Install Mesa if: matrix.os == 'ubuntu-24.04' uses: ./.github/actions/install-mesa - name: Delete Naga snapshots shell: bash run: | set -e # Delete snapshots so we can ensure there aren't any excess output files. rm -r naga/tests/out - name: Run tests shell: bash run: | set -e cargo --locked xtask test --llvm-cov - name: Check Naga snapshots # git diff doesn't check untracked files, we need to stage those then compare with HEAD. run: git add . && git diff --exit-code HEAD naga/tests/out - uses: actions/upload-artifact@v7 name: Upload comparison images if: always() # We want artifacts even if the tests fail. with: name: comparison-images-${{ matrix.os }} path: | **/*-actual.png **/*-difference.png # Print GPU configuration so we can see what features were available during the test. - name: Print GPU configurations if: always() # We want this information even if the tests fail. shell: bash run: | set -e cat .gpuconfig - name: Generate coverage report id: coverage shell: bash continue-on-error: true run: | set -e cargo --locked llvm-cov report --lcov --output-path lcov.info - name: Upload coverage report to Codecov uses: codecov/codecov-action@v5 if: steps.coverage.outcome == 'success' with: files: lcov.info token: ${{ secrets.CODECOV_TOKEN }} doctest: # runtime is normally 2 minutes timeout-minutes: 10 name: Doctest runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v6 - name: Install repo MSRV toolchain run: | rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component rustfmt rustup override set ${{ env.REPO_MSRV }} cargo -V - name: Caching uses: Swatinem/rust-cache@v2 with: key: doctests-${{ env.CACHE_SUFFIX }} - name: Run doctests shell: bash run: | set -e cargo --locked test --doc miri: # runtime is normally 2 minutes timeout-minutes: 10 name: Miri (limited scope) runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v6 # Miri is *only* available on nightly. - name: Install nightly toolchain run: | rustup toolchain install nightly --no-self-update --profile=minimal --component miri rustup override set nightly cargo -V - name: Caching uses: Swatinem/rust-cache@v2 with: key: miri-${{ env.CACHE_SUFFIX }} - name: Run Miri on selected tests shell: bash run: | set -e # Note: this is a *smaller* set of tests than `cargo xtask miri` runs; # it is ones that are both important and cheap. # Consider expanding it to include some API tests with the noop backend. cargo miri test --package=wgpu --no-default-features -- write_only fmt: # runtime is normally 15 seconds timeout-minutes: 2 name: Format & Typos runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v6 - name: Install repo MSRV toolchain run: | rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component rustfmt rustup override set ${{ env.REPO_MSRV }} cargo -V - name: Run `cargo fmt` run: | cargo --locked fmt -- --check - name: Install Taplo uses: uncenter/setup-taplo@v1 with: version: "0.9.3" - name: Run `taplo fmt` run: taplo format --check --diff - name: Check for typos uses: crate-ci/typos@v1.44.0 check-cts-runner: # runtime is normally 2 minutes timeout-minutes: 10 name: Clippy cts_runner runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v6 - name: Install MSRV toolchain run: | rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component clippy rustup override set ${{ env.REPO_MSRV }} cargo -V - name: Disable debug symbols shell: bash run: | mkdir -p .cargo cat <> .cargo/config.toml [profile.dev] debug = false EOF - name: Caching uses: Swatinem/rust-cache@v2 with: key: cts-runner-${{ env.CACHE_SUFFIX }} - name: Build Deno run: | cargo --locked clippy --manifest-path cts_runner/Cargo.toml # Separate job so that new advisories don't block CI. # # This job is not required to pass for PRs to be merged. cargo-deny-check-advisories: # runtime is normally 1 minute timeout-minutes: 5 name: "cargo-deny advisories" runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v6 - name: Run `cargo deny check` uses: EmbarkStudios/cargo-deny-action@v2 with: command: check advisories arguments: --all-features --workspace command-arguments: -Dwarnings -Aunmatched-organization rust-version: ${{ env.REPO_MSRV }} cargo-deny-check-rest: # runtime is normally 1 minute timeout-minutes: 5 name: "cargo-deny" runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v6 - name: Run `cargo deny check` uses: EmbarkStudios/cargo-deny-action@v2 with: command: check bans licenses sources arguments: --all-features --workspace command-arguments: -Dwarnings -Aunmatched-organization rust-version: ${{ env.REPO_MSRV }} ================================================ FILE: .github/workflows/cts.yml ================================================ name: CTS on: push: branches-ignore: [ # Renovate branches are always PRs, so they will be covered # by the pull_request event. "renovate/**", # Branches with the `gh-readonly-queue` prefix are used by the # merge queue, so they are already covered by the `merge_group` event. "gh-readonly-queue/**", ] pull_request: merge_group: env: REPO_MSRV: "1.93" CARGO_INCREMENTAL: false CARGO_TERM_COLOR: always DENO_WEBGPU_DX12_COMPILER: dynamicdxc RUST_BACKTRACE: full RUSTFLAGS: -D warnings RUSTDOCFLAGS: -D warnings # Every time a PR is pushed to, cancel any previous jobs. This # makes us behave nicer to github and get faster turnaround times # on PRs that are pushed to multiple times in rapid succession. concurrency: group: ${{github.workflow}}-${{github.ref}} cancel-in-progress: ${{github.event_name == 'pull_request'}} jobs: cts: strategy: fail-fast: false matrix: suite: ["API Operation", "API Validation", "Other"] platform: ["Windows x86_64", "Mac aarch64", "Linux x86_64"] include: # When an `include` item matches existing matrix entries, the additional items are # added only to the matching entries. The two lines above define the matrix, and # the items below attach the rest of the configuration values that apply to suites # and platforms. - suite: API Operation filter: "^webgpu:api,operation," - suite: API Validation filter: "^webgpu:api,validation," - suite: Other filter: "!^webgpu:api,operation,|^webgpu:api,validation," # Windows - platform: Windows x86_64 os: windows-2022 target: x86_64-pc-windows-msvc backend: dx12 shell: bash # Mac # Need to use `zsh` for `source <(cmd)` because it doesn't work in # ancient MacOS bash. `zsh` is a non-standard shell in GitHub # actions, so must be specified as a command string. - platform: Mac aarch64 os: macos-14 target: x86_64-apple-darwin backend: metal shell: zsh {0} # Linux - platform: Linux x86_64 os: ubuntu-24.04 target: x86_64-unknown-linux-gnu backend: vulkan shell: bash name: ${{ matrix.suite }} ${{ matrix.platform }} runs-on: ${{ matrix.os }} defaults: run: # Substitution of ${{ matrix.shell }} appears not to work in per-step configuration shell: ${{ matrix.shell }} steps: - name: checkout repo uses: actions/checkout@v6 - name: Install Repo MSRV toolchain run: | rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --target ${{ matrix.target }} --component llvm-tools rustup override set ${{ env.REPO_MSRV }} cargo -V - name: Install `cargo-llvm-cov` uses: taiki-e/install-action@v2 with: tool: cargo-llvm-cov - name: caching uses: Swatinem/rust-cache@v2 with: # The version number can be incremented for cache busting. prefix-key: v2-rust-${{ hashFiles('cts_runner/revision.txt') }} cache-directories: cts # We enable line numbers for panics, but that's it - name: disable debug shell: bash run: | mkdir -p .cargo cat <> .cargo/config.toml [profile.dev] debug = "line-tables-only" EOF - name: (Windows) Install DXC if: matrix.os == 'windows-2022' uses: ./.github/actions/install-dxc # Note: `target-dir` is intentionally different from other jobs. # See note in xtask about `llvm-cov show-env`. - name: (Windows) Install WARP if: matrix.os == 'windows-2022' uses: ./.github/actions/install-warp with: target-dir: "target/debug" - name: (Linux) Install Mesa if: matrix.os == 'ubuntu-24.04' uses: ./.github/actions/install-mesa with: target-dir: "target/debug" # Some of these tests check stdout, so we explicitly set the backend # to avoid getting EGL messages in the output. - name: Test cts_runner if: matrix.suite == 'other' shell: bash run: | export LLVM_PROFILE_FILE=${{ github.workspace }}/target/wgpu-%p-%m.profraw export DENO_WEBGPU_BACKEND=${{ matrix.backend }} cargo --locked llvm-cov --no-cfg-coverage --no-report test -p cts_runner - name: Run CTS shell: bash run: cargo --locked xtask cts --llvm-cov --backend ${{ matrix.backend }} --filter '${{matrix.filter }}' - name: Generate coverage report id: coverage continue-on-error: true run: | set -e source <(cargo llvm-cov --no-cfg-coverage show-env --sh) cargo --locked llvm-cov report --lcov --output-path lcov.info - name: Upload coverage report to Codecov uses: codecov/codecov-action@v5 if: steps.coverage.outcome == 'success' with: files: lcov.info token: ${{ secrets.CODECOV_TOKEN }} ================================================ FILE: .github/workflows/docs.yml ================================================ name: Docs on: push: branches-ignore: [ # Renovate branches are always PRs, so they will be covered # by the pull_request event. "renovate/**", # Branches with the `gh-readonly-queue` prefix are used by the # merge queue, so they are already covered by the `merge_group` event. "gh-readonly-queue/**", ] pull_request: merge_group: env: # This is the MSRV used by all repository infrastructure. REPO_MSRV: "1.93" CARGO_INCREMENTAL: false CARGO_TERM_COLOR: always RUST_BACKTRACE: full # Every time a PR is pushed to, cancel any previous jobs. This # makes us behave nicer to github and get faster turnaround times # on PRs that are pushed to multiple times in rapid succession. concurrency: group: ${{github.workflow}}-${{github.ref}} cancel-in-progress: ${{github.event_name == 'pull_request'}} jobs: build: runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v6 with: persist-credentials: false - name: Install documentation toolchain run: | rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal rustup override set ${{ env.REPO_MSRV }} - name: Disable debug symbols shell: bash run: | mkdir -p .cargo cat <> .cargo/config.toml [profile.dev] debug = false EOF - name: Caching uses: Swatinem/rust-cache@v2 with: key: doc-build - name: Build the docs run: | cargo doc --no-deps --lib --document-private-items env: RUSTDOCFLAGS: --cfg docsrs RUSTC_BOOTSTRAP: 1 - name: Deploy the docs uses: JamesIves/github-pages-deploy-action@v4.8.0 if: github.ref == 'refs/heads/trunk' with: token: ${{ secrets.WEB_DEPLOY }} folder: target/doc repository-name: gfx-rs/wgpu-rs.github.io branch: master target-folder: doc ================================================ FILE: .github/workflows/generate.yml ================================================ name: cargo-generate on: push: branches-ignore: [ # Renovate branches are always PRs, so they will be covered # by the pull_request event. "renovate/**", # Branches with the `gh-readonly-queue` prefix are used by the # merge queue, so they are already covered by the `merge_group` event. "gh-readonly-queue/**", ] pull_request: merge_group: env: # # Dependency versioning # # This is the MSRV used by `wgpu` itself. WGPU_MSRV: "1.87" RUSTFLAGS: -D warnings # Every time a PR is pushed to, cancel any previous jobs. This # makes us behave nicer to github and get faster turnaround times # on PRs that are pushed to multiple times in rapid succession. concurrency: group: ${{github.workflow}}-${{github.ref}} cancel-in-progress: ${{github.event_name == 'pull_request'}} jobs: cargo-generate: timeout-minutes: 5 runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - name: "01-hello-compute" path: "examples/standalone/01_hello_compute" - name: "02-hello-window" path: "examples/standalone/02_hello_window" - name: "custom_backend" path: "examples/standalone/custom_backend" name: "${{ matrix.name }}" steps: - uses: actions/checkout@v6 # We can't rely on an override here, as that would only set # the toolchain for the current directory, not the newly generated project. - name: Install repo MSRV toolchain run: | rustup toolchain install ${{ env.WGPU_MSRV }} --no-self-update --profile=minimal cargo -V - name: Disable debug symbols shell: bash run: | mkdir -p .cargo cat <> .cargo/config.toml [profile.dev] debug = false EOF - name: Caching uses: Swatinem/rust-cache@v2 with: key: cargo-generate-${{ matrix.name }} - name: Install `cargo-generate` uses: taiki-e/install-action@v2 with: tool: cargo-generate - name: Run `cargo-generate` run: | cd .. cargo generate --path wgpu --name ${{ matrix.name }} ${{ matrix.path }} - name: Check generated files run: | cd ../${{ matrix.name }}/ cat <> Cargo.toml [patch.crates-io] wgpu = { path = "../wgpu/wgpu" } EOF cargo +${{ env.WGPU_MSRV }} check ================================================ FILE: .github/workflows/lazy.yml ================================================ # Non-critical jobs name: Lazy on: push: branches-ignore: [ # Renovate branches are always PRs, so they will be covered # by the pull_request event. "renovate/**", # Branches with the `gh-readonly-queue` prefix are used by the # merge queue, so they are already covered by the `merge_group` event. "gh-readonly-queue/**", ] pull_request: merge_group: env: CARGO_INCREMENTAL: false CARGO_TERM_COLOR: always RUST_BACKTRACE: full # Every time a PR is pushed to, cancel any previous jobs. This # makes us behave nicer to github and get faster turnaround times # on PRs that are pushed to multiple times in rapid succession. concurrency: group: ${{github.workflow}}-${{github.ref}} cancel-in-progress: ${{github.event_name == 'pull_request'}} jobs: parse-dota2: name: "Validate Shaders: Dota2" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - run: mkdir naga/data - name: Download shaders run: curl https://user.fm/files/v2-5573e18b9f03f42c6ae53c392083da35/dota2-shaders.zip -o naga/data/all.zip - name: Unpack shaders run: | cd naga/data unzip all.zip - name: Build Naga run: | cd naga cargo build --release -p naga-cli - name: Convert shaders run: | cd naga for file in data/*.spv ; do echo "Translating" ${file} && ../target/release/naga --validate 27 ${file} ${file}.metal; done parse-vulkan-tutorial-shaders: name: "Validate Shaders: Sascha Willems Vulkan Tutorial" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Download shaders run: cd naga && git clone https://github.com/SaschaWillems/Vulkan.git - name: Build Naga run: | cd naga cargo build --release -p naga-cli - name: Convert Metal shaders run: | # No needed to stop workflow if we can't validate one file set +e cd naga touch counter SUCCESS_RESULT_COUNT=0 FILE_COUNT=0 mkdir -p out find "Vulkan/data/shaders/glsl/" -name '*.spv' | while read fname; do echo "Convert: $fname" FILE_COUNT=$((FILE_COUNT+1)) ../target/release/naga --validate 27 $(realpath ${fname}) out/$(basename ${fname}).metal if [[ $? -eq 0 ]]; then SUCCESS_RESULT_COUNT=$((SUCCESS_RESULT_COUNT + 1)) fi echo "Result: $(expr $FILE_COUNT - $SUCCESS_RESULT_COUNT) / $FILE_COUNT" > counter done cat counter dneto0_spirv-samples: name: "Validate Shaders: dneto0 spirv-samples" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Download shaders run: | cd naga git clone https://github.com/dneto0/spirv-samples.git - name: Build Naga run: | cargo build --release -p naga-cli - name: Install Vulkan SDK uses: ./.github/actions/install-vulkan-sdk - name: Compile `spv` from `spvasm` run: | cd naga/spirv-samples mkdir -p spv find "./spvasm" -name '*.spvasm' | while read fname; do echo "Convert to spv with spirv-as: $fname" spirv-as --target-env spv1.3 $(realpath ${fname}) -o ./spv/$(basename ${fname}).spv done; - name: Validate `spv` and generate `wgsl` run: | set +e cd naga/spirv-samples SUCCESS_RESULT_COUNT=0 FILE_COUNT=0 mkdir -p spv mkdir -p wgsl echo "==== Validate spv and generate wgsl ====" rm -f counter touch counter find "./spv" -name '*.spv' | while read fname; do echo "Convert: $fname" FILE_COUNT=$((FILE_COUNT+1)) ../../target/release/naga --validate 27 $(realpath ${fname}) ./wgsl/$(basename ${fname}).wgsl if [[ $? -eq 0 ]]; then SUCCESS_RESULT_COUNT=$((SUCCESS_RESULT_COUNT + 1)) fi echo "Result: $(expr $FILE_COUNT - $SUCCESS_RESULT_COUNT) / $FILE_COUNT" > counter done cat counter - name: Validate output `wgsl` run: | set +e cd naga/spirv-samples SUCCESS_RESULT_COUNT=0 FILE_COUNT=0 rm -f counter touch counter find "./wgsl" -name '*.wgsl' | while read fname; do echo "Validate: $fname" FILE_COUNT=$((FILE_COUNT+1)) ../../target/release/naga --validate 27 $(realpath ${fname}) if [[ $? -eq 0 ]]; then SUCCESS_RESULT_COUNT=$((SUCCESS_RESULT_COUNT + 1)) fi echo "Result: $(expr $FILE_COUNT - $SUCCESS_RESULT_COUNT) / $FILE_COUNT" > counter done cat counter ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish on: push: branches-ignore: [ # Renovate branches are always PRs, so they will be covered # by the pull_request event. "renovate/**", # Branches with the `gh-readonly-queue` prefix are used by the # merge queue, so they are already covered by the `merge_group` event. "gh-readonly-queue/**", ] pull_request: merge_group: env: CARGO_INCREMENTAL: false CARGO_TERM_COLOR: always RUST_BACKTRACE: full # Every time a PR is pushed to, cancel any previous jobs. This # makes us behave nicer to github and get faster turnaround times # on PRs that are pushed to multiple times in rapid succession. concurrency: group: ${{github.workflow}}-${{github.ref}} cancel-in-progress: ${{github.event_name == 'pull_request'}} jobs: publish: runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v6 with: persist-credentials: false - name: Install Rust WASM target run: rustup target add wasm32-unknown-unknown - name: Get `wasm-bindgen` version run: | WASM_BINDGEN_VERSION=$(cargo metadata --format-version 1 --all-features | jq '.packages[] | select(.name == "wasm-bindgen") | .version' | tr -d '"') echo $WASM_BINDGEN_VERSION echo "WASM_BINDGEN_VERSION=$WASM_BINDGEN_VERSION" >> "$GITHUB_ENV" - name: Install `wasm-bindgen` run: cargo +stable install wasm-bindgen-cli --version=$WASM_BINDGEN_VERSION - name: Debug symbols to line-tables-only shell: bash run: | mkdir -p .cargo cat <> .cargo/config.toml [profile.dev] debug = "line-tables-only" EOF - name: Caching uses: Swatinem/rust-cache@v2 with: key: publish-build - name: Build examples run: cargo xtask run-wasm --no-serve - name: Deploy WebGPU examples uses: JamesIves/github-pages-deploy-action@v4.8.0 if: github.ref == 'refs/heads/trunk' with: token: ${{ secrets.WEB_DEPLOY }} folder: target/generated repository-name: gfx-rs/wgpu-rs.github.io branch: master target-folder: examples/ ================================================ FILE: .github/workflows/shaders.yml ================================================ name: Shaders on: push: branches-ignore: [ # Renovate branches are always PRs, so they will be covered # by the pull_request event. "renovate/**", # Branches with the `gh-readonly-queue` prefix are used by the # merge queue, so they are already covered by the `merge_group` event. "gh-readonly-queue/**", ] pull_request: merge_group: # Every time a PR is pushed to, cancel any previous jobs. This # makes us behave nicer to github and get faster turnaround times # on PRs that are pushed to multiple times in rapid succession. concurrency: group: ${{github.workflow}}-${{github.ref}} cancel-in-progress: ${{github.event_name == 'pull_request'}} jobs: naga-validate-windows: name: "Validate: HLSL" runs-on: windows-latest steps: - uses: actions/checkout@v6 - name: Debug symbols to `line-tables-only` shell: bash run: | mkdir -p .cargo cat <> .cargo/config.toml [profile.dev] debug = "line-tables-only" EOF - uses: Swatinem/rust-cache@v2 # We must have the FXC job before the DXC job, so the DXC PATH has priority # over the FXC PATH. This is because the windows kits also include an older # version of DXC, which we don't want to use. - name: Setup FXC run: | Get-Childitem -Path "C:\Program Files (x86)\Windows Kits\10\bin\**\x64\fxc.exe" ` | Sort-Object -Property LastWriteTime -Descending ` | Select-Object -First 1 ` | Split-Path -Parent ` | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf8 -Append shell: powershell - name: Setup DXC uses: ./.github/actions/install-dxc - name: Validate shell: bash run: | set -e dxc --version cd naga cargo xtask validate hlsl dxc cargo xtask validate hlsl fxc naga-validate-macos: name: "Validate: MSL" runs-on: macos-15 steps: - uses: actions/checkout@v6 - name: Debug symbols to line-tables-only shell: bash run: | mkdir -p .cargo cat <> .cargo/config.toml [profile.dev] debug = "line-tables-only" EOF - uses: Swatinem/rust-cache@v2 - run: | cd naga cargo xtask validate msl naga-validate-linux: name: "Validate: SPIR-V/GLSL/DOT/WGSL" runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 - name: Install Vulkan SDK uses: ./.github/actions/install-vulkan-sdk - name: Install Graphviz run: sudo apt-get install graphviz - name: Debug symbols to `line-tables-only` shell: bash run: | mkdir -p .cargo cat <> .cargo/config.toml [profile.dev] debug = "line-tables-only" EOF - uses: Swatinem/rust-cache@v2 - run: cd naga; cargo xtask validate spv - run: cd naga; cargo xtask validate glsl - run: cd naga; cargo xtask validate dot - run: cd naga; cargo xtask validate wgsl ================================================ FILE: .gitignore ================================================ # Jujutsu # # When using the wgpu repository through [Jujutsu](https://github.com/martinvonz/jj), `.jj/` dirs. # are ignored because Jujutsu writes a `.jj/.gitignore` file containing `/*`. Some tools, like # `prettier`, don't handle nested `.gitgnore` properly, but they _do_ handle top-level `.gitignore` # patterns properly. So, include it here, even though we shouldn't need it. :cry: .jj/ # Generated by Cargo # will have compiled files and executables /target # Include the root lockfile but not the others */Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk # Other .fuse_hidden* .DS_Store # IDE/Editor configuration files .vscode .idea # Output from capture example wgpu/red.png # Output from render_to_texture example **/please_don't_git_push_me.png # Output from invalid comparison tests **/*-actual.png **/*-difference.png # Readme says to check out CTS here /cts # Readme says to put angle in working directory *.dll # Cached GPU config .gpuconfig # Temporary clone location for wasm-bindgen mirroring wgpu/src/backend/webgpu/webgpu_sys/wasm_bindgen_clone_tmp # LLM-related .claude/settings.local.json ================================================ FILE: .prettierignore ================================================ **/*.frag **/*.vert naga/tests/in/ naga/tests/out/ ================================================ FILE: AGENTS.md ================================================ # Global instructions ## Code Style - Limit the amount of comments you put in the code to a strict minimum. You should almost never add comments, except sometimes on non-trivial code, function definitions if the arguments aren't self-explanatory, and class definitions and their members. - Do not use emoji. ## Workflow - You can test that the code builds by running `cargo build`. Don't pass `--release` unless there's a specific need to test a release build, it's much slower. - After making code changes, ensure the code is formatted by using `cargo fmt`, linted by using `cargo clippy --tests`. If there are no errors, you can use `cargo xtask test` and `cargo xtask cts --backend ` to run tests (to fully validate a change, run both). On MacOS, the backend is `metal`. On Windows, the backend is `dx12`. On Linux, the backend is `vulkan`. - Do not perform commits yourself, ever. - Use the WebGPU and WGSL specifications as a reference to determine the correct behavior. Do not assume that a behavior is correct just because the CTS expects it. ## Changelog We maintain a changelog in CHANGELOG.md. Changes should be noted in the changelog if they are user-visible (changes to documented public APIs, significant bug fixes, or new functionality). Changelog descriptions should be concise. If you are not sure whether something should be in the CHANGELOG or you are not sure how to describe the change, ask the user for guidance. ## More information about tests There is some more detailed information about tests in @docs/testing.md. # CTS ## Listing and Running Individual Tests Each line is a selector that will run multiple subtests. You can run a test with: ```bash cargo xtask cts 'webgpu:selector/path:test:*' ``` For fixing problems, it may be useful to run specific cases. You can get a list of the subtests with: ```bash cargo xtask cts -- --list 'webgpu:selector,path:test:*' ``` Then you can pass individual subtests on the command line as the selector. ## Running Suites To run a suite of CTS tests and keep just the summary at the end: ```bash cargo xtask cts 'webgpu:selector,path:test:*' 2>&1 | grep -E "(Summary|Passed|Failed)" | tail -5 ``` To run a suite of tests and keep just the failing tests: ```bash cargo xtask cts 'webgpu:selector,path:test:*' 2>&1 | grep "\[fail\]" ``` ## Keeping Track of Results We maintain three files listing CTS test selectors that are expected to pass, not pass, or to be entirely skipped. These files are respectively cts_runner/test.lst, cts_runner/fail.lst, and cts_runner/skip.lst. If you fix a CTS test, add the selector to test.lst. Be aware that the file is not an exhaustive list of passing tests. CI expects every test in test.lst to pass, so only add suites that have zero failures to test.lst. Do not add any suites that are not 100% pass/skip, even if they are ≥99% passing. List suites with failures in fail.lst. You can put a comment `// xx%` noting the pass rate, especially in cases where the pass rate is high. List suites that are ≥90% skips in skip.lst When adding to a lst file, use wildcards to identify the tests with as few lines as possible. In some cases when adding tests it may be possible to combine with existing lines to use a higher level wildcard. Only use a higher level wildcard when all the tests it will match go in the same file. Do not use a higher level wildcard if it would match tests that belong in a different file. ## Source for the CTS Tests The TypeScript sources for the CTS are under cts/src. There is also generated JavaScript in cts/out. Do not assume that a behavior is correct just because a CTS test expects it. Verify the correct behavior in the WebGPU or WGSL specification. # Overview of the Code See `docs/big-picture.png` for an overview of the system. The major components are: * `wgpu-hal` implements a backend for each supported graphics API (Vulkan, DX12, Metal, GLES). * `wgpu-core` implements the WebGPU API, including resource management and validation. It calls the platform graphics APIs via `wgpu-hal`, and uses `naga` for shader translation. * `wgpu` is the native Rust API. In addition to providing bindings to `wgpu-core`, the `wgpu` crate can also be compiled to WASM and built against the "WebGPU" backend, where it uses whatever WebGPU implementation is provided by the WASM environment. `wgpu` is not used by Deno and Firefox. * `naga` is the shader translator. It reads shaders in WGSL, GLSL, or SPIR-V, translates to Naga IR, and then writes shaders in GLSL, HLSL, MSL (Metal Shading Language), SPIR-V, or WGSL. It is responsible for validating that WGSL shaders are valid according to the WGSL language specification. * `wgpu-types` contains some type definitions that are applicable both to `wgpu-core` and to the `wgpu` "WebGPU" backend. * `deno_webgpu` contains WebGPU bindings for the Deno Javascript runtime. We also use Deno as a test environment for running the WebGPU CTS. Only make a change in the Deno bindings if you are sure that the issue doesn't apply to other clients (Firefox or `wgpu` Rust API). If it does apply to other clients, the issue should probably be fixed in `wgpu-core`. For a more detailed discussion of the `wgpu` architecture, refer to . ## Naga ### Constant Evaluator (`naga/src/proc/constant_evaluator.rs`) The constant evaluator is responsible for evaluating constant expressions at compile time in WGSL/GLSL shaders. **Key Components:** 1. **Expression Handling** (~line 1228): The `try_eval_and_append_impl` method handles different expression types and evaluates them if possible. 2. **Type Conversions**: - `cast()` (~line 2256): Handles type conversions with `Expression::As` where `convert` is `Some(width)` - `bitcast()` (~line 2449): Handles bit reinterpretation with `Expression::As` where `convert` is `None` - Key insight: For bitcast, the target width equals the source width (bit-preserving operation) 3. **Helper Macros**: - `gen_component_wise_extractor!`: Generates functions for component-wise operations on scalars/vectors - `component_wise_scalar!`, `component_wise_float!`: Convenience macros for applying operations to each component 4. **Borrow Checker Patterns**: - When implementing methods that need to call themselves recursively or make multiple mutable borrows, extract all needed data from `self` fields before the recursive calls - Clone vectors before iterating if you need to mutably borrow `self` during iteration ## Expression Types and Type Inference - `Expression::As` with `convert: None` represents bitcast operations - The validator (`naga/src/valid/expression.rs` ~line 1132) determines result types by: - Getting source type's scalar - Updating the `kind` to target kind - Keeping the same `width` for bitcast (no conversion) # Other This is stuff that Claude wrote for itself. It can probably be improved. ## Naga Tests **Test Structure:** 1. **Unit Tests**: In the same file as the code being tested - Example: `naga/src/proc/constant_evaluator.rs` has tests in a `#[cfg(test)] mod tests` block - Pattern: Create arenas, build expressions, evaluate, assert results 2. **Snapshot Tests**: - Input files: `naga/tests/in/wgsl/*.wgsl` - Output files: `naga/tests/out/wgsl/*.wgsl` - Run with: `cargo test -p naga --test naga snapshots::convert_snapshots_wgsl` - The output shows how constant expressions are evaluated at compile time 3. **Adding Integration Tests**: - Add test WGSL code to `naga/tests/in/wgsl/const-exprs.wgsl` for const expression tests - The test automatically parses, validates, and generates output in `naga/tests/out/wgsl/wgsl-const-exprs.wgsl` - Verify constant evaluation by checking the output file **Running Tests:** ```bash # Unit tests only cargo test -p naga --lib # Specific test cargo test -p naga --lib bitcast # All naga tests (including integration) cargo test -p naga # WGSL snapshot tests cargo test -p naga --test naga snapshots::convert_snapshots_wgsl ``` ## WGSL Specification Implementation When implementing WGSL built-in functions: 1. **Read the spec carefully**: WGSL spec section numbers are usually commented in code (e.g., "17.2.1. bitcast") 2. **Check parameterization**: The spec lists exactly which type combinations are valid 3. **Don't assume types**: For bitcast, only `i32`, `u32`, `f32` are specified - not 64-bit types 4. **AbstractInt special cases**: Some operations have special handling for abstract integers ================================================ FILE: CHANGELOG.md ================================================ # Change Log ## Unreleased ### Changes #### General - `Features::CLIP_DISTANCE`, `naga::Capabilities::CLIP_DISTANCE`, and `naga::BuiltIn::ClipDistance` have been renamed to `CLIP_DISTANCES` and `ClipDistances` (viz., pluralized) as appropriate, to match the WebGPU spec. By @ErichDonGubler in [#9267](https://github.com/gfx-rs/wgpu/pull/9267). #### Validation - Add clip distances validation for `maxInterStageShaderVariables`. By @ErichDonGubler in [8762](https://github.com/gfx-rs/wgpu/pull/8762). This may break some existing programs, but it compiles with the WebGPU spec. ### Bug Fixes #### General - Fix limit comparison logic for `max_inter_stage_shader_variables` By @ErichDonGubler in [9264](https://github.com/gfx-rs/wgpu/pull/9264). ## v29.0.0 (2026-03-18) ### Major Changes #### `Surface::get_current_texture` now returns `CurrentSurfaceTexture` enum `Surface::get_current_texture` no longer returns `Result`. Instead, it returns a single `CurrentSurfaceTexture` enum that represents all possible outcomes as variants. `SurfaceError` has been removed, and the `suboptimal` field on `SurfaceTexture` has been replaced by a dedicated `Suboptimal` variant. ```rust match surface.get_current_texture() { wgpu::CurrentSurfaceTexture::Success(frame) => { /* render */ } wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => { /* skip frame */ } wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Suboptimal(frame) => { /* reconfigure surface */ } wgpu::CurrentSurfaceTexture::Lost => { /* reconfigure surface, or recreate device if device lost */ } wgpu::CurrentSurfaceTexture::Validation => { /* Only happens if there is a validation error and you have registered a error scope or uncaptured error handler. */ } } ``` By @cwfitzgerald, @Wumpf, and @emilk in [#9141](https://github.com/gfx-rs/wgpu/pull/9141) and [#9257](https://github.com/gfx-rs/wgpu/pull/9257). #### `InstanceDescriptor` initialization APIs and display handle changes A display handle represents a connection to the platform's display server (e.g. a Wayland or X11 connection on Linux). This is distinct from a window — a display handle is the system-level connection through which windows are created and managed. `InstanceDescriptor`'s convenience constructors (an implementation of `Default` and the static `from_env_or_default` method) have been removed. In their place are new static methods that force recognition of whether a display handle is used: - `new_with_display_handle` - `new_with_display_handle_from_env` - `new_without_display_handle` - `new_without_display_handle_from_env` If you are using `winit`, this can be populated using `EventLoop::owned_display_handle`. ```diff - InstanceDescriptor::default(); - InstanceDescriptor::from_env_or_default(); + InstanceDescriptor::new_with_display_handle(Box::new(event_loop.owned_display_handle())); + InstanceDescriptor::new_with_display_handle_from_env(Box::new(event_loop.owned_display_handle())); ``` Additionally, `DisplayHandle` is now optional when creating a surface if a display handle was already passed to `InstanceDescriptor`. This means that once you've provided the display handle at instance creation time, you no longer need to pass it again for each surface you create. By @MarijnS95 in [#8782](https://github.com/gfx-rs/wgpu/pull/8782) #### Bind group layouts now optional in `PipelineLayoutDescriptor` This allows gaps in bind group layouts and adds full support for unbinding, bring us in compliance with the WebGPU spec. As a result of this `PipelineLayoutDescriptor`'s `bind_group_layouts` field now has type of `&[Option<&BindGroupLayout>]`. To migrate wrap bind group layout references in `Some`: ```diff let pl_desc = wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[ - &bind_group_layout + Some(&bind_group_layout) ], immediate_size: 0, }); ``` By @teoxoy in [#9034](https://github.com/gfx-rs/wgpu/pull/9034). #### MSRV update `wgpu` now has a new MSRV policy. This release has an MSRV of **1.87**. This is lower than v27's 1.88 and v28's 1.92. Going forward, we will only bump wgpu's MSRV if it has tangible benefits for the code, and we will never bump to an MSRV higher than `stable - 3`. So if stable is at 1.97 and 1.94 brought benefit to our code, we could bump it no higher than 1.94. As before, MSRV bumps will always be breaking changes. By @cwfitzgerald in [#8999](https://github.com/gfx-rs/wgpu/pull/8999). #### `WriteOnly` To ensure memory safety when accessing mapped GPU memory, `MapMode::Write` buffer mappings (`BufferViewMut` and also `QueueWriteBufferView`) can no longer be dereferenced to Rust `&mut [u8]`. Instead, they must be used through the new pointer type `wgpu::WriteOnly<[u8]>`, which does not allow reading at all. `WriteOnly<[u8]>` is designed to offer similar functionality to `&mut [u8]` and have almost no performance overhead, but you will probably need to make some changes for anything more complicated than `get_mapped_range_mut().copy_from_slice(my_data)`; in particular, replacing `view[start..end]` with `view.slice(start..end)`. By @kpreid in [#9042](https://github.com/gfx-rs/wgpu/pull/9042). #### Depth/stencil state changes The `depth_write_enabled` and `depth_compare` members of `DepthStencilState` are now optional, and may be omitted when they do not apply, to match WebGPU. `depth_write_enabled` is applicable, and must be `Some`, if `format` has a depth aspect, i.e., is a depth or depth/stencil format. Otherwise, a value of `None` best reflects that it does not apply, although `Some(false)` is also accepted. `depth_compare` is applicable, and must be `Some`, if `depth_write_enabled` is `Some(true)`, or if `depth_fail_op` for either stencil face is not `Keep`. Otherwise, a value of `None` best reflects that it does not apply, although `Some(CompareFunction::Always)` is also accepted. There is also a new constructor `DepthStencilState::stencil` which may be used instead of a struct literal for stencil operations. Example 1: A configuration that does a depth test and writes updated values: ```diff depth_stencil: Some(wgpu::DepthStencilState { format: wgpu::TextureFormat::Depth32Float, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::Less, + depth_write_enabled: Some(true), + depth_compare: Some(wgpu::CompareFunction::Less), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }), ``` Example 2: A configuration with only stencil: ```diff depth_stencil: Some(wgpu::DepthStencilState { format: wgpu::TextureFormat::Stencil8, - depth_write_enabled: false, - depth_compare: wgpu::CompareFunction::Always, + depth_write_enabled: None, + depth_compare: None, stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }), ``` Example 3: The previous example written using the new `stencil()` constructor: ```rust depth_stencil: Some(wgpu::DepthStencilState::stencil( wgpu::TextureFormat::Stencil8, wgpu::StencilState::default(), )), ``` #### D3D12 Agility SDK support Added support for loading a specific [DirectX 12 Agility SDK](https://devblogs.microsoft.com/directx/directx12agility/) runtime via the [Independent Devices API](https://devblogs.microsoft.com/directx/d3d12-independent-devices/). The Agility SDK lets applications ship a newer D3D12 runtime alongside their binary, unlocking the latest D3D12 features without waiting for an OS update. Configure it programmatically: ```rust let options = wgpu::Dx12BackendOptions { agility_sdk: Some(wgpu::Dx12AgilitySDK { sdk_version: 619, sdk_path: "path/to/sdk/bin/x64".into(), }), ..Default::default() }; ``` Or via environment variables: ``` WGPU_DX12_AGILITY_SDK_PATH=path/to/sdk/bin/x64 WGPU_DX12_AGILITY_SDK_VERSION=619 ``` The `sdk_version` must match the version of the `D3D12Core.dll` in the provided path exactly, or loading will fail. If the Agility SDK fails to load (e.g. version mismatch, missing DLL, or unsupported OS), wgpu logs a warning and falls back to the system D3D12 runtime. By @cwfitzgerald in [#9130](https://github.com/gfx-rs/wgpu/pull/9130). #### `primitive_index` is now a WGSL `enable` extension WGSL shaders using `@builtin(primitive_index)` must now request it with `enable primitive_index;`. The `SHADER_PRIMITIVE_INDEX` feature has been renamed to `PRIMITIVE_INDEX` and moved from `FeaturesWGPU` to `FeaturesWebGPU`. By @inner-daemons in [#8879](https://github.com/gfx-rs/wgpu/pull/8879) and @andyleiserson in [#9101](https://github.com/gfx-rs/wgpu/pull/9101). ```diff - device.features().contains(wgpu::FeaturesWGPU::SHADER_PRIMITIVE_INDEX) + device.features().contains(wgpu::FeaturesWebGPU::PRIMITIVE_INDEX) ``` ```wgsl // WGSL shaders must now include this directive: enable primitive_index; ``` #### `maxInterStageShaderComponents` replaced by `maxInterStageShaderVariables` Migrated from the `max_inter_stage_shader_components` limit to `max_inter_stage_shader_variables`, following the latest WebGPU spec. Components counted individual scalars (e.g. a `vec4` = 4 components), while variables counts locations (e.g. a `vec4` = 1 variable). This changes validation in a way that should not affect most programs. By @ErichDonGubler in [#8652](https://github.com/gfx-rs/wgpu/pull/8652), [#8792](https://github.com/gfx-rs/wgpu/pull/8792). ```diff - limits.max_inter_stage_shader_components + limits.max_inter_stage_shader_variables ``` #### Other Breaking Changes - Use clearer field names for `StageError::InvalidWorkgroupSize`. By @ErichDonGubler in [#9192](https://github.com/gfx-rs/wgpu/pull/9192). ### New Features #### General - Added TLAS binding array support via `ACCELERATION_STRUCTURE_BINDING_ARRAY`. By @kvark in [#8923](https://github.com/gfx-rs/wgpu/pull/8923). - Added `wgpu-naga-bridge` crate with conversions between `naga` and `wgpu-types` (features to capabilities, storage format mapping, shader stage mapping). By @atlv24 in [#9201](https://github.com/gfx-rs/wgpu/pull/9201). - Added support for cooperative load/store operations in shaders. Currently only WGSL on the input and SPIR-V, METAL, and WGSL on the output are supported. By @kvark in [#8251](https://github.com/gfx-rs/wgpu/issues/8251). - Added support for per-vertex attributes in fragment shaders. Currently only WGSL input is supported, and only SPIR-V or WGSL output is supported. By @atlv24 in [#8821](https://github.com/gfx-rs/wgpu/issues/8821). - Added support for no-perspective barycentric coordinates. By @atlv24 in [#8852](https://github.com/gfx-rs/wgpu/issues/8852). - Added support for obtaining `AdapterInfo` from `Device`. By @sagudev in [#8807](https://github.com/gfx-rs/wgpu/pull/8807). - Added `Limits::or_worse_values_from`. By @atlv24 in [#8870](https://github.com/gfx-rs/wgpu/pull/8870). - Added `Features::FLOAT32_BLENDABLE` on Vulkan and Metal. By @timokoesters in [#8963](https://github.com/gfx-rs/wgpu/pull/8963) and @andyleiserson in [#9032](https://github.com/gfx-rs/wgpu/pull/9032). - Added `Dx12BackendOptions::force_shader_model` to allow using advanced features in passthrough shaders without bundling DXC. By @inner-daemons in [#8984](https://github.com/gfx-rs/wgpu/pull/8984). - Changed passthrough shaders to not require an entry point parameter, so that the same shader module may be used in multiple entry points. Also added support for metallib passthrough. By @inner-daemons in [#8886](https://github.com/gfx-rs/wgpu/pull/8886). - Added `Dx12Compiler::Auto` to automatically use static or dynamic DXC if available, before falling back to FXC. By @inner-daemons in [#8882](https://github.com/gfx-rs/wgpu/pull/8882). - Added support for `insert_debug_marker`, `push_debug_group` and `pop_debug_group` on WebGPU. By @evilpie in [#9017](https://github.com/gfx-rs/wgpu/pull/9017). - Added support for `@builtin(draw_index)` to the vulkan backend. By @inner-daemons in [#8883](https://github.com/gfx-rs/wgpu/pull/8883). - Added `TextureFormat::channels` method to get some information about which color channels are covered by the texture format. By @TornaxO7 in [#9167](https://github.com/gfx-rs/wgpu/pull/9167) - BREAKING: Add `V6_8` variant to `DxcShaderModel` and `naga::back::hlsl::ShaderModel`. By @inner-daemons in [#8882](https://github.com/gfx-rs/wgpu/pull/8882) and @ErichDonGubler in [#9083](https://github.com/gfx-rs/wgpu/pull/9083). - BREAKING: Add `V6_9` variant to `DxcShaderModel` and `naga::back::hlsl::ShaderModel`. By @ErichDonGubler in [#9083](https://github.com/gfx-rs/wgpu/pull/9083). #### naga - Initial wgsl-in ray tracing pipelines. By @Vecvec in [#8570](https://github.com/gfx-rs/wgpu/pull/8570). - wgsl-out ray tracing pipelines. By @Vecvec in [#8970](https://github.com/gfx-rs/wgpu/pull/8970). - Allow parsing shaders which make use of `SPV_KHR_non_semantic_info` for debug info. Also removes `naga::front::spv::SUPPORTED_EXT_SETS`. By @inner-daemons in [#8827](https://github.com/gfx-rs/wgpu/pull/8827). - Added memory decorations for storage buffers: `coherent`, supported on all native backends, and `volatile`, only on Vulkan and GL. By @atlv24 in [#9168](https://github.com/gfx-rs/wgpu/pull/9168). - Made the following available in `const` contexts; by @ErichDonGubler in [#8943](https://github.com/gfx-rs/wgpu/pull/8943): - `naga` - `Arena::len` - `Arena::is_empty` - `Range::first_and_last` - `front::wgsl::Frontend::set_options` - `ir::Block::is_empty` - `ir::Block::len` #### GLES - Added `GlDebugFns` option in `GlBackendOptions` to control OpenGL debug functions (`glPushDebugGroup`, `glPopDebugGroup`, `glObjectLabel`, etc.). Automatically disables them on Mali GPUs to work around a driver crash. By @Xavientois in [#8931](https://github.com/gfx-rs/wgpu/pull/8931). #### WebGPU - Added support for `insert_debug_marker`, `push_debug_group` and `pop_debug_group`. By @evilpie in [#9017](https://github.com/gfx-rs/wgpu/pull/9017). - Added support for `begin_occlusion_query` and `end_occlusion_query`. By @evilpie in [#9039](https://github.com/gfx-rs/wgpu/pull/9039). ### Changes #### General - Tracing now uses the `.metal` extension for metal source files, instead of `.msl`. By @inner-daemons in [#8880](https://github.com/gfx-rs/wgpu/pull/8880). - BREAKING: Several error APIs were changed by @ErichDonGubler in [#9073](https://github.com/gfx-rs/wgpu/pull/9073) and [#9205](https://github.com/gfx-rs/wgpu/pull/9205): - `BufferAccessError`: - Split the `OutOfBoundsOverrun` variant into new `OutOfBoundsStartOffsetOverrun` and `OutOfBoundsEndOffsetOverrun` variants. - Removed the `NegativeRange` variant in favor of new `MapStartOffsetUnderrun` and `MapStartOffsetOverrun` variants. - Split the `TransferError::BufferOverrun` variant into new `BufferStartOffsetOverrun` and `BufferEndOffsetOverrun` variants. - `ImmediateUploadError`: - Removed the `TooLarge` variant in favor of new `StartOffsetOverrun` and `EndOffsetOverrun` variants. - Removed the `Unaligned` variant in favor of new `StartOffsetUnaligned` and `SizeUnaligned` variants. - Added the `ValueStartIndexOverrun` and `ValueEndIndexOverrun` invariants - The various "max resources per stage" limits are now capped at 100, so that their total remains below `max_bindings_per_bind_group`, as required by WebGPU. By @andyleiserson in [#9118](https://github.com/gfx-rs/wgpu/pull/9118). - The `max_uniform_buffer_binding_size` and `max_storage_buffer_binding_size` limits are now `u64` instead of `u32`, to match WebGPU. By @wingertge in [#9146](https://github.com/gfx-rs/wgpu/pull/9146). - The main 3 native backends now report their limits properly. By @teoxoy in [#9196](https://github.com/gfx-rs/wgpu/pull/9196). #### naga - Naga and `wgpu` now reject shaders with an `enable` directive for functionality that is not available, even if that functionality is not used by the shader. By @andyleiserson in [#8913](https://github.com/gfx-rs/wgpu/pull/8913). - Prevent UB from incorrectly using ray queries on HLSL. By @Vecvec in [#8763](https://github.com/gfx-rs/wgpu/pull/8763). - Added support for dual-source blending in SPIR-V shaders. By @andyleiserson in [#8865](https://github.com/gfx-rs/wgpu/pull/8865). - Added `supported_capabilities` to all backends. By @inner-daemons in [#9068](https://github.com/gfx-rs/wgpu/pull/9068). - Updated codespan-reporting to 0.13. By @cwfitzgerald in [#9243](https://github.com/gfx-rs/wgpu/pull/9243). #### Metal - Use autogenerated `objc2` bindings internally, which should resolve a lot of leaks and unsoundness. By @madsmtm in [#5641](https://github.com/gfx-rs/wgpu/pull/5641). - Implements ray-tracing acceleration structures for metal backend. By @lichtso in [#8071](https://github.com/gfx-rs/wgpu/pull/8071). - Remove mutex for `MTLCommandQueue` because the Metal object is thread-safe. By @andyleiserson in [#9217](https://github.com/gfx-rs/wgpu/pull/9217). #### deno_webgpu - Expose the `GPU.wgslLanguageFeatures` property. By @andyleiserson in [#8884](https://github.com/gfx-rs/wgpu/pull/8884). - `GPUFeatureName` now includes all `wgpu` extensions. Feature names for extensions should be written with a `wgpu-` prefix, although unprefixed names that were accepted previously are still accepted. By @andyleiserson in [#9163](https://github.com/gfx-rs/wgpu/pull/9163). #### Hal - Make ordered texture and buffer uses hal specific. By @NiklasEi in [#8924](https://github.com/gfx-rs/wgpu/pull/8924). ### Bug Fixes #### General - Tracing support has been restored. By @andyleiserson in [#8429](https://github.com/gfx-rs/wgpu/pull/8429). - Pipelines using passthrough shaders now correctly require explicit pipeline layout. By @inner-daemons in [#8881](https://github.com/gfx-rs/wgpu/pull/8881). - Allow using a shader that defines I/O for dual-source blending in a pipeline that does not make use of it. By @andyleiserson in [#8856](https://github.com/gfx-rs/wgpu/pull/8856). - Improve validation of dual-source blending, by @andyleiserson in [#9200](https://github.com/gfx-rs/wgpu/pull/9200): - Validate structs with `@blend_src` members whether or not they are used by an entry point. - Dual-source blending is not supported when there are multiple color attachments. - `TypeFlags::IO_SHAREABLE` is not set for structs other than `@blend_src` structs. - Validate `strip_index_format` isn't None and equals index buffer format for indexed drawing with strip topology. By @beicause in [#8850](https://github.com/gfx-rs/wgpu/pull/8850). - BREAKING: Renamed `EXPERIMENTAL_PASSTHROUGH_SHADERS` to `PASSTHROUGH_SHADERS` and made this no longer an experimental feature. By @inner-daemons in [#9054](https://github.com/gfx-rs/wgpu/pull/9054). - BREAKING: End offsets in trace and `player` commands are now represented using `offset` + `size` instead. By @ErichDonGubler in [#9073](https://github.com/gfx-rs/wgpu/pull/9073). - Validate some uncaught cases where buffer transfer operations could overflow when computing an end offset. By @ErichDonGubler in [#9073](https://github.com/gfx-rs/wgpu/pull/9073). - Fix `local_invocation_id` and `local_invocation_index` being written multiple times in HLSL/MSL backends, and naming conflicts when users name variables `__local_invocation_id` or `__local_invocation_index`. By @inner-daemons in [#9099](https://github.com/gfx-rs/wgpu/pull/9099). - Added internal labels to validation GPU objects and timestamp normalization code to improve clarity in graphics debuggers. By @szostid in [#9094](https://github.com/gfx-rs/wgpu/pull/9094) - Fix multi-planar texture copying. By @noituri in [#9069](https://github.com/gfx-rs/wgpu/pull/9069) #### naga - The validator checks that override-sized arrays have a positive size, if overrides have been resolved. By @andyleiserson in [#8822](https://github.com/gfx-rs/wgpu/pull/8822). - Fix some cases where f16 constants were not working. By @andyleiserson in [#8816](https://github.com/gfx-rs/wgpu/pull/8816). - Use wrapping arithmetic when evaluating constant expressions involving `u32`. By @andyleiserson in [#8912](https://github.com/gfx-rs/wgpu/pull/8912). - Fix missing side effects from sequence expressions in GLSL. By @Vipitis in [#8787](https://github.com/gfx-rs/wgpu/pull/8787). - Naga now enforces the `@must_use` attribute on WGSL built-in functions, when applicable. You can waive the error with a phony assignment, e.g., `_ = subgroupElect()`. By @andyleiserson in [#8713](https://github.com/gfx-rs/wgpu/pull/8713). - Reject zero-value construction of a runtime-sized array with a validation error. Previously it would crash in the HLSL backend. By @mooori in [#8741](https://github.com/gfx-rs/wgpu/pull/8741). - Reject splat vector construction if the argument type does not match the type of the vector's scalar. Previously it would succeed. By @mooori in [#8829](https://github.com/gfx-rs/wgpu/pull/8829). - Fixed `workgroupUniformLoad` incorrectly returning an atomic when called on an atomic, it now returns the inner `T` as per the spec. By @cryvosh in [#8791](https://github.com/gfx-rs/wgpu/pull/8791). - Fixed constant evaluation for `sign()` builtin to return zero when the argument is zero. By @mandryskowski in [#8942](https://github.com/gfx-rs/wgpu/pull/8942). - Allow array generation to compile with the macOS 10.12 Metal compiler. By @madsmtm in [#8953](https://github.com/gfx-rs/wgpu/pull/8953) - Naga now detects bitwise shifts by a constant exceeding the operand bit width at compile time, and disallows scalar-by-vector and vector-by-scalar shifts in constant evaluation. By @andyleiserson in [#8907](https://github.com/gfx-rs/wgpu/pull/8907). - Naga uses wrapping arithmetic when evaluating dot products on concrete integer types (`u32` and `i32`). By @BKDaugherty in [#9142](https://github.com/gfx-rs/wgpu/pull/9142). - Disallow negation of a matrix in WGSL. By @andyleiserson in [#9157](https://github.com/gfx-rs/wgpu/pull/9157). - Fix evaluation order of compound assignment (e.g. `+=`) LHS and RHS. By @andyleiserson in [#9181](https://github.com/gfx-rs/wgpu/pull/9181). - Fixed invalid MSL when `float16`-format vertex input data was accessed via an `f16`-type variable in a vertex shader. By @andyleiserson in [#9166](https://github.com/gfx-rs/wgpu/pull/9166). #### Validation - Fixed validation of the texture format in GPUDepthStencilState when neither depth nor stencil is actually enabled. By @andyleiserson in [#8766](https://github.com/gfx-rs/wgpu/pull/8766). - Check that depth bias is not used with non-triangle topologies. By @andyleiserson in [#8856](https://github.com/gfx-rs/wgpu/pull/8856). - Check that if the shader outputs `frag_depth`, then the pipeline must have a depth attachment. By @andyleiserson in [#8856](https://github.com/gfx-rs/wgpu/pull/8856). - Fix incorrect acceptance of some swizzle selectors that are not valid for their operand, e.g. `const v = vec2(); let r = v.xyz`. By @andyleiserson in [#8949](https://github.com/gfx-rs/wgpu/pull/8949). - Fixed calculation of the total number of bindings in a pipeline layout when validating against device limits. By @andyleiserson in [#8997](https://github.com/gfx-rs/wgpu/pull/8997). - Reject non-constructible types (runtime- and override-sized arrays, and structs containing non-constructible types) in more places where they should not be allowed. By @andyleiserson in [#8873](https://github.com/gfx-rs/wgpu/pull/8873). - The query set type for an occlusion query is now validated when opening the render pass, in addition to within the call to `beginOcclusionQuery`. By @andyleiserson in [#9086](https://github.com/gfx-rs/wgpu/pull/9086). - Require that the blend factor is `One` when the blend operation is `Min` or `Max`. The `BlendFactorOnUnsupportedTarget` error is now reported within `ColorStateError` rather than directly in `CreateRenderPipelineError`. By @andyleiserson in [#9110](https://github.com/gfx-rs/wgpu/pull/9110). #### Vulkan - Fixed a variety of mesh shader SPIR-V writer issues from the original implementation. By @inner-daemons in [#8756](https://github.com/gfx-rs/wgpu/pull/8756) - Offset the vertex buffer device address when building a BLAS instead of using the `first_vertex` field. By @Vecvec in [#9220](https://github.com/gfx-rs/wgpu/pull/9220) - Remove incorrect ordered texture uses. By @NiklasEi in [#8924](https://github.com/gfx-rs/wgpu/pull/8924). #### Metal / macOS - Fix one-second delay when switching a wgpu app to the foreground. By [@emilk](https://github.com/emilk) in [#9141](https://github.com/gfx-rs/wgpu/pull/9141) - Work around Metal driver bug with atomic textures. By @atlv24 in [#9185](https://github.com/gfx-rs/wgpu/pull/9185) - Fix setting an immediate for a Mesh shader. By [@waywardmonkeys](https://github.com/waywardmonkeys) in [#9254](https://github.com/gfx-rs/wgpu/pull/9254) #### GLES - `DisplayHandle` should now be passed to `InstanceDescriptor` for correct EGL initialization on Wayland. By @MarijnS95 in [#8012](https://github.com/gfx-rs/wgpu/pull/8012) Note that the existing workaround to create surfaces before the adapter is no longer valid. - Changing shader constants now correctly recompiles the shader. By @DerSchmale in [#8291](https://github.com/gfx-rs/wgpu/pull/8291). ### Performance #### GLES - The GL backend would now try to take advantage of `GL_EXT_multisampled_render_to_texture` extension when applicable to skip the multi-sample resolve operation. By @opstic in [#8536](https://github.com/gfx-rs/wgpu/pull/8536). ### Documentation #### General - Expanded documentation of `QuerySet`, `QueryType`, and `resolve_query_set()` describing how to use queries. By @kpreid in [#8776](https://github.com/gfx-rs/wgpu/pull/8776). ## v28.0.1 (2025-03-01) ### General - Fixed crash on nvidia cards when presenting from another thread. By @inner-daemons in [#9036](https://github.com/gfx-rs/wgpu/pull/9036). ### Vulkan - Fixed crash on some Mali drivers on Android. By @beicause in [#8769](https://github.com/gfx-rs/wgpu/pull/8769). ### Metal - Re-added support for TRANSIENT textures on Apple A7 chips. By @Opstic in [#8725](https://github.com/gfx-rs/wgpu/pull/8725). ## v28.0.0 (2025-12-17) ### Major Changes #### Mesh Shaders This has been a long time coming. See [the tracking issue](https://github.com/gfx-rs/wgpu/issues/7197) for more information. They are now fully supported on Vulkan, and supported on Metal and DX12 with passthrough shaders. WGSL parsing and rewriting is supported, meaning they can be used through WESL or naga_oil. Mesh shader pipelines replace the standard vertex shader pipelines and allow new ways to render meshes. They are ideal for meshlet rendering, a form of rendering where small groups of triangles are handled together, for both culling and rendering. They are compute-like shaders, and generate primitives which are passed directly to the rasterizer, rather than having a list of vertices generated individually and then using a static index buffer. This means that certain computations on nearby groups of triangles can be done together, the relationship between vertices and primitives is more programmable, and you can even pass non-interpolated per-primitive data to the fragment shader, independent of vertices. Mesh shaders are very versatile, and are powerful enough to replace vertex shaders, tesselation shaders, and geometry shaders on their own or with task shaders. A full example of mesh shaders in use can be seen in the `mesh_shader` example. For the full specification of mesh shaders in wgpu, go to [docs/api-specs/mesh_shading.md](docs/api-specs/mesh_shading.md). Below is a small snippet of shader code demonstrating their usage: ```wgsl @task @payload(taskPayload) @workgroup_size(1) fn ts_main() -> @builtin(mesh_task_size) vec3 { // Task shaders can use workgroup variables like compute shaders workgroupData = 1.0; // Pass some data to all mesh shaders dispatched by this workgroup taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0); taskPayload.visible = 1; // Dispatch a mesh shader grid with one workgroup return vec3(1, 1, 1); } @mesh(mesh_output) @payload(taskPayload) @workgroup_size(1) fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocation_id) id: vec3) { // Set how many outputs this workgroup will generate mesh_output.vertex_count = 3; mesh_output.primitive_count = 1; // Can also use workgroup variables workgroupData = 2.0; // Set vertex outputs mesh_output.vertices[0].position = positions[0]; mesh_output.vertices[0].color = colors[0] * taskPayload.colorMask; mesh_output.vertices[1].position = positions[1]; mesh_output.vertices[1].color = colors[1] * taskPayload.colorMask; mesh_output.vertices[2].position = positions[2]; mesh_output.vertices[2].color = colors[2] * taskPayload.colorMask; // Set the vertex indices for the only primitive mesh_output.primitives[0].indices = vec3(0, 1, 2); // Cull it if the data passed by the task shader says to mesh_output.primitives[0].cull = taskPayload.visible == 1; // Give a noninterpolated per-primitive vec4 to the fragment shader mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } ``` ##### Thanks This was a monumental effort from many different people, but it was championed by @inner-daemons, without whom it would not have happened. Thank you @cwfitzgerald for doing the bulk of the code review. Finally thank you @ColinTimBarndt for coordinating the testing effort. Reviewers: - @cwfitzgerald - @jimblandy - @ErichDonGubler `wgpu` Contributions: - Metal implementation in wgpu-hal. By @inner-daemons in [#8139](https://github.com/gfx-rs/wgpu/pull/8139). - DX12 implementation in wgpu-hal. By @inner-daemons in [#8110](https://github.com/gfx-rs/wgpu/pull/8110). - Vulkan implementation in wgpu-hal. By @inner-daemons in [#7089](https://github.com/gfx-rs/wgpu/pull/7089). - wgpu/wgpu-core implementation. By @inner-daemons in [#7345](https://github.com/gfx-rs/wgpu/pull/7345). - New mesh shader limits and validation. By @inner-daemons in [#8507](https://github.com/gfx-rs/wgpu/pull/8507). `naga` Contributions: - Naga IR implementation. By @inner-daemons in [#8104](https://github.com/gfx-rs/wgpu/pull/8104). - `wgsl-in` implementation in naga. By @inner-daemons in [#8370](https://github.com/gfx-rs/wgpu/pull/8370). - `spv-out` implementation in naga. By @inner-daemons in [#8456](https://github.com/gfx-rs/wgpu/pull/8456). - `wgsl-out` implementation in naga. By @Slightlyclueless in [#8481](https://github.com/gfx-rs/wgpu/pull/8481). - Allow barriers in mesh/task shaders. By @inner-daemons in [#8749](https://github.com/gfx-rs/wgpu/pull/8749) Testing Assistance: - @ColinTimBarndt - @AdamK2003 - @Mhowser - @9291Sam - 3 more testers who wished to remain anonymous. Thank you to everyone to made this happen! #### Switch from `gpu-alloc` to `gpu-allocator` in the `vulkan` backend `gpu-allocator` is the allocator used in the `dx12` backend, allowing to configure the allocator the same way in those two backends converging their behavior. This also brings the `Device::generate_allocator_report` feature to the vulkan backend. By @DeltaEvo in [#8158](https://github.com/gfx-rs/wgpu/pull/8158). #### `wgpu::Instance::enumerate_adapters` is now `async` & available on WebGPU BREAKING CHANGE: `enumerate_adapters` is now `async`: ```diff - pub fn enumerate_adapters(&self, backends: Backends) -> Vec { + pub fn enumerate_adapters(&self, backends: Backends) -> impl Future> { ``` This yields two benefits: - This method is now implemented on non-native using the standard `Adapter::request_adapter(…)`, making `enumerate_adapters` a portable surface. This was previously a nontrivial pain point when an application wanted to do some of its own filtering of adapters. - This method can now be implemented in custom backends. By @R-Cramer4 in [#8230](https://github.com/gfx-rs/wgpu/pull/8230) #### New `LoadOp::DontCare` In the case where a renderpass unconditionally writes to all pixels in the rendertarget, `Load` can cause unnecessary memory traffic, and `Clear` can spend time unnecessarily clearing the rendertargets. `DontCare` is a new `LoadOp` which will leave the contents of the rendertarget undefined. Because this could lead to undefined behavior, this API requires that the user gives an unsafe token to use the api. While you can use this unconditionally, on platforms where `DontCare` is not available, it will internally use a different load op. ```rust load: LoadOp::DontCare(unsafe { wgpu::LoadOpDontCare::enabled() }) ``` By @cwfitzgerald in [#8549](https://github.com/gfx-rs/wgpu/pull/8549) #### `MipmapFilterMode` is split from `FilterMode` This is a breaking change that aligns wgpu with spec. ```diff SamplerDescriptor { ... - mipmap_filter: FilterMode::Nearest + mipmap_filter: MipmapFilterMode::Nearest ... } ``` By @sagudev in [#8314](https://github.com/gfx-rs/wgpu/pull/8314). #### Multiview on all major platforms and support for multiview bitmasks Multiview is a feature that allows rendering the same content to multiple layers of a texture. This is useful primarily in VR where you wish to display almost identical content to 2 views, just with a different perspective. Instead of using 2 draw calls or 2 instances for each object, you can use this feature. Multiview is also called view instancing in DX12 or vertex amplification in Metal. Multiview has been reworked, adding support for Metal and DX12, and adding testing and validation to wgpu itself. This change also introduces a view bitmask, a new field in `RenderPassDescriptor` that allows a render pass to render to multiple non-adjacent layers when using the `SELECTIVE_MULTIVIEW` feature. If you don't use multi-view, you can set this field to none. ```diff - wgpu::RenderPassDescriptor { - label: None, - color_attachments: &color_attachments, - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - } + wgpu::RenderPassDescriptor { + label: None, + color_attachments: &color_attachments, + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + multiview_mask: NonZero::new(3), + } ``` One other breaking change worth noting is that in WGSL `@builtin(view_index)` now requires a type of `u32`, where previously it required `i32`. By @inner-daemons in [#8206](https://github.com/gfx-rs/wgpu/pull/8206). #### Error scopes now use guards and are thread-local. ```diff - device.push_error_scope(wgpu::ErrorFilter::Validation); + let scope = device.push_error_scope(wgpu::ErrorFilter::Validation); // ... perform operations on the device ... - let error: Option = device.pop_error_scope().await; + let error: Option = scope.pop().await; ``` Device error scopes now operate on a per-thread basis. This allows them to be used easily within multithreaded contexts, without having the error scope capture errors from other threads. When the `std` feature is **not** enabled, we have no way to differentiate between threads, so error scopes return to be global operations. By @cwfitzgerald in [#8685](https://github.com/gfx-rs/wgpu/pull/8685) #### Log Levels We have received complaints about wgpu being way too log spammy at log levels `info`/`warn`/`error`. We have adjusted our log policy and changed logging such that `info` and above should be silent unless some exceptional event happens. Our new log policy is as follows: - Error: if we can’t (for some reason, usually a bug) communicate an error any other way. - Warning: similar, but there may be one-shot warnings about almost certainly sub-optimal. - Info: do not use - Debug: Used for interesting events happening inside wgpu. - Trace: Used for all events that might be useful to either `wgpu` or application developers. By @cwfitzgerald in [#8579](https://github.com/gfx-rs/wgpu/pull/8579). #### Push constants renamed immediates, API brought in line with spec. As the "immediate data" api is getting close to stabilization in the WebGPU specification, we're bringing our implementation in line with what the spec dictates. First, in the `PipelineLayoutDescriptor`, you now pass a unified size for all stages: ```diff - push_constant_ranges: &[wgpu::PushConstantRange { - stages: wgpu::ShaderStages::VERTEX_FRAGMENT, - range: 0..12, - }] + immediate_size: 12, ``` Second, on the command encoder you no longer specify a shader stage, uploads apply to all shader stages that use immediate data. ```diff - rpass.set_push_constants(wgpu::ShaderStages::FRAGMENT, 0, bytes); + rpass.set_immediates(0, bytes); ``` Third, immediates are now declared with the `immediate` address space instead of the `push_constant` address space. Due to a [known issue on DX12](https://github.com/gfx-rs/wgpu/issues/5683) it is advised to always use a structure for your immediates until that issue is fixed. ```diff - var my_pc: MyPushConstant; + var my_imm: MyImmediate; ``` Finally, our implementation currently still zero-initializes the immediate data range you declared in the pipeline layout. This is not spec compliant and failing to populate immediate "slots" that are used in the shader will be a validation error in a future version. See [the proposal][immediate-data-spec] for details for determining which slots are populated in a given shader. By @cwfitzgerald in [#8724](https://github.com/gfx-rs/wgpu/pull/8724). [immediate-data-spec]: https://github.com/gpuweb/gpuweb/blob/main/proposals/immediate-data.md#immediate-slots #### `subgroup_{min,max}_size` renamed and moved from `Limits` -> `AdapterInfo` To bring our code in line with the WebGPU spec, we have moved information about subgroup size from limits to adapter info. Limits was not the correct place for this anyway, and we had some code special casing those limits. Additionally we have renamed the fields to match the spec. ```diff - let min = limits.min_subgroup_size; + let min = info.subgroup_min_size; - let max = limits.max_subgroup_size; + let max = info.subgroup_max_size; ``` By @cwfitzgerald in [#8609](https://github.com/gfx-rs/wgpu/pull/8609). ### New Features - Added support for transient textures on Vulkan and Metal. By @opstic in [#8247](https://github.com/gfx-rs/wgpu/pull/8247) - Implement shader triangle barycentric coordinate builtins. By @atlv24 in [#8320](https://github.com/gfx-rs/wgpu/pull/8320). - Added support for binding arrays of storage textures on Metal. By @msvbg in [#8464](https://github.com/gfx-rs/wgpu/pull/8464) - Added support for multisampled texture arrays on Vulkan through adapter feature `MULTISAMPLE_ARRAY`. By @LaylBongers in [#8571](https://github.com/gfx-rs/wgpu/pull/8571). - Added `get_configuration` to `wgpu::Surface`, that returns the current configuration of `wgpu::Surface`. By @sagudev in [#8664](https://github.com/gfx-rs/wgpu/pull/8664). - Add `wgpu_core::Global::create_bind_group_layout_error`. By @ErichDonGubler in [#8650](https://github.com/gfx-rs/wgpu/pull/8650). ### Changes #### General - Require new enable extensions when using ray queries and position fetch (`wgpu_ray_query`, `wgpu_ray_query_vertex_return`). By @Vecvec in [#8545](https://github.com/gfx-rs/wgpu/pull/8545). - Texture now has `from_custom`. By @R-Cramer4 in [#8315](https://github.com/gfx-rs/wgpu/pull/8315). - Using both the wgpu command encoding APIs and `CommandEncoder::as_hal_mut` on the same encoder will now result in a panic. - Allow `include_spirv!` and `include_spirv_raw!` macros to be used in constants and statics. By @clarfonthey in [#8250](https://github.com/gfx-rs/wgpu/pull/8250). - Added support for rendering onto multi-planar textures. By @noituri in [#8307](https://github.com/gfx-rs/wgpu/pull/8307). - Validation errors from `CommandEncoder::finish()` will report the label of the invalid encoder. By @kpreid in [#8449](https://github.com/gfx-rs/wgpu/pull/8449). - Corrected documentation of the minimum alignment of the _end_ of a mapped range of a buffer (it is 4, not 8). By @kpreid in [#8450](https://github.com/gfx-rs/wgpu/pull/8450). - `util::StagingBelt` now takes a `Device` when it is created instead of when it is used. By @kpreid in [#8462](https://github.com/gfx-rs/wgpu/pull/8462). - `wgpu_hal::vulkan::Texture` API changes to handle externally-created textures and memory more flexibly. By @s-ol in [#8512](https://github.com/gfx-rs/wgpu/pull/8512), [#8521](https://github.com/gfx-rs/wgpu/pull/8521). - Render passes are now validated against the `maxColorAttachmentBytesPerSample` limit. By @andyleiserson in [#8697](https://github.com/gfx-rs/wgpu/pull/8697). #### Metal - Expose render layer. By @xiaopengli89 in [#8707](https://github.com/gfx-rs/wgpu/pull/8707) - `MTLDevice` is thread-safe. By @uael in [#8168](https://github.com/gfx-rs/wgpu/pull/8168) #### naga - Prevent UB with invalid ray query calls on spirv. By @Vecvec in [#8390](https://github.com/gfx-rs/wgpu/pull/8390). - Update the set of binding_array capabilities. In most cases, they are set automatically from `wgpu` features, and this change should not be user-visible. By @andyleiserson in [#8671](https://github.com/gfx-rs/wgpu/pull/8671). - Naga now accepts the `var` syntax for declaring local variables. By @andyleiserson in [#8710](https://github.com/gfx-rs/wgpu/pull/8710). ### Bug Fixes #### General - Fixed a bug where mapping sub-ranges of a buffer on web would fail with `OperationError: GPUBuffer.getMappedRange: GetMappedRange range extends beyond buffer's mapped range`. By @ryankaplan in [#8349](https://github.com/gfx-rs/wgpu/pull/8349) - Reject fragment shader output `location`s > `max_color_attachments` limit. By @ErichDonGubler in [#8316](https://github.com/gfx-rs/wgpu/pull/8316). - WebGPU device requests now support the required limits `maxColorAttachments` and `maxColorAttachmentBytesPerSample`. By @evilpie in [#8328](https://github.com/gfx-rs/wgpu/pull/8328) - Reject binding indices that exceed `wgpu_types::Limits::max_bindings_per_bind_group` when deriving a bind group layout for a pipeline. By @jimblandy in [#8325](https://github.com/gfx-rs/wgpu/pull/8325). - Removed three features from `wgpu-hal` which did nothing useful: `"cargo-clippy"`, `"gpu-allocator"`, and `"rustc-hash"`. By @kpreid in [#8357](https://github.com/gfx-rs/wgpu/pull/8357). - `wgpu_types::PollError` now always implements the `Error` trait. By @kpreid in [#8384](https://github.com/gfx-rs/wgpu/pull/8384). - The texture subresources used by the color attachments of a render pass are no longer allowed to overlap when accessed via different texture views. By @andyleiserson in [#8402](https://github.com/gfx-rs/wgpu/pull/8402). - The `STORAGE_READ_ONLY` texture usage is now permitted to coexist with other read-only usages. By @andyleiserson in [#8490](https://github.com/gfx-rs/wgpu/pull/8490). - Validate that buffers are unmapped in `write_buffer` calls. By @ErichDonGubler in [#8454](https://github.com/gfx-rs/wgpu/pull/8454). - Shorten critical section inside present such that the snatch write lock is no longer held during present, preventing other work happening on other threads. By @cwfitzgerald in [#8608](https://github.com/gfx-rs/wgpu/pull/8608). #### naga - The `||` and `&&` operators now "short circuit", i.e., do not evaluate the RHS if the result can be determined from just the LHS. By @andyleiserson in [#7339](https://github.com/gfx-rs/wgpu/pull/7339). - Fix a bug that resulted in the Metal error `program scope variable must reside in constant address space` in some cases. By @teoxoy in [#8311](https://github.com/gfx-rs/wgpu/pull/8311). - Handle `rayQueryTerminate` in spv-out instead of ignoring it. By @Vecvec in [#8581](https://github.com/gfx-rs/wgpu/pull/8581). #### DX12 - Align copies b/w textures and buffers via a single intermediate buffer per copy when `D3D12_FEATURE_DATA_D3D12_OPTIONS13.UnrestrictedBufferTextureCopyPitchSupported` is `false`. By @ErichDonGubler in [#7721](https://github.com/gfx-rs/wgpu/pull/7721). - Fix detection of Int64 Buffer/Texture atomic features. By @cwfitzgerald in [#8667](https://github.com/gfx-rs/wgpu/pull/8667). #### Vulkan - Fixed a validation error regarding atomic memory semantics. By @atlv24 in [#8391](https://github.com/gfx-rs/wgpu/pull/8391). #### Metal - Fixed a variety of feature detection related bugs. By @inner-daemons in [#8439](https://github.com/gfx-rs/wgpu/pull/8439). #### WebGPU - Fixed a bug where the texture aspect was not passed through when calling `copy_texture_to_buffer` in WebGPU, causing the copy to fail for depth/stencil textures. By @Tim-Evans-Seequent in [#8445](https://github.com/gfx-rs/wgpu/pull/8445). #### GLES - Fix race when downloading texture from compute shader pass. By @SpeedCrash100 in [#8527](https://github.com/gfx-rs/wgpu/pull/8527) - Fix double window class registration when dynamic libraries are used. By @Azorlogh in [#8548](https://github.com/gfx-rs/wgpu/pull/8548) - Fix context loss on device initialization on GL3.3-4.1 contexts. By @cwfitzgerald in [#8674](https://github.com/gfx-rs/wgpu/pull/8674). - `VertexFormat::Unorm10_10_10_2` can now be used on `gl` backends. By @mooori in [#8717](https://github.com/gfx-rs/wgpu/pull/8717). #### hal - `DropCallback`s are now called after dropping all other fields of their parent structs. By @jerzywilczek in [#8353](https://github.com/gfx-rs/wgpu/pull/8353) ## v27.0.4 (2025-10-23) This release includes `wgpu-hal` version `27.0.4`. All other crates remain at their previous versions. ### Bug Fixes #### General - Remove fragile dependency constraint on `ordered-float` that prevented semver-compatible changes above `5.0.0`. By @kpreid in [#8371](https://github.com/gfx-rs/wgpu/pull/8371). #### Vulkan - Work around extremely poor frame pacing from AMD and Nvidia cards on Windows in `Fifo` and `FifoRelaxed` present modes. This is due to the drivers implicitly using a DXGI (Direct3D) swapchain to implement these modes and it having vastly different timing properties. See https://github.com/gfx-rs/wgpu/issues/8310 and https://github.com/gfx-rs/wgpu/issues/8354 for more information. By @cwfitzgerald in [#8420](https://github.com/gfx-rs/wgpu/pull/8420). ## v27.0.3 (2025-10-22) This release includes `naga`, `wgpu-core` and `wgpu-hal` version `27.0.3`. All other crates remain at their previous versions. ### Bug Fixes #### naga - Fix a bug that resulted in the Metal error `program scope variable must reside in constant address space` in some cases. Backport of [#8311](https://github.com/gfx-rs/wgpu/pull/8311) by @teoxoy. #### General - Remove an assertion that causes problems if `CommandEncoder::as_hal_mut` is used. By @andyleiserson in [#8387](https://github.com/gfx-rs/wgpu/pull/8387). #### DX12 - Align copies b/w textures and buffers via a single intermediate buffer per copy when `D3D12_FEATURE_DATA_D3D12_OPTIONS13.UnrestrictedBufferTextureCopyPitchSupported` is `false`. By @ErichDonGubler in [#7721](https://github.com/gfx-rs/wgpu/pull/7721), backported in [#8374](https://github.com/gfx-rs/wgpu/pull/8374). ## v27.0.2 (2025-10-03) ### Bug Fixes #### DX12 - Fix device creation failures for devices that do not support mesh shaders. By @vorporeal in [#8297](https://github.com/gfx-rs/wgpu/pull/8297). ## v27.0.1 (2025-10-02) ### Bug Fixes - Fixed the build on docs.rs. By @cwfitzgerald in [#8292](https://github.com/gfx-rs/wgpu/pull/8292). ## v27.0.0 (2025-10-01) ### Major Changes #### Deferred command buffer actions: `map_buffer_on_submit` and `on_submitted_work_done` You may schedule buffer mapping and a submission-complete callback to run automatically after you submit, directly from encoders, command buffers, and passes. ```rust // Record some GPU work so the submission isn't empty and touches `buffer`. encoder.clear_buffer(&buffer, 0, None); // Defer mapping until this encoder is submitted. encoder.map_buffer_on_submit(&buffer, wgpu::MapMode::Read, 0..size, |result| { .. }); // Fires after the command buffer's work is finished. encoder.on_submitted_work_done(|| { .. }); // Automatically calls `map_async` and `on_submitted_work_done` after this submission finishes. queue.submit([encoder.finish()]); ``` Available on `CommandEncoder`, `CommandBuffer`, `RenderPass`, and `ComputePass`. By @cwfitzgerald in [#8125](https://github.com/gfx-rs/wgpu/pull/8125). #### Builtin Support for DXGI swapchains on top of of DirectComposition Visuals in DX12 By enabling DirectComposition support, the dx12 backend can now support transparent windows. This creates a single `IDCompositionVisual` over the entire window that is used by the mf`Surface`. If a user wants to manage the composition tree themselves, they should create their own device and composition, and pass the relevant visual down into `wgpu` via `SurfaceTargetUnsafe::CompositionVisual`. ```rust let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backend_options: wgpu::BackendOptions { dx12: wgpu::Dx12BackendOptions { presentation_system: wgpu::Dx12SwapchainKind::DxgiFromVisual, .. }, .. }, .. }); ``` By @n1ght-hunter in [#7550](https://github.com/gfx-rs/wgpu/pull/7550). #### `EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE` has been merged into `EXPERIMENTAL_RAY_QUERY` We have merged the acceleration structure feature into the `RayQuery` feature. This is to help work around an AMD driver bug and reduce the feature complexity of ray tracing. In the future when ray tracing pipelines are implemented, if either feature is enabled, acceleration structures will be available. ```diff - Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE + Features::EXPERIMENTAL_RAY_QUERY ``` By @Vecvec in [#7913](https://github.com/gfx-rs/wgpu/pull/7913). #### New `EXPERIMENTAL_PRECOMPILED_SHADERS` API We have added `Features::EXPERIMENTAL_PRECOMPILED_SHADERS`, replacing existing passthrough types with a unified `CreateShaderModuleDescriptorPassthrough` which allows passing multiple shader codes for different backends. By @SupaMaggie70Incorporated in [#7834](https://github.com/gfx-rs/wgpu/pull/7834) Difference for SPIR-V passthrough: ```diff - device.create_shader_module_passthrough(wgpu::ShaderModuleDescriptorPassthrough::SpirV( - wgpu::ShaderModuleDescriptorSpirV { - label: None, - source: spirv_code, - }, - )) + device.create_shader_module_passthrough(wgpu::ShaderModuleDescriptorPassthrough { + entry_point: "main".into(), + label: None, + spirv: Some(spirv_code), + ..Default::default() }) ``` This allows using precompiled shaders without manually checking which backend's code to pass, for example if you have shaders precompiled for both DXIL and SPIR-V. #### Buffer mapping apis no longer have lifetimes `Buffer::get_mapped_range()`, `Buffer::get_mapped_range_mut()`, and `Queue::write_buffer_with()` now return guard objects without any lifetimes. This makes it significantly easier to store these types in structs, which is useful for building utilities that build the contents of a buffer over time. ```diff - let buffer_mapping_ref: wgpu::BufferView<'_> = buffer.get_mapped_range(..); - let buffer_mapping_mut: wgpu::BufferViewMut<'_> = buffer.get_mapped_range_mut(..); - let queue_write_with: wgpu::QueueWriteBufferView<'_> = queue.write_buffer_with(..); + let buffer_mapping_ref: wgpu::BufferView = buffer.get_mapped_range(..); + let buffer_mapping_mut: wgpu::BufferViewMut = buffer.get_mapped_range_mut(..); + let queue_write_with: wgpu::QueueWriteBufferView = queue.write_buffer_with(..); ``` By @sagudev in [#8046](https://github.com/gfx-rs/wgpu/pull/8046) and @cwfitzgerald in [#8070](https://github.com/gfx-rs/wgpu/pull/8161). #### `EXPERIMENTAL_*` features now require unsafe code to enable We want to be able to expose potentially experimental features to our users before we have ensured that they are fully sound to use. As such, we now require any feature that is prefixed with `EXPERIMENTAL` to have a special unsafe token enabled in the device descriptor acknowledging that the features may still have bugs in them and to report any they find. ```rust adapter.request_device(&wgpu::DeviceDescriptor { features: wgpu::Features::EXPERIMENTAL_MESH_SHADER, experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() } .. }) ``` By @cwfitzgerald in [#8163](https://github.com/gfx-rs/wgpu/pull/8163). #### Multi-draw indirect is now unconditionally supported when indirect draws are supported We have removed `Features::MULTI_DRAW_INDIRECT` as it was unconditionally available on all platforms. `RenderPass::multi_draw_indirect` is now available if the device supports downlevel flag `DownlevelFlags::INDIRECT_EXECUTION`. If you are using spirv-passthrough with multi-draw indirect and `gl_DrawID`, you can know if `MULTI_DRAW_INDIRECT` is being emulated by if the `Feature::MULTI_DRAW_INDIRECT_COUNT` feature is available on the device, this feature cannot be emulated efficicently. By @cwfitzgerald in [#8162](https://github.com/gfx-rs/wgpu/pull/8162). #### `wgpu::PollType::Wait` has now an optional timeout We removed `wgpu::PollType::WaitForSubmissionIndex` and added fields to `wgpu::PollType::Wait` in order to express timeouts. Before/after for `wgpu::PollType::Wait`: ```diff -device.poll(wgpu::PollType::Wait).unwrap(); -device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); +device.poll(wgpu::PollType::Wait { + submission_index: None, // Wait for most recent submission + timeout: Some(std::time::Duration::from_secs(60)), // Previous behavior, but more likely you want `None` instead. + }) + .unwrap(); ``` Before/after for `wgpu::PollType::WaitForSubmissionIndex`: ```diff -device.poll(wgpu::PollType::WaitForSubmissionIndex(index_to_wait_on)) +device.poll(wgpu::PollType::Wait { + submission_index: Some(index_to_wait_on), + timeout: Some(std::time::Duration::from_secs(60)), // Previous behavior, but more likely you want `None` instead. + }) + .unwrap(); ``` ⚠️ Previously, both `wgpu::PollType::WaitForSubmissionIndex` and `wgpu::PollType::Wait` had a hard-coded timeout of 60 seconds. To wait indefinitely on the latest submission, you can also use the `wait_indefinitely` convenience function: ```rust device.poll(wgpu::PollType::wait_indefinitely()); ``` By @wumpf in [#8282](https://github.com/gfx-rs/wgpu/pull/8282), [#8285](https://github.com/gfx-rs/wgpu/pull/8285) ### New Features #### General - Added mesh shader support to `wgpu`, with examples. Requires passthrough. By @SupaMaggie70Incorporated in [#7345](https://github.com/gfx-rs/wgpu/pull/7345). - Added support for external textures based on WebGPU's [`GPUExternalTexture`](https://www.w3.org/TR/webgpu/#gpuexternaltexture). These allow shaders to transparently operate on potentially multiplanar source texture data in either RGB or YCbCr formats via WGSL's `texture_external` type. This is gated behind the `Features::EXTERNAL_TEXTURE` feature, which is currently only supported on DX12. By @jamienicol in [#4386](https://github.com/gfx-rs/wgpu/issues/4386). - `wgpu::Device::poll` can now specify a timeout via `wgpu::PollType::Wait`. By @wumpf in [#8282](https://github.com/gfx-rs/wgpu/pull/8282) & [#8285](https://github.com/gfx-rs/wgpu/pull/8285) #### naga - Expose `naga::front::wgsl::UnimplementedEnableExtension`. By @ErichDonGubler in [#8237](https://github.com/gfx-rs/wgpu/pull/8237). ### Changes #### General - Command encoding now happens when `CommandEncoder::finish` is called, not when the individual operations are requested. This does not affect the API, but may affect performance characteristics. By @andyleiserson in [#8220](https://github.com/gfx-rs/wgpu/pull/8220). - Prevent resources for acceleration structures being created if acceleration structures are not enabled. By @Vecvec in [#8036](https://github.com/gfx-rs/wgpu/pull/8036). - Validate that each `push_debug_group` pairs with exactly one `pop_debug_group`. By @andyleiserson in [#8048](https://github.com/gfx-rs/wgpu/pull/8048). - `set_viewport` now requires that the supplied minimum depth value is less than the maximum depth value. By @andyleiserson in [#8040](https://github.com/gfx-rs/wgpu/pull/8040). - Validation of `copy_texture_to_buffer`, `copy_buffer_to_texture`, and `copy_texture_to_texture` operations more closely follows the WebGPU specification. By @andyleiserson in various PRs. - Copies within the same texture must not overlap. - Copies of multisampled or depth/stencil formats must span an entire subresource (layer). - Copies of depth/stencil formats must be 4B aligned. - For texture-buffer copies, `bytes_per_row` on the buffer side must be 256B-aligned, even if the transfer is a single row. - The offset for `set_vertex_buffer` and `set_index_buffer` must be 4B aligned. By @andyleiserson in [#7929](https://github.com/gfx-rs/wgpu/pull/7929). - The offset and size of bindings are validated as fitting within the underlying buffer in more cases. By @andyleiserson in [#7911](https://github.com/gfx-rs/wgpu/pull/7911). - The function you pass to `Device::on_uncaptured_error()` must now implement `Sync` in addition to `Send`, and be wrapped in `Arc` instead of `Box`. In exchange for this, it is no longer possible for calling `wgpu` functions while in that callback to cause a deadlock (not that we encourage you to actually do that). By @kpreid in [#8011](https://github.com/gfx-rs/wgpu/pull/8011). - Make a compacted hal acceleration structure inherit a label from the base BLAS. By @Vecvec in [#8103](https://github.com/gfx-rs/wgpu/pull/8103). - The limits requested for a device must now satisfy `min_subgroup_size <= max_subgroup_size`. By @andyleiserson in [#8085](https://github.com/gfx-rs/wgpu/pull/8085). - Improve errors when buffer mapping is done incorrectly. Allow aliasing immutable [`BufferViews`]. By @cwfitzgerald in [#8150](https://github.com/gfx-rs/wgpu/pull/8150). - Require new `F16_IN_F32` downlevel flag for `quantizeToF16`, `pack2x16float`, and `unpack2x16float` in WGSL input. By @aleiserson in [#8130](https://github.com/gfx-rs/wgpu/pull/8130). - The error message for non-copyable depth/stencil formats no longer mentions the aspect when it is not relevant. By @reima in [#8156](https://github.com/gfx-rs/wgpu/pull/8156). - Track the initialization status of buffer memory correctly when `copy_texture_to_buffer` skips over padding space between rows or layers, or when the start/end of a texture-buffer transfer is not 4B aligned. By @andyleiserson in [#8099](https://github.com/gfx-rs/wgpu/pull/8099). #### naga - naga now requires that no type be larger than 1 GB. This limit may be lowered in the future; feedback on an appropriate value for the limit is welcome. By @andyleiserson in [#7950](https://github.com/gfx-rs/wgpu/pull/7950). - If the shader source contains control characters, naga now replaces them with U+FFFD ("replacement character") in diagnostic output. By @andyleiserson in [#8049](https://github.com/gfx-rs/wgpu/pull/8049). - Add f16 IO polyfill on Vulkan backend to enable SHADER_F16 use without requiring `storageInputOutput16`. By @cryvosh in [#7884](https://github.com/gfx-rs/wgpu/pull/7884). - For custom Naga backend authors: `naga::proc::Namer` now accepts reserved keywords using two new dedicated types, `proc::{KeywordSet, CaseInsensitiveKeywordSet}`. By @kpreid in [#8136](https://github.com/gfx-rs/wgpu/pull/8136). - **BREAKING**: Previously the WGSL storage-texture format `rg11b10float` was incorrectly accepted and generated by naga, but now only accepts the the correct name `rg11b10ufloat` instead. By @ErikWDev in [#8219](https://github.com/gfx-rs/wgpu/pull/8219). - The [`source()`](https://doc.rust-lang.org/std/error/trait.Error.html#method.source) method of `ShaderError` no longer reports the error as its own source. By @andyleiserson in [#8258](https://github.com/gfx-rs/wgpu/pull/8258). - naga correctly ingests SPIR-V that use descriptor runtime indexing, which in turn is correctly converted into WGSLs binding array. By @hasenbanck in [8256](https://github.com/gfx-rs/wgpu/pull/8256). - naga correctly ingests SPIR-V that loads from multi-sampled textures, which in turn is correctly converted into WGSLs texture_multisampled_2d and load operations. By @hasenbanck in [8270](https://github.com/gfx-rs/wgpu/pull/8270). - naga implement OpImageGather and OpImageDrefGather operations when ingesting SPIR-V. By @hasenbanck in [8280](https://github.com/gfx-rs/wgpu/pull/8280). #### DX12 - Allow disabling waiting for latency waitable object. By @marcpabst in [#7400](https://github.com/gfx-rs/wgpu/pull/7400) - Add mesh shader support, including to the example. By @SupaMaggie70Incorporated in [#8110](https://github.com/gfx-rs/wgpu/issues/8110) ### Bug Fixes #### General - Validate that effective buffer binding size is aligned to 4 when creating bind groups with buffer entries.. By @ErichDonGubler in [8041](https://github.com/gfx-rs/wgpu/pull/8041). #### DX12 - Create an event per wait to prevent 60 second hangs in certain multithreaded scenarios. By @Vecvec in [#8273](https://github.com/gfx-rs/wgpu/pull/8273). - Fixed a bug where access to matrices with 2 rows would not work in some cases. By @andyleiserson in [#7438](https://github.com/gfx-rs/wgpu/pull/7438). ##### EGL - Fixed unwrap failed in context creation for some Android devices. By @uael in [#8024](https://github.com/gfx-rs/wgpu/pull/8024). ##### Vulkan - Fixed wrong color format+space being reported versus what is hardcoded in `create_swapchain()`. By @MarijnS95 in [#8226](https://github.com/gfx-rs/wgpu/pull/8226). #### naga - [wgsl-in] Allow a trailing comma in `@blend_src(…)` attributes. By @ErichDonGubler in [#8137](https://github.com/gfx-rs/wgpu/pull/8137). - [wgsl-in] Allow a trailing comma in the list of `case` values inside a `switch`. By @reima in [#8165](https://github.com/gfx-rs/wgpu/pull/8165). - Escape, rather than strip, identifiers with Unicode. By @ErichDonGubler in [7995](https://github.com/gfx-rs/wgpu/pull/7995). ### Documentation #### General - Clarify that subgroup barriers require both the `SUBGROUP` and `SUBGROUP_BARRIER` features / capabilities. By @andyleiserson in [#8203](https://github.com/gfx-rs/wgpu/pull/8203). # v26.0.6 (2025-10-23) This release includes `wgpu-hal` version `26.0.6`. All other crates remain at their previous versions. ### Bug Fixes #### Vulkan - Work around extremely poor frame pacing from AMD and Nvidia cards on Windows in `Fifo` and `FifoRelaxed` present modes. This is due to the drivers implicitly using a DXGI (Direct3D) swapchain to implement these modes and it having vastly different timing properties. See https://github.com/gfx-rs/wgpu/issues/8310 and https://github.com/gfx-rs/wgpu/issues/8354 for more information. By @cwfitzgerald in [#8420](https://github.com/gfx-rs/wgpu/pull/8420). ## v26.0.5 (2025-10-21) This release includes `wgpu-hal` version `26.0.5`. All other crates remain at their previous versions. ### Bug Fixes #### DX12 - Align copies b/w textures and buffers via a single intermediate buffer per copy when `D3D12_FEATURE_DATA_D3D12_OPTIONS13.UnrestrictedBufferTextureCopyPitchSupported` is `false`. By @ErichDonGubler in [#7721](https://github.com/gfx-rs/wgpu/pull/7721), backported in [#8375](https://github.com/gfx-rs/wgpu/pull/8375). ## v26.0.4 (2025-08-07) ### Bug Fixes #### Vulkan - Fix `STATUS_HEAP_CORRUPTION` crash when concurrently calling `create_sampler`. By @atlv24 in [#8043](https://github.com/gfx-rs/wgpu/pull/8043), [#8056](https://github.com/gfx-rs/wgpu/pull/8056). ## v26.0.3 (2025-07-30) ### Bug Fixes - Fixed memory leak in vulkan backend. By @cwfitzgerald in [#8031](https://github.com/gfx-rs/wgpu/pull/8031). ### Bug Fixes #### naga - Fix empty `if` statements causing errors on spirv 1.6+. By @Vecvec in [#7883](https://github.com/gfx-rs/wgpu/pull/7883). ## v26.0.2 (2025-07-23) ### Bug Fixes - Fixed vulkan validation error regarding the swapchain in latest SDK. By @cwfitzgerald in [#7971](https://github.com/gfx-rs/wgpu/pull/7971). - Fixed flickering on AMD devices and crashes inside Renderdoc due to incorrect caching of `VkFramebuffer`s when the driver re-used image view handles. By @cwfitzgerald in [#7972](https://github.com/gfx-rs/wgpu/pull/7972). > [!WARNING] > There is formally a breaking change in `wgpu_hal::vulkan::Device::texture_from_raw` as there is now a `&self` receiver where > there previously wasn't one. This will not affect you unless you explicitly use this api. We have gone ahead with the release > as the bug was pervasive and made wgpu unusable for the affected people on v26. ## v26.0.1 (2025-07-10) ### Bug Fixes - Fixed build error inside `wgpu::util::initialize_adapter_from_env` when `std` feature is not enabled. By @kpreid in [#7918](https://github.com/gfx-rs/wgpu/pull/7918). - Fixed build error occurring when the `profiling` dependency is configured to have profiling active. By @kpreid in [#7916](https://github.com/gfx-rs/wgpu/pull/7916). - Emit a validation error instead of panicking when a query set index is OOB. By @ErichDonGubler in [#7908](https://github.com/gfx-rs/wgpu/pull/7908). ## v26.0.0 (2025-07-09) ### Major Features #### New method `TextureView::texture` You can now call `texture_view.texture()` to get access to the texture that a given texture view points to. By @cwfitzgerald and @Wumpf in [#7907](https://github.com/gfx-rs/wgpu/pull/7907). #### `as_hal` calls now return guards instead of using callbacks. Previously, if you wanted to get access to the wgpu-hal or underlying api types, you would call `as_hal` and get the hal type as a callback. Now the function returns a guard which dereferences to the hal type. ```diff - device.as_hal::(|hal_device| {...}); + let hal_device: impl Deref = device.as_hal::(); ``` By @cwfitzgerald in [#7863](https://github.com/gfx-rs/wgpu/pull/7863). #### Enabling Vulkan Features/Extensions For those who are doing vulkan/wgpu interop or passthrough and need to enable features/extensions that wgpu does not expose, there is a new `wgpu_hal::vulkan::Adapter::open_with_callback` that allows the user to modify the pnext chains and extension lists populated by wgpu before we create a vulkan device. This should vastly simplify the experience, as previously you needed to create a device yourself. Underlying api interop is a quickly evolving space, so we welcome all feedback! ```rust type VkApi = wgpu::hal::api::Vulkan; let adapter: wgpu::Adapter = ...; let mut buffer_device_address_create_info = ash::vk::PhysicalDeviceBufferDeviceAddressFeatures { .. }; let hal_device: wgpu::hal::OpenDevice = adapter .as_hal::() .unwrap() .open_with_callback( wgpu::Features::empty(), &wgpu::MemoryHints::Performance, Some(Box::new(|args| { // Add the buffer device address extension. args.extensions.push(ash::khr::buffer_device_address::NAME); // Extend the create info with the buffer device address create info. *args.create_info = args .create_info .push_next(&mut buffer_device_address_create_info); // We also have access to the queue create infos if we need them. let _ = args.queue_create_infos; })), ) .unwrap(); let (device, queue) = adapter .create_device_from_hal(hal_device, &wgpu::DeviceDescriptor { .. }) .unwrap(); ``` By @Vecvec in [#7829](https://github.com/gfx-rs/wgpu/pull/7829). ### naga - Added `no_std` support with default features disabled. By @Bushrat011899 in [#7585](https://github.com/gfx-rs/wgpu/pull/7585). - [wgsl-in,ir] Add support for parsing rust-style doc comments via `naga::front::glsl::Frontend::new_with_options`. By @Vrixyz in [#6364](https://github.com/gfx-rs/wgpu/pull/6364). - When emitting GLSL, Uniform and Storage Buffer memory layouts are now emitted even if no explicit binding is given. By @cloone8 in [#7579](https://github.com/gfx-rs/wgpu/pull/7579). - Diagnostic rendering methods (i.e., `naga::{front::wgsl::ParseError,WithSpan}::emit_error_to_string_with_path`) now accept more types for their `path` argument via a new sealed `AsDiagnosticFilePath` trait. By @atlv24, @bushrat011899, and @ErichDonGubler in [#7643](https://github.com/gfx-rs/wgpu/pull/7643). - Add support for [quad operations](https://www.w3.org/TR/WGSL/#quad-builtin-functions) (requires `SUBGROUP` feature to be enabled). By @dzamkov and @valaphee in [#7683](https://github.com/gfx-rs/wgpu/pull/7683). - Add support for `atomicCompareExchangeWeak` in HLSL and GLSL backends. By @cryvosh in [#7658](https://github.com/gfx-rs/wgpu/pull/7658) ### General - Add support for astc-sliced-3d feature. By @mehmetoguzderin in [#7577](https://github.com/gfx-rs/wgpu/issues/7577) - Added `wgpu_hal::dx12::Adapter::as_raw()`. By @tronical in [##7852](https://github.com/gfx-rs/wgpu/pull/7852) - Add support for rendering to slices of 3D texture views and single layered 2D-Array texture views (this requires `VK_KHR_maintenance1` which should be widely available on newer drivers). By @teoxoy in [#7596](https://github.com/gfx-rs/wgpu/pull/7596) - Add extra acceleration structure vertex formats. By @Vecvec in [#7580](https://github.com/gfx-rs/wgpu/pull/7580). - Add acceleration structure limits. By @Vecvec in [#7845](https://github.com/gfx-rs/wgpu/pull/7845). - Add support for clip-distances feature for Vulkan and GL backends. By @dzamkov in [#7730](https://github.com/gfx-rs/wgpu/pull/7730) - Added `wgpu_types::error::{ErrorType, WebGpuError}` for classification of errors according to WebGPU's [`GPUError`]'s classification scheme, and implement `WebGpuError` for existing errors. This allows users of `wgpu-core` to offload error classification onto the wgpu ecosystem, rather than having to do it themselves without sufficient information. By @ErichDonGubler in [#6547](https://github.com/gfx-rs/wgpu/pull/6547). [`GPUError`]: https://www.w3.org/TR/webgpu/#gpuerror ### Bug Fixes #### General - Fix error message for sampler array limit. By @LPGhatguy in [#7704](https://github.com/gfx-rs/wgpu/pull/7704). - Fix bug where using `BufferSlice::get_mapped_range_as_array_buffer()` on a buffer would prevent you from ever unmapping it. Note that this API has changed and is now `BufferView::as_uint8array()`. #### naga - naga now infers the correct binding layout when a resource appears only in an assignment to `_`. By @andyleiserson in [#7540](https://github.com/gfx-rs/wgpu/pull/7540). - Implement `dot4U8Packed` and `dot4I8Packed` for all backends, using specialized intrinsics on SPIR-V, HLSL, and Metal if available, and polyfills everywhere else. By @robamler in [#7494](https://github.com/gfx-rs/wgpu/pull/7494), [#7574](https://github.com/gfx-rs/wgpu/pull/7574), and [#7653](https://github.com/gfx-rs/wgpu/pull/7653). - Add polyfilled `pack4x{I,U}8Clamped` built-ins to all backends and WGSL frontend. By @ErichDonGubler in [#7546](https://github.com/gfx-rs/wgpu/pull/7546). - Allow textureLoad's sample index arg to be unsigned. By @jimblandy in [#7625](https://github.com/gfx-rs/wgpu/pull/7625). - Properly convert arguments to atomic operations. By @jimblandy in [#7573](https://github.com/gfx-rs/wgpu/pull/7573). - Apply necessary automatic conversions to the `value` argument of `textureStore`. By @jimblandy in [#7567](https://github.com/gfx-rs/wgpu/pull/7567). - Properly apply WGSL's automatic conversions to the arguments to texture sampling functions. By @jimblandy in [#7548](https://github.com/gfx-rs/wgpu/pull/7548). - Properly evaluate `abs(most negative abstract int)`. By @jimblandy in [#7507](https://github.com/gfx-rs/wgpu/pull/7507). - Generate vectorized code for `[un]pack4x{I,U}8[Clamp]` on SPIR-V and MSL 2.1+. By @robamler in [#7664](https://github.com/gfx-rs/wgpu/pull/7664). - Fix typing for `select`, which had issues particularly with a lack of automatic type conversion. By @ErichDonGubler in [#7572](https://github.com/gfx-rs/wgpu/pull/7572). - Allow scalars as the first argument of the `distance` built-in function. By @bernhl in [#7530](https://github.com/gfx-rs/wgpu/pull/7530). - Don't panic when handling `f16` for pipeline constants, i.e., `override`s in WGSL. By @ErichDonGubler in [#7801](https://github.com/gfx-rs/wgpu/pull/7801). - Prevent aliased ray queries crashing naga when writing SPIR-V out. By @Vecvec in [#7759](https://github.com/gfx-rs/wgpu/pull/7759). #### DX12 - Get `vertex_index` & `instance_index` builtins working for indirect draws. By @teoxoy in [#7535](https://github.com/gfx-rs/wgpu/pull/7535) #### Vulkan - Fix OpenBSD compilation of `wgpu_hal::vulkan::drm`. By @ErichDonGubler in [#7810](https://github.com/gfx-rs/wgpu/pull/7810). - Fix warnings for unrecognized present mode. By @Wumpf in [#7850](https://github.com/gfx-rs/wgpu/pull/7850). #### Metal - Remove extraneous main thread warning in `fn surface_capabilities()`. By @jamesordner in [#7692](https://github.com/gfx-rs/wgpu/pull/7692) #### WebGPU - Fix setting unclipped_depth. By @atlv24 in [#7841](https://github.com/gfx-rs/wgpu/pull/7841) - Implement `on_submitted_work_done` for WebGPU backend. By @drewcrawford in [#7864](https://github.com/gfx-rs/wgpu/pull/7864) ### Changes - Loosen Viewport validation requirements to match the [new specs](https://github.com/gpuweb/gpuweb/pull/5025). By @ebbdrop in [#7564](https://github.com/gfx-rs/wgpu/pull/7564) - `wgpu` and `deno_webgpu` now use `wgpu-types::error::WebGpuError` to classify errors. Any changes here are likely to be regressions; please report them if you find them! By @ErichDonGubler in [#6547](https://github.com/gfx-rs/wgpu/pull/6547). #### General - Support BLAS compaction in wgpu. By @Vecvec in [#7285](https://github.com/gfx-rs/wgpu/pull/7285). - Removed `MaintainBase` in favor of using `PollType`. By @waywardmonkeys in [#7508](https://github.com/gfx-rs/wgpu/pull/7508). - The `destroy` functions for buffers and textures in wgpu-core are now infallible. Previously, they returned an error if called multiple times for the same object. This only affects the wgpu-core API; the wgpu API already allowed multiple `destroy` calls. By @andyleiserson in [#7686](https://github.com/gfx-rs/wgpu/pull/7686) and [#7720](https://github.com/gfx-rs/wgpu/pull/7720). - Remove `CommandEncoder::build_acceleration_structures_unsafe_tlas` in favour of `as_hal` and apply simplifications allowed by this. By @Vecvec in [#7513](https://github.com/gfx-rs/wgpu/pull/7513) - The type of the `size` parameter to `copy_buffer_to_buffer` has changed from `BufferAddress` to `impl Into>`. This achieves the spec-defined behavior of the value being optional, while still accepting existing calls without changes. By @andyleiserson in [#7659](https://github.com/gfx-rs/wgpu/pull/7659). - To bring wgpu's error reporting into compliance with the WebGPU specification, the error type returned from some functions has changed, and some errors may be raised at a different time than they were previously. - The error type returned by many methods on `CommandEncoder`, `RenderPassEncoder`, `ComputePassEncoder`, and `RenderBundleEncoder` has changed to `EncoderStateError` or `PassStateError`. These functions will return the `Ended` variant of these errors if called on an encoder that is no longer active. Reporting of all other errors is deferred until a call to `finish()`. - Variants holding a `CommandEncoderError` in the error enums `ClearError`, `ComputePassErrorInner`, `QueryError`, and `RenderPassErrorInner` have been replaced with variants holding an `EncoderStateError`. - The definition of `enum CommandEncoderError` has changed significantly, to reflect which errors can be raised by `CommandEncoder.finish()`. There are also some errors that no longer appear directly in `CommandEncoderError`, and instead appear nested within the `RenderPass` or `ComputePass` variants. - `CopyError` has been removed. Errors that were previously a `CopyError` are now a `CommandEncoderError` returned by `finish()`. (The detailed reasons for copies to fail were and still are described by `TransferError`, which was previously a variant of `CopyError`, and is now a variant of `CommandEncoderError`). #### naga - Mark `readonly_and_readwrite_storage_textures` & `packed_4x8_integer_dot_product` language extensions as implemented. By @teoxoy in [#7543](https://github.com/gfx-rs/wgpu/pull/7543) - `naga::back::hlsl::Writer::new` has a new `pipeline_options` argument. `hlsl::PipelineOptions::default()` can be passed as a default. The `shader_stage` and `entry_point` members of `pipeline_options` can be used to write only a single entry point when using the HLSL and MSL backends (GLSL and SPIR-V already had this functionality). The Metal and DX12 HALs now write only a single entry point when loading shaders. By @andyleiserson in [#7626](https://github.com/gfx-rs/wgpu/pull/7626). - Implemented `early_depth_test` for SPIR-V backend, enabling `SHADER_EARLY_DEPTH_TEST` for Vulkan. Additionally, fixed conservative depth optimizations when using `early_depth_test`. The syntax for forcing early depth tests is now `@early_depth_test(force)` instead of `@early_depth_test`. By @dzamkov in [#7676](https://github.com/gfx-rs/wgpu/pull/7676). - `ImplementedLanguageExtension::VARIANTS` is now implemented manually rather than derived using `strum` (allowing `strum` to become a dev-only dependency) so it is no longer a member of the `strum::VARIANTS` trait. Unless you are using this trait as a bound this should have no effect. - Compaction changes, by @andyleiserson in [#7703](https://github.com/gfx-rs/wgpu/pull/7703): - [`process_overrides`](https://docs.rs/naga/latest/naga/back/pipeline_constants/fn.process_overrides.html) now compacts the module to remove unused items. It is no longer necessary to supply values for overrides that are not used by the active entry point. - The `compact` Cargo feature has been removed. It is no longer possible to exclude compaction support from the build. - [`compact`](https://docs.rs/naga/latest/naga/compact/fn.compact.html) now has an additional argument that specifies whether to remove unused functions, globals, and named types and overrides. For the previous behavior, pass `KeepUnused::Yes`. #### D3D12 - Remove the need for dxil.dll. By @teoxoy in [#7566](https://github.com/gfx-rs/wgpu/pull/7566) - Ability to get the raw `IDXGIFactory4` from `Instance`. By @MendyBerger in [#7827](https://github.com/gfx-rs/wgpu/pull/7827) #### Vulkan - Use highest SPIR-V version supported by Vulkan API version. By @robamler in [#7595](https://github.com/gfx-rs/wgpu/pull/7595) #### HAL - Added initial `no_std` support to `wgpu-hal`. By @bushrat011899 in [#7599](https://github.com/gfx-rs/wgpu/pull/7599) ### Documentation #### General - Remove outdated information about `Adapter::request_device`. By @tesselode in [#7768](https://github.com/gfx-rs/wgpu/pull/7768) ## v25.0.2 (2025-05-24) ### Bug Fixes #### General - Fix a possible deadlock within `Queue::write_buffer`. By @RedMindZ in [#7582](https://github.com/gfx-rs/wgpu/pull/7582) - Fix `raw-window-handle` dependency being too lenient. By @kpreid in [#7526](https://github.com/gfx-rs/wgpu/pull/7526) #### WebGPU - Insert fragment pipeline constants into fragment descriptor instead of vertex descriptor. By @DerSchmale in [#7621](https://github.com/gfx-rs/wgpu/pull/7621) ## v25.0.1 (2025-04-11) ### Bug Fixes - Fix typos in various documentation. By @waywardmonkeys in [#7510](https://github.com/gfx-rs/wgpu/pull/7510). - Fix compile error when building with `profiling/profile-with-*` feature enabled. By @waywardmonkeys in [#7509](https://github.com/gfx-rs/wgpu/pull/7509). - Use `once_cell::race::OnceBox` instead of `std::sync::LazyLock` to allow `naga::proc::Namer::default()` to be available without backend features being enabled. By @cwfitzgerald in [#7517](https://github.com/gfx-rs/wgpu/pull/7517). #### DX12 - Fix validation error when creating a non-mappable buffer using the committed allocation scheme. By @cwfitzgerald and @ErichDonGubler in [#7519](https://github.com/gfx-rs/wgpu/pull/7519). ## v25.0.0 (2025-04-10) ### Major Features #### Hashmaps Removed from APIs Both `PipelineCompilationOptions::constants` and `ShaderSource::Glsl::defines` now take slices of key-value pairs instead of `hashmap`s. This is to prepare for `no_std` support and allow us to keep which `hashmap` hasher and such as implementation details. It also allows more easily creating these structures inline. By @cwfitzgerald in [#7133](https://github.com/gfx-rs/wgpu/pull/7133) #### All Backends Now Have Features Previously, the `vulkan` and `gles` backends were non-optional on windows, linux, and android and there was no way to disable them. We have now figured out how to properly make them disablable! Additionally, if you turn on the `webgl` feature, you will only get the GLES backend on WebAssembly, it won't leak into native builds, like previously it might have. > [!WARNING] > If you use wgpu with `default-features = false` and you want to retain the `vulkan` and `gles` backends, you will need to add them to your feature list. > > ```diff > -wgpu = { version = "24", default-features = false, features = ["metal", "wgsl", "webgl"] } > +wgpu = { version = "25", default-features = false, features = ["metal", "wgsl", "webgl", "vulkan", "gles"] } > ``` By @cwfitzgerald in [#7076](https://github.com/gfx-rs/wgpu/pull/7076). #### `device.poll` Api Reworked This release reworked the poll api significantly to allow polling to return errors when polling hits internal timeout limits. `Maintain` was renamed `PollType`. Additionally, `poll` now returns a result containing information about what happened during the poll. ```diff -pub fn wgpu::Device::poll(&self, maintain: wgpu::Maintain) -> wgpu::MaintainResult +pub fn wgpu::Device::poll(&self, poll_type: wgpu::PollType) -> Result -device.poll(wgpu::Maintain::Poll); +device.poll(wgpu::PollType::Poll).unwrap(); ``` ```rust pub enum PollType { /// On wgpu-core based backends, block until the given submission has /// completed execution, and any callbacks have been invoked. /// /// On WebGPU, this has no effect. Callbacks are invoked from the /// window event loop. WaitForSubmissionIndex(T), /// Same as WaitForSubmissionIndex but waits for the most recent submission. Wait, /// Check the device for a single time without blocking. Poll, } pub enum PollStatus { /// There are no active submissions in flight as of the beginning of the poll call. /// Other submissions may have been queued on other threads during the call. /// /// This implies that the given Wait was satisfied before the timeout. QueueEmpty, /// The requested Wait was satisfied before the timeout. WaitSucceeded, /// This was a poll. Poll, } pub enum PollError { /// The requested Wait timed out before the submission was completed. Timeout, } ``` > [!WARNING] > As part of this change, WebGL's default behavior has changed. Previously `device.poll(Wait)` appeared as though it functioned correctly. This was a quirk caused by the bug that these PRs fixed. Now it will always return `Timeout` if the submission has not already completed. As many people rely on this behavior on WebGL, there is a new options in `BackendOptions`. If you want the old behavior, set the following on instance creation: > > ```rust > instance_desc.backend_options.gl.fence_behavior = wgpu::GlFenceBehavior::AutoFinish; > ``` > > You will lose the ability to know exactly when a submission has completed, but `device.poll(Wait)` will behave the same as it does on native. By @cwfitzgerald in [#6942](https://github.com/gfx-rs/wgpu/pull/6942) and [#7030](https://github.com/gfx-rs/wgpu/pull/7030). #### `wgpu::Device::start_capture` renamed, documented, and made unsafe ```diff - device.start_capture(); + unsafe { device.start_graphics_debugger_capture() } // Your code here - device.stop_capture(); + unsafe { device.stop_graphics_debugger_capture() } ``` There is now documentation to describe how this maps to the various debuggers' apis. By @cwfitzgerald in [#7470](https://github.com/gfx-rs/wgpu/pull/7470) ##### Ensure loops generated by SPIR-V and HLSL naga backends are bounded Make sure that all loops in shaders generated by these naga backends are bounded to avoid undefined behaviour due to infinite loops. Note that this may have a performance cost. As with the existing implementation for the MSL backend this can be disabled by using `Device::create_shader_module_trusted()`. By @jamienicol in [#6929](https://github.com/gfx-rs/wgpu/pull/6929) and [#7080](https://github.com/gfx-rs/wgpu/pull/7080). #### Split up `Features` internally Internally split up the `Features` struct and recombine them internally using a macro. There should be no breaking changes from this. This means there are also namespaces (as well as the old `Features::*`) for all wgpu specific features and webgpu feature (`FeaturesWGPU` and `FeaturesWebGPU` respectively) and `Features::from_internal_flags` which allow you to be explicit about whether features you need are available on the web too. By @Vecvec in [#6905](https://github.com/gfx-rs/wgpu/pull/6905), [#7086](https://github.com/gfx-rs/wgpu/pull/7086) #### WebGPU compliant dual source blending feature Previously, dual source blending was implemented with a `wgpu` native only feature flag and used a custom syntax in wgpu. By now, dual source blending was added to the [WebGPU spec as an extension](https://www.w3.org/TR/webgpu/#dom-gpufeaturename-dual-source-blending). We're now following suite and implement the official syntax. Existing shaders using dual source blending need to be updated: ```diff struct FragmentOutput{ - @location(0) source0: vec4, - @location(0) @second_blend_source source1: vec4, + @location(0) @blend_src(0) source0: vec4, + @location(0) @blend_src(1) source1: vec4, } ``` With that `wgpu::Features::DUAL_SOURCE_BLENDING` is now available on WebGPU. Furthermore, GLSL shaders now support dual source blending as well via the `index` layout qualifier: ```c layout(location = 0, index = 0) out vec4 output0; layout(location = 0, index = 1) out vec4 output1; ``` By @wumpf in [#7144](https://github.com/gfx-rs/wgpu/pull/7144) #### Unify interface for SpirV shader passthrough Replace device `create_shader_module_spirv` function with a generic `create_shader_module_passthrough` function taking a `ShaderModuleDescriptorPassthrough` enum as parameter. Update your calls to `create_shader_module_spirv` and use `create_shader_module_passthrough` instead: ```diff - device.create_shader_module_spirv( - wgpu::ShaderModuleDescriptorSpirV { - label: Some(&name), - source: Cow::Borrowed(&source), - } - ) + device.create_shader_module_passthrough( + wgpu::ShaderModuleDescriptorPassthrough::SpirV( + wgpu::ShaderModuleDescriptorSpirV { + label: Some(&name), + source: Cow::Borrowed(&source), + }, + ), + ) ``` By @syl20bnr in [#7326](https://github.com/gfx-rs/wgpu/pull/7326). #### Noop Backend It is now possible to create a dummy `wgpu` device even when no GPU is available. This may be useful for testing of code which manages graphics resources. Currently, it supports reading and writing buffers, and other resource types can be created but do nothing. To use it, enable the `noop` feature of `wgpu`, and either call `Device::noop()`, or add `NoopBackendOptions { enable: true }` to the backend options of your `Instance` (this is an additional safeguard beyond the `Backends` bits). By @kpreid in [#7063](https://github.com/gfx-rs/wgpu/pull/7063) and [#7342](https://github.com/gfx-rs/wgpu/pull/7342). #### `SHADER_F16` feature is now available with naga shaders Previously this feature only allowed you to use `f16` on SPIR-V passthrough shaders. Now you can use it on all shaders, including WGSL, SPIR-V, and GLSL! ```wgsl enable f16; fn hello_world(a: f16) -> f16 { return a + 1.0h; } ``` By @FL33TW00D, @ErichDonGubler, and @cwfitzgerald in [#5701](https://github.com/gfx-rs/wgpu/pull/5701) #### Bindless support improved and validation rules changed. Metal support for bindless has significantly improved and the limits for binding arrays have been increased. Previously, all resources inside binding arrays contributed towards the standard limit of their type (`texture_2d` arrays for example would contribute to `max_sampled_textures_per_shader_stage`). Now these resources will only contribute towards binding-array specific limits: - `max_binding_array_elements_per_shader_stage` for all non-sampler resources - `max_binding_array_sampler_elements_per_shader_stage` for sampler resources. This change has allowed the metal binding array limits to go from between 32 and 128 resources, all the way 500,000 sampled textures. Additionally binding arrays are now bound more efficiently on Metal. This change also enabled legacy Intel GPUs to support 1M bindless resources, instead of the previous 1800. To facilitate this change, there was an additional validation rule put in place: if there is a binding array in a bind group, you may not use dynamic offset buffers or uniform buffers in that bind group. This requirement comes from vulkan rules on `UpdateAfterBind` descriptors. By @cwfitzgerald in [#6811](https://github.com/gfx-rs/wgpu/pull/6811), [#6815](https://github.com/gfx-rs/wgpu/pull/6815), and [#6952](https://github.com/gfx-rs/wgpu/pull/6952). ### New Features #### General - Add `Buffer` methods corresponding to `BufferSlice` methods, so you can skip creating a `BufferSlice` when it offers no benefit, and `BufferSlice::slice()` for sub-slicing a slice. By @kpreid in [#7123](https://github.com/gfx-rs/wgpu/pull/7123). - Add `BufferSlice::buffer()`, `BufferSlice::offset()` and `BufferSlice::size()`. By @kpreid in [#7148](https://github.com/gfx-rs/wgpu/pull/7148). - Add `impl From for BufferBinding` and `impl From for BindingResource`, allowing `BufferSlice`s to be easily used in creating bind groups. By @kpreid in [#7148](https://github.com/gfx-rs/wgpu/pull/7148). - Add `util::StagingBelt::allocate()` so the staging belt can be used to write textures. By @kpreid in [#6900](https://github.com/gfx-rs/wgpu/pull/6900). - Added `CommandEncoder::transition_resources()` for native API interop, and allowing users to slightly optimize barriers. By @JMS55 in [#6678](https://github.com/gfx-rs/wgpu/pull/6678). - Add `wgpu_hal::vulkan::Adapter::texture_format_as_raw` for native API interop. By @JMS55 in [#7228](https://github.com/gfx-rs/wgpu/pull/7228). - Support getting vertices of the hit triangle when raytracing. By @Vecvec in [#7183](https://github.com/gfx-rs/wgpu/pull/7183). - Add `as_hal` for both acceleration structures. By @Vecvec in [#7303](https://github.com/gfx-rs/wgpu/pull/7303). - Add Metal compute shader passthrough. Use `create_shader_module_passthrough` on device. By @syl20bnr in [#7326](https://github.com/gfx-rs/wgpu/pull/7326). - new `Features::MSL_SHADER_PASSTHROUGH` run-time feature allows providing pass-through MSL Metal shaders. By @syl20bnr in [#7326](https://github.com/gfx-rs/wgpu/pull/7326). - Added mesh shader support to `wgpu_hal`. By @SupaMaggie70Incorporated in [#7089](https://github.com/gfx-rs/wgpu/pull/7089) #### naga - Add support for unsigned types when calling textureLoad with the level parameter. By @ygdrasil-io in [#7058](https://github.com/gfx-rs/wgpu/pull/7058). - Support @must_use attribute on function declarations. By @turbocrime in [#6801](https://github.com/gfx-rs/wgpu/pull/6801). - Support for generating the candidate intersections from AABB geometry, and confirming the hits. By @kvark in [#7047](https://github.com/gfx-rs/wgpu/pull/7047). - Make naga::back::spv::Function::to_words write the OpFunctionEnd instruction in itself, instead of making another call after it. By @junjunjd in [#7156](https://github.com/gfx-rs/wgpu/pull/7156). - Add support for texture memory barriers. By @Devon7925 in [#7173](https://github.com/gfx-rs/wgpu/pull/7173). - Add polyfills for `unpackSnorm4x8`, `unpackUnorm4x8`, `unpackSnorm2x16`, `unpackUnorm2x16` for GLSL versions they aren't supported in. By @DJMcNab in [#7408](https://github.com/gfx-rs/wgpu/pull/7408). #### Examples - Added an example that shows how to handle datasets too large to fit in a single `GPUBuffer` by distributing it across many buffers, and then having the shader receive them as a `binding_array` of storage buffers. By @alphastrata in [#6138](https://github.com/gfx-rs/wgpu/pull/6138) ### Changes #### General - `wgpu::Instance::request_adapter()` now returns `Result` instead of `Option`; the error provides information about why no suitable adapter was returned. By @kpreid in [#7330](https://github.com/gfx-rs/wgpu/pull/7330). - Support BLAS compaction in wgpu-hal. By @Vecvec in [#7101](https://github.com/gfx-rs/wgpu/pull/7101). - Avoid using default features in many dependencies, etc. By Brody in [#7031](https://github.com/gfx-rs/wgpu/pull/7031) - Use `hashbrown` to simplify no-std support. By Brody in [#6938](https://github.com/gfx-rs/wgpu/pull/6938) & [#6925](https://github.com/gfx-rs/wgpu/pull/6925). - If you use Binding Arrays in a bind group, you may not use Dynamic Offset Buffers or Uniform Buffers in that bind group. By @cwfitzgerald in [#6811](https://github.com/gfx-rs/wgpu/pull/6811) - Rename `instance_id` and `instance_custom_index` to `instance_index` and `instance_custom_data` by @Vecvec in [#6780](https://github.com/gfx-rs/wgpu/pull/6780) #### naga - naga IR types are now available in the module `naga::ir` (e.g. `naga::ir::Module`). The original names (e.g. `naga::Module`) remain present for compatibility. By @kpreid in [#7365](https://github.com/gfx-rs/wgpu/pull/7365). - Refactored `use` statements to simplify future `no_std` support. By @bushrat011899 in [#7256](https://github.com/gfx-rs/wgpu/pull/7256) - naga's WGSL frontend no longer allows using the `&` operator to take the address of a component of a vector, which is not permitted by the WGSL specification. By @andyleiserson in [#7284](https://github.com/gfx-rs/wgpu/pull/7284) - naga's use of `termcolor` and `stderr` are now optional behind features of the same names. By @bushrat011899 in [#7482](https://github.com/gfx-rs/wgpu/pull/7482) #### Vulkan ##### HAL queue callback support - Add a way to notify with `Queue::submit()` to Vulkan's `vk::Semaphore` allocated outside of wgpu. By @sotaroikeda in [#6813](https://github.com/gfx-rs/wgpu/pull/6813). ### Bug Fixes #### naga - Fix some instances of functions which have a return type but don't return a value being incorrectly validated. By @jamienicol in [#7013](https://github.com/gfx-rs/wgpu/pull/7013). - Allow abstract expressions to be used in WGSL function return statements. By @jamienicol in [#7035](https://github.com/gfx-rs/wgpu/pull/7035). - Error if structs have two fields with the same name. By @SparkyPotato in [#7088](https://github.com/gfx-rs/wgpu/pull/7088). - Forward '--keep-coordinate-space' flag to GLSL backend in naga-cli. By @cloone8 in [#7206](https://github.com/gfx-rs/wgpu/pull/7206). - Allow template lists to have a trailing comma. By @KentSlaney in [#7142](https://github.com/gfx-rs/wgpu/pull/7142). - Allow WGSL const declarations to have abstract types. By @jamienicol in [#7055](https://github.com/gfx-rs/wgpu/pull/7055) and [#7222](https://github.com/gfx-rs/wgpu/pull/7222). - Allows override-sized arrays to resolve to the same size without causing the type arena to panic. By @KentSlaney in [#7082](https://github.com/gfx-rs/wgpu/pull/7082). - Allow abstract types to be used for WGSL switch statement selector and case selector expressions. By @jamienicol in [#7250](https://github.com/gfx-rs/wgpu/pull/7250). - Apply automatic conversions to `let` declarations, and accept `vecN()` as a constructor for vectors (in any context). By @andyleiserson in [#7367](https://github.com/gfx-rs/wgpu/pull/7367). - The `&&` and `||` operators are no longer allowed on vectors. By @andyleiserson in [#7368](https://github.com/gfx-rs/wgpu/pull/7368). - Prevent ray intersection function overwriting each other. By @Vecvec in [#7497](https://github.com/gfx-rs/wgpu/pull/7497). - Require that the level operand of an ImageQuery::Size expression is i32 or u32, per spec. By @jimblandy in [#7426](https://github.com/gfx-rs/wgpu/pull/7426). - Implement constant evaluation for the cross builtin. By @jimblandy in [#7404](https://github.com/gfx-rs/wgpu/pull/7404). - Properly handle automatic type conversions in calls to `MathFunction` builtins. By @jimblandy in [#6833](https://github.com/gfx-rs/wgpu/pull/6833). #### General - Fix some validation errors when building acceleration structures. By @Vecvec in [#7486](https://github.com/gfx-rs/wgpu/pull/7486). - Avoid overflow in query set bounds check validation. By @ErichDonGubler in [#6933](https://github.com/gfx-rs/wgpu/pull/6933). - Add Flush to GL Queue::submit. By @cwfitzgerald in [#6941](https://github.com/gfx-rs/wgpu/pull/6941). - Reduce downlevel `max_color_attachments` limit from 8 to 4 for better GLES compatibility. By @adrian17 in [#6994](https://github.com/gfx-rs/wgpu/pull/6994). - Fix building a BLAS with a transform buffer by adding a flag to indicate usage of the transform buffer. By @Vecvec in [#7062](https://github.com/gfx-rs/wgpu/pull/7062). - Move incrementation of `Device::last_acceleration_structure_build_command_index` into queue submit. By @Vecvec in [#7462](https://github.com/gfx-rs/wgpu/pull/7462). - Implement indirect draw validation. By @teoxoy in [#7140](https://github.com/gfx-rs/wgpu/pull/7140) #### Vulkan - Stop naga causing undefined behavior when a ray query misses. By @Vecvec in [#6752](https://github.com/gfx-rs/wgpu/pull/6752). - In naga's SPIR-V backend, avoid duplicating SPIR-V OpTypePointer instructions. By @jimblandy in [#7246](https://github.com/gfx-rs/wgpu/pull/7246). #### Gles - Support OpenHarmony render with `gles`. By @richerfu in [#7085](https://github.com/gfx-rs/wgpu/pull/7085) #### Dx12 - Fix HLSL storage format generation. By @Vecvec in [#6993](https://github.com/gfx-rs/wgpu/pull/6993) and [#7104](https://github.com/gfx-rs/wgpu/pull/7104) - Fix 3D storage texture bindings. By @SparkyPotato in [#7071](https://github.com/gfx-rs/wgpu/pull/7071) - Fix DX12 composite alpha modes. By @amrbashir in [#7117](https://github.com/gfx-rs/wgpu/pull/7117) - Bound check dynamic buffers. By @teoxoy in [#6931](https://github.com/gfx-rs/wgpu/pull/6931) - Fix size of buffer. By @teoxoy in [#7310](https://github.com/gfx-rs/wgpu/pull/7310) #### WebGPU - Improve efficiency of dropping read-only buffer mappings. By @kpreid in [#7007](https://github.com/gfx-rs/wgpu/pull/7007). ### Performance #### naga - Replace `unicode-xid` with `unicode-ident`. By @CrazyboyQCD in [#7135](https://github.com/gfx-rs/wgpu/pull/7135) ### Documentation - Improved documentation around pipeline caches and `TextureBlitter`. By @DJMcNab in [#6978](https://github.com/gfx-rs/wgpu/pull/6978) and [#7003](https://github.com/gfx-rs/wgpu/pull/7003). - Improved documentation of `PresentMode`, buffer mapping functions, memory alignment requirements, texture formats’ automatic conversions, and various types and constants. By @kpreid in [#7211](https://github.com/gfx-rs/wgpu/pull/7211) and [#7283](https://github.com/gfx-rs/wgpu/pull/7283). - Added a hello window example. By @laycookie in [#6992](https://github.com/gfx-rs/wgpu/pull/6992). ### Examples - Call `pre_present_notify()` before presenting. By @kjarosh in [#7074](https://github.com/gfx-rs/wgpu/pull/7074). ## v24.0.5 (2025-05-24) ### Bug Fixes #### General - Fix a possible deadlock within `Queue::write_buffer`. By @RedMindZ in [#7582](https://github.com/gfx-rs/wgpu/pull/7582) #### WebGPU - Insert fragment pipeline constants into fragment descriptor instead of vertex descriptor. By @DerSchmale in [#7621](https://github.com/gfx-rs/wgpu/pull/7621) ## v24.0.4 (2025-04-03) ### Metal - Use resize observers for smoother resizing. By @madsmtm in [#7026](https://github.com/gfx-rs/wgpu/pull/7026). ## v24.0.3 (2025-03-19) ### Bug Fixes - Fix drop order in `Surface`, solving segfaults on exit on some systems. By @ed-2100 in [#6997](https://github.com/gfx-rs/wgpu/pull/6997) ## v24.0.2 (2025-02-26) ### Bug Fixes - Fix GLES renderpass clears causing violation of `max_color_attachments` limit. By @adrian17 in [#6994](https://github.com/gfx-rs/wgpu/pull/6994). - Fix a possible deadlock within `Queue::write_texture`. By @metamuffin in [#7004](https://github.com/gfx-rs/wgpu/pull/7004) - Decrement `max_storage_buffer_binding_size` by 1 to match `max_buffer_size`. By @minus1ms in [#7217](https://github.com/gfx-rs/wgpu/pull/7217) ## v24.0.1 (2025-01-22) ### Bug Fixes - Fix `wgpu` not building with `--no-default-features` on when targeting `wasm32-unknown-unknown`. By @wumpf in [#6946](https://github.com/gfx-rs/wgpu/pull/6946). - Implement `Clone` on `ShaderModule`. By @a1phyr in [#6937](https://github.com/gfx-rs/wgpu/pull/6937). - Fix `CopyExternalImageDestInfo` not exported on `wgpu`. By @wumpf in [#6962](https://github.com/gfx-rs/wgpu/pull/6962). ## v24.0.0 (2025-01-15) ### Major changes #### Refactored Dispatch Between `wgpu-core` and `webgpu` The crate `wgpu` has two different "backends", one which targets webgpu in the browser, one which targets `wgpu_core` on native platforms and webgl. This was previously very difficult to traverse and add new features to. The entire system was refactored to make it simpler. Additionally the new system has zero overhead if there is only one "backend" in use. You can see the new system in action by using go-to-definition on any wgpu functions in your IDE. By @cwfitzgerald in [#6619](https://github.com/gfx-rs/wgpu/pull/6619). #### Most objects in `wgpu` are now `Clone` All types in the `wgpu` API are now `Clone`. This is implemented with internal reference counting, so cloning for instance a `Buffer` does copies only the "handle" of the GPU buffer, not the underlying resource. Previously, libraries using `wgpu` objects like `Device`, `Buffer` or `Texture` etc. often had to manually wrap them in a `Arc` to allow passing between libraries. This caused a lot of friction since if one library wanted to use a `Buffer` by value, calling code had to give up ownership of the resource which may interfere with other subsystems. Note that this also mimics how the WebGPU javascript API works where objects can be cloned and moved around freely. By @cwfitzgerald in [#6665](https://github.com/gfx-rs/wgpu/pull/6665). #### Render and Compute Passes Now Properly Enforce Their Lifetime A regression introduced in 23.0.0 caused lifetimes of render and compute passes to be incorrectly enforced. While this is not a soundness issue, the intent is to move an error from runtime to compile time. This issue has been fixed and restored to the 22.0.0 behavior. #### Bindless (`binding_array`) Grew More Capabilities - DX12 now supports `PARTIALLY_BOUND_BINDING_ARRAY` on Resource Binding Tier 3 Hardware. This is most D3D12 hardware [D3D12 Feature Table] for more information on what hardware supports this feature. By @cwfitzgerald in [#6734](https://github.com/gfx-rs/wgpu/pull/6734). [D3D12 Feature Table]: https://d3d12infodb.boolka.dev/FeatureTable.html #### `Device::create_shader_module_unchecked` Renamed and Now Has Configuration Options `create_shader_module_unchecked` became `create_shader_module_trusted`. This allows you to customize which exact checks are omitted so that you can get the correct balance of performance and safety for your use case. Calling the function is still unsafe, but now can be used to skip certain checks only on certain builds. This also allows users to disable the workarounds in the `msl-out` backend to prevent the compiler from optimizing infinite loops. This can have a big impact on performance, but is not recommended for untrusted shaders. ```diff let desc: ShaderModuleDescriptor = include_wgsl!(...) - let module = unsafe { device.create_shader_module_unchecked(desc) }; + let module = unsafe { device.create_shader_module_trusted(desc, wgpu::ShaderRuntimeChecks::unchecked()) }; ``` By @cwfitzgerald and @rudderbucky in [#6662](https://github.com/gfx-rs/wgpu/pull/6662). #### `wgpu::Instance::new` now takes `InstanceDescriptor` by reference Previously `wgpu::Instance::new` took `InstanceDescriptor` by value (which is overall fairly uncommon in wgpu). Furthermore, `InstanceDescriptor` is now cloneable. ```diff - let instance = wgpu::Instance::new(instance_desc); + let instance = wgpu::Instance::new(&instance_desc); ``` By @wumpf in [#6849](https://github.com/gfx-rs/wgpu/pull/6849). #### Environment Variable Handling Overhaul Previously how various bits of code handled reading settings from environment variables was inconsistent and unideomatic. We have unified it to (`Type::from_env()` or `Type::from_env_or_default()`) and `Type::with_env` for all types. ```diff - wgpu::util::backend_bits_from_env() + wgpu::Backends::from_env() - wgpu::util::power_preference_from_env() + wgpu::PowerPreference::from_env() - wgpu::util::dx12_shader_compiler_from_env() + wgpu::Dx12Compiler::from_env() - wgpu::util::gles_minor_version_from_env() + wgpu::Gles3MinorVersion::from_env() - wgpu::util::instance_descriptor_from_env() + wgpu::InstanceDescriptor::from_env_or_default() - wgpu::util::parse_backends_from_comma_list(&str) + wgpu::Backends::from_comma_list(&str) ``` By @cwfitzgerald in [#6895](https://github.com/gfx-rs/wgpu/pull/6895) #### Backend-specific instance options are now in separate structs In order to better facilitate growing more interesting backend options, we have put them into individual structs. This allows users to more easily understand what options can be defaulted and which they care about. All of these new structs implement `from_env()` and delegate to their respective `from_env()` methods. ```diff - let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), - flags: wgpu::InstanceFlags::default(), - dx12_shader_compiler: wgpu::Dx12Compiler::Dxc, - gles_minor_version: wgpu::Gles3MinorVersion::Automatic, - }); + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::all(), + flags: wgpu::InstanceFlags::default(), + backend_options: wgpu::BackendOptions { + dx12: wgpu::Dx12BackendOptions { + shader_compiler: wgpu::Dx12ShaderCompiler::Dxc, + }, + gl: wgpu::GlBackendOptions { + gles_minor_version: wgpu::Gles3MinorVersion::Automatic, + }, + }, + }); ``` If you do not need any of these options, or only need one backend's info use the `default()` impl to fill out the remaining feelds. By @cwfitzgerald in [#6895](https://github.com/gfx-rs/wgpu/pull/6895) #### The `diagnostic(…);` directive is now supported in WGSL naga now parses `diagnostic(…);` directives according to the WGSL spec. This allows users to control certain lints, similar to Rust's `allow`, `warn`, and `deny` attributes. For example, in standard WGSL (but, notably, not naga yet—see ) this snippet would emit a uniformity error: ```wgsl @group(0) @binding(0) var s : sampler; @group(0) @binding(2) var tex : texture_2d; @group(1) @binding(0) var ro_buffer : array; @fragment fn main(@builtin(position) p : vec4f) -> @location(0) vec4f { if ro_buffer[0] == 0 { // Emits a derivative uniformity error during validation. return textureSample(tex, s, vec2(0.,0.)); } return vec4f(0.); } ``` …but we can now silence it with the `off` severity level, like so: ```wgsl // Disable the diagnosic with this… diagnostic(off, derivative_uniformity); @group(0) @binding(0) var s : sampler; @group(0) @binding(2) var tex : texture_2d; @group(1) @binding(0) var ro_buffer : array; @fragment fn main(@builtin(position) p : vec4f) -> @location(0) vec4f { if ro_buffer[0] == 0 { // Look ma, no error! return textureSample(tex, s, vec2(0.,0.)); } return vec4f(0.); } ``` There are some limitations to keep in mind with this new functionality: - We support `@diagnostic(…)` rules as `fn` attributes, but prioritization for rules in statement positions (i.e., `if (…) @diagnostic(…) { … }` is unclear. If you are blocked by not being able to parse `diagnostic(…)` rules in statement positions, please let us know in , so we can determine how to prioritize it! - Standard WGSL specifies `error`, `warning`, `info`, and `off` severity levels. These are all technically usable now! A caveat, though: warning- and info-level are only emitted to `stderr` via the `log` façade, rather than being reported through a `Result::Err` in naga or the `CompilationInfo` interface in `wgpu{,-core}`. This will require breaking changes in naga to fix, and is being tracked by . - Not all lints can be controlled with `diagnostic(…)` rules. In fact, only the `derivative_uniformity` triggering rule exists in the WGSL standard. That said, naga contributors are excited to see how this level of control unlocks a new ecosystem of configurable diagnostics. - Finally, `diagnostic(…)` rules are not yet emitted in WGSL output. This means that `wgsl-in` → `wgsl-out` is currently a lossy process. We felt that it was important to unblock users who needed `diagnostic(…)` rules (i.e., ) before we took significant effort to fix this (tracked in ). By @ErichDonGubler in [#6456](https://github.com/gfx-rs/wgpu/pull/6456), [#6148](https://github.com/gfx-rs/wgpu/pull/6148), [#6533](https://github.com/gfx-rs/wgpu/pull/6533), [#6353](https://github.com/gfx-rs/wgpu/pull/6353), [#6537](https://github.com/gfx-rs/wgpu/pull/6537). #### New Features ##### naga - Support atomic operations on fields of global structs in the SPIR-V frontend. By @schell in [#6693](https://github.com/gfx-rs/wgpu/pull/6693). - Clean up tests for atomic operations support in SPIR-V frontend. By @schell in [#6692](https://github.com/gfx-rs/wgpu/pull/6692) - Fix an issue where `naga` CLI would incorrectly skip the first positional argument when `--stdin-file-path` was specified. By @ErichDonGubler in [#6480](https://github.com/gfx-rs/wgpu/pull/6480). - Fix textureNumLevels in the GLSL backend. By @magcius in [#6483](https://github.com/gfx-rs/wgpu/pull/6483). - Support 64-bit hex literals and unary operations in constants [#6616](https://github.com/gfx-rs/wgpu/pull/6616). - Implement `quantizeToF16()` for WGSL frontend, and WGSL, SPIR-V, HLSL, MSL, and GLSL backends. By @jamienicol in [#6519](https://github.com/gfx-rs/wgpu/pull/6519). - Add support for GLSL `usampler*` and `isampler*`. By @DavidPeicho in [#6513](https://github.com/gfx-rs/wgpu/pull/6513). - Expose Ray Query flags as constants in WGSL. Implement candidate intersections. By @kvark in [#5429](https://github.com/gfx-rs/wgpu/pull/5429) - Add new vertex formats (`{U,S}{int,norm}{8,16}`, `Float16` and `Unorm8x4Bgra`). By @nolanderc in [#6632](https://github.com/gfx-rs/wgpu/pull/6632) - Allow for override-expressions in `workgroup_size`. By @KentSlaney in [#6635](https://github.com/gfx-rs/wgpu/pull/6635). - Add support for OpAtomicCompareExchange in SPIR-V frontend. By @schell in [#6590](https://github.com/gfx-rs/wgpu/pull/6590). - Implement type inference for abstract arguments to user-defined functions. By @jamienicol in [#6577](https://github.com/gfx-rs/wgpu/pull/6577). - Allow for override-expressions in array sizes. By @KentSlaney in [#6654](https://github.com/gfx-rs/wgpu/pull/6654). - [`pointer_composite_access` WGSL language extension](https://www.w3.org/TR/WGSL/#language_extension-pointer_composite_access) is implemented. By @sagudev in [#6913](https://github.com/gfx-rs/wgpu/pull/6913) ##### General - Add unified documentation for ray-tracing. By @Vecvec in [#6747](https://github.com/gfx-rs/wgpu/pull/6747) - Return submission index in `map_async` and `on_submitted_work_done` to track down completion of async callbacks. By @eliemichel in [#6360](https://github.com/gfx-rs/wgpu/pull/6360). - Move raytracing alignments into HAL instead of in core. By @Vecvec in [#6563](https://github.com/gfx-rs/wgpu/pull/6563). - Allow for statically linking DXC rather than including separate `.dll` files. By @DouglasDwyer in [#6574](https://github.com/gfx-rs/wgpu/pull/6574). - `DeviceType` and `AdapterInfo` now impl `Hash` by @cwfitzgerald in [#6868](https://github.com/gfx-rs/wgpu/pull/6868) - Add build support for Apple Vision Pro. By @guusw in [#6611](https://github.com/gfx-rs/wgpu/pull/6611). - Add `wgsl_language_features` for obtaining available WGSL language feature by @sagudev in [#6814](https://github.com/gfx-rs/wgpu/pull/6814) - Image atomic support in shaders. By @atlv24 in [#6706](https://github.com/gfx-rs/wgpu/pull/6706) - 64 bit image atomic support in shaders. By @atlv24 in [#5537](https://github.com/gfx-rs/wgpu/pull/5537) - Add `no_std` support to `wgpu-types`. By @bushrat011899 in [#6892](https://github.com/gfx-rs/wgpu/pull/6892). ##### Vulkan - Allow using some 32-bit floating-point atomic operations (load, store, add, sub, exchange) in shaders. It requires the extension `VK_EXT_shader_atomic_float`. By @AsherJingkongChen in [#6234](https://github.com/gfx-rs/wgpu/pull/6234). ##### Metal - Allow using some 32-bit floating-point atomic operations (load, store, add, sub, exchange) in shaders. It requires Metal 3.0+ with Apple 7, 8, 9 or Mac 2. By @AsherJingkongChen in [#6234](https://github.com/gfx-rs/wgpu/pull/6234). - Add build support for Apple Vision Pro. By @guusw in [#6611](https://github.com/gfx-rs/wgpu/pull/6611). - Add `raw_handle` method to access raw Metal textures in [#6894](https://github.com/gfx-rs/wgpu/pull/6894). #### D3D12 - Support DXR (DirectX Ray-tracing) in wgpu-hal. By @Vecvec in [#6777](https://github.com/gfx-rs/wgpu/pull/6777) #### Changes ##### naga - Show types of LHS and RHS in binary operation type mismatch errors. By @ErichDonGubler in [#6450](https://github.com/gfx-rs/wgpu/pull/6450). - The GLSL parser now uses less expressions for function calls. By @magcius in [#6604](https://github.com/gfx-rs/wgpu/pull/6604). - Add a note to help with a common syntax error case for global diagnostic filter directives. By @e-hat in [#6718](https://github.com/gfx-rs/wgpu/pull/6718) - Change arithmetic operations between two i32 variables to wrap on overflow to match WGSL spec. By @matthew-wong1 in [#6835](https://github.com/gfx-rs/wgpu/pull/6835). - Add directives to suggestions in error message for parsing global items. By @e-hat in [#6723](https://github.com/gfx-rs/wgpu/pull/6723). - Automatic conversion for `override` initializers. By @sagudev in [6920](https://github.com/gfx-rs/wgpu/pull/6920) ##### General - Align Storage Access enums to the webgpu spec. By @atlv24 in [#6642](https://github.com/gfx-rs/wgpu/pull/6642) - Make `Surface::as_hal` take an immutable reference to the surface. By @jerzywilczek in [#9999](https://github.com/gfx-rs/wgpu/pull/9999) - Add actual sample type to `CreateBindGroupError::InvalidTextureSampleType` error message. By @ErichDonGubler in [#6530](https://github.com/gfx-rs/wgpu/pull/6530). - Improve binding error to give a clearer message when there is a mismatch between resource binding as it is in the shader and as it is in the binding layout. By @eliemichel in [#6553](https://github.com/gfx-rs/wgpu/pull/6553). - `Surface::configure` and `Surface::get_current_texture` are no longer fatal. By @alokedesai in [#6253](https://github.com/gfx-rs/wgpu/pull/6253) - Rename `BlasTriangleGeometry::index_buffer_offset` to `BlasTriangleGeometry::first_index`. By @Vecvec in [#6873](https://github.com/gfx-rs/wgpu/pull/6873/files) ##### D3D12 - Avoid using FXC as fallback when the DXC container was passed at instance creation. Paths to `dxcompiler.dll` & `dxil.dll` are also now required. By @teoxoy in [#6643](https://github.com/gfx-rs/wgpu/pull/6643). ##### Vulkan - Add a cache for samplers, deduplicating any samplers, allowing more programs to stay within the global sampler limit. By @cwfitzgerald in [#6847](https://github.com/gfx-rs/wgpu/pull/6847) ##### HAL - Replace `usage: Range`, for `BufferUses`, `TextureUses`, and `AccelerationStructureBarrier` with a new `StateTransition`. By @atlv24 in [#6703](https://github.com/gfx-rs/wgpu/pull/6703) - Change the `DropCallback` API to use `FnOnce` instead of `FnMut`. By @jerzywilczek in [#6482](https://github.com/gfx-rs/wgpu/pull/6482) ### Bug Fixes #### General - Handle query set creation failure as an internal error that loses the `Device`, rather than panicking. By @ErichDonGubler in [#6505](https://github.com/gfx-rs/wgpu/pull/6505). - Ensure that `Features::TIMESTAMP_QUERY` is set when using timestamp writes in render and compute passes. By @ErichDonGubler in [#6497](https://github.com/gfx-rs/wgpu/pull/6497). - Check for device mismatches when beginning render and compute passes. By @ErichDonGubler in [#6497](https://github.com/gfx-rs/wgpu/pull/6497). - Lower `QUERY_SET_MAX_QUERIES` (and enforced limits) from 8192 to 4096 to match WebGPU spec. By @ErichDonGubler in [#6525](https://github.com/gfx-rs/wgpu/pull/6525). - Allow non-filterable float on texture bindings never used with samplers when using a derived bind group layout. By @ErichDonGubler in [#6531](https://github.com/gfx-rs/wgpu/pull/6531/). - Replace potentially unsound usage of `PreHashedMap` with `FastHashMap`. By @jamienicol in [#6541](https://github.com/gfx-rs/wgpu/pull/6541). - Add missing validation for timestamp writes in compute and render passes. By @ErichDonGubler in [#6578](https://github.com/gfx-rs/wgpu/pull/6578), [#6583](https://github.com/gfx-rs/wgpu/pull/6583). - Check the status of the `TIMESTAMP_QUERY` feature before other validation. - Check that indices are in-bounds for the query set. - Check that begin and end indices are not equal. - Check that at least one index is specified. - Reject destroyed buffers in query set resolution. By @ErichDonGubler in [#6579](https://github.com/gfx-rs/wgpu/pull/6579). - Fix panic when dropping `Device` on some environments. By @Dinnerbone in [#6681](https://github.com/gfx-rs/wgpu/pull/6681). - Reduced the overhead of command buffer validation. By @nical in [#6721](https://github.com/gfx-rs/wgpu/pull/6721). - Set index type to NONE in `get_acceleration_structure_build_sizes`. By @Vecvec in [#6802](https://github.com/gfx-rs/wgpu/pull/6802). - Fix `wgpu-info` not showing dx12 adapters. By @wumpf in [#6844](https://github.com/gfx-rs/wgpu/pull/6844). - Use `transform_buffer_offset` when initialising `transform_buffer`. By @Vecvec in [#6864](https://github.com/gfx-rs/wgpu/pull/6864). #### naga - Fix crash when a texture argument is missing. By @aedm in [#6486](https://github.com/gfx-rs/wgpu/pull/6486) - Emit an error in constant evaluation, rather than crash, in certain cases where `vecN` constructors have less than N arguments. By @ErichDonGubler in [#6508](https://github.com/gfx-rs/wgpu/pull/6508). - Fix an error in template list matching `>=` in `a=c`. By @KentSlaney in [#6898](https://github.com/gfx-rs/wgpu/pull/6898). - Correctly validate handles in override-sized array types. By @jimblandy in [#6882](https://github.com/gfx-rs/wgpu/pull/6882). - Clean up validation of `Statement::ImageStore`. By @jimblandy in [#6729](https://github.com/gfx-rs/wgpu-pull/6729). - In compaction, avoid cloning the type arena. By @jimblandy in [#6790](https://github.com/gfx-rs/wgpu-pull/6790) - In validation, forbid cycles between global expressions and types. By @jimblandy in [#6800](https://github.com/gfx-rs/wgpu-pull/6800) - Allow abstract scalars in modf and frexp results. By @jimblandy in [#6821](https://github.com/gfx-rs/wgpu-pull/6821) - In the WGSL front end, apply automatic conversions to values being assigned. By @jimblandy in [#6822](https://github.com/gfx-rs/wgpu-pull/6822) - Fix a leak by ensuring that types that depend on expressions are correctly compacted. By @KentSlaney in [#6934](https://github.com/gfx-rs/wgpu/pull/6934). #### Vulkan - Allocate descriptors for acceleration structures. By @Vecvec in [#6861](https://github.com/gfx-rs/wgpu/pull/6861). - `max_color_attachment_bytes_per_sample` is now correctly set to 128. By @cwfitzgerald in [#6866](https://github.com/gfx-rs/wgpu/pull/6866) #### D3D12 - Fix no longer showing software rasterizer adapters. By @wumpf in [#6843](https://github.com/gfx-rs/wgpu/pull/6843). - `max_color_attachment_bytes_per_sample` is now correctly set to 128. By @cwfitzgerald in [#6866](https://github.com/gfx-rs/wgpu/pull/6866) ### Examples - Add multiple render targets example. By @kaphula in [#5297](https://github.com/gfx-rs/wgpu/pull/5313) ### Testing - Tests the early returns in the acceleration structure build calls with empty calls. By @Vecvec in [#6651](https://github.com/gfx-rs/wgpu/pull/6651). ## 23.0.1 (2024-11-25) This release includes patches for `wgpu`, `wgpu-core` and `wgpu-hal`. All other crates remain at [23.0.0](https://github.com/gfx-rs/wgpu/releases/tag/v23.0.0). Below changes were cherry-picked from 24.0.0 development line. ### Bug fixes #### General - Fix Texture view leaks regression. By @xiaopengli89 in [#6576](https://github.com/gfx-rs/wgpu/pull/6576) #### Metal - Fix surface creation crashing on iOS. By @mockersf in [#6535](https://github.com/gfx-rs/wgpu/pull/6535) #### Vulkan - Fix surface capabilities being advertised when its query failed. By @wumpf in [#6510](https://github.com/gfx-rs/wgpu/pull/6510) ## 23.0.0 (2024-10-25) ### Themes of this release This release's theme is one that is likely to repeat for a few releases: convergence with the WebGPU specification! wgpu's design and base functionality are actually determined by two specifications: one for WebGPU, and one for the WebGPU Shading Language. This may not sound exciting, but let us convince you otherwise! All major web browsers have committed to offering WebGPU in their environment. Even JS runtimes like [Node][nodejs-webgpu-interest] and [Deno][deno_webgpu-crate-manifest] have communities that are very interested in providing WebGPU! WebGPU is slowly [eating the world][eat-the-world-meaning], as it were. 😀 It's really important, then, that WebGPU implementations behave in ways that one would expect across all platforms. For example, if Firefox's WebGPU implementation were to break when running scripts and shaders that worked just fine in Chrome, that would mean sad users for both application authors _and_ browser authors. [nodejs-webgpu-interest]: https://github.com/orgs/nodejs/discussions/41994 [deno_webgpu-crate-manifest]: https://github.com/gfx-rs/wgpu/tree/64a61ee5c69569bbb3db03563997e88a229eba17/deno_webgpu#deno_webgpu [eat-the-world-meaning]: https://www.quora.com/What-did-Marc-Andreessen-mean-when-he-said-that-software-is-eating-the-world wgpu also benefits from standard, portable behavior in the same way as web browsers. Because of this behavior, it's generally fairly easy to port over usage of WebGPU in JavaScript to wgpu. It is also what lets wgpu go full circle: wgpu can be an implementation of WebGPU on native targets, but _also_ it can use _other implementations of WebGPU_ as a backend in JavaScript when compiled to WASM. Therefore, the same dynamic applies: if wgpu's own behavior were significantly different, then wgpu and end users would be _sad, sad humans_ as soon as they discover places where their nice apps are breaking, right? The answer is: yes, we _do_ have sad, sad humans that really want their wgpu code to work _everywhere_. As Firefox and others use wgpu to implement WebGPU, the above example of Firefox diverging from standard is, unfortunately, today's reality. It _mostly_ behaves the same as a standards-compliant WebGPU, but it still doesn't in many important ways. Of particular note is naga, its implementation of the WebGPU Shader Language. Shaders are pretty much a black-and-white point of failure in GPU programming; if they don't compile, then you can't use the rest of the API! And yet, it's extremely easy to run into a case like that from : ```wgsl fn gimme_a_float() -> f32 { return 42; // fails in naga, but standard WGSL happily converts to `f32` } ``` We intend to continue making visible strides in converging with specifications for WebGPU and WGSL, as this release has. This is, unfortunately, one of the major reasons that wgpu has no plans to work hard at keeping a SemVer-stable interface for the foreseeable future; we have an entire platform of GPU programming functionality we have to catch up with, and SemVer stability is unfortunately in tension with that. So, for now, you're going to keep seeing major releases and breaking changes. Where possible, we'll try to make that painless, but compromises to do so don't always make sense with our limited resources. This is also the last planned major version release of 2024; the next milestone is set for January 1st, 2025, according to our regular 12-week cadence (offset from the originally planned date of 2024-10-09 for _this_ release 😅). We'll see you next year! ### Contributor spotlight: @sagudev This release, we'd like to spotlight the work of @sagudev, who has made significant contributions to the wgpu ecosystem this release. Among other things, they contributed a particularly notable feature where runtime-known indices are finally allowed for use with `const` array values. For example, this WGSL shader previously wasn't allowed: ```wgsl const arr: array = array(1, 2, 3, 4); fn what_number_should_i_use(idx: u32) -> u32 { return arr[idx]; } ``` …but now it works! This is significant because this sort of shader rejection was one of the most impactful issues we are aware of for converging with the WGSL specification. There are more still to go—some of which we expect to even more drastically change how folks author shaders—but we suspect that many more will come in the next few releases, including with @sagudev's help. We're excited for more of @sagudev's contributions via the Servo community. Oh, did we forget to mention that these contributions were motivated by their work on Servo? That's right, a _third_ well-known JavaScript runtime is now using wgpu to implement its WebGPU implementation. We're excited to support Servo to becoming another fully fledged browsing environment this way. ### Major Changes In addition to the above spotlight, we have the following particularly interesting items to call out for this release: #### `wgpu-core` is no longer generic over `wgpu-hal` backends Dynamic dispatch between different backends has been moved from the user facing `wgpu` crate, to a new dynamic dispatch mechanism inside the backend abstraction layer `wgpu-hal`. Whenever targeting more than a single backend (default on Windows & Linux) this leads to faster compile times and smaller binaries! This also solves a long standing issue with `cargo doc` failing to run for `wgpu-core`. Benchmarking indicated that compute pass recording is slower as a consequence, whereas on render passes speed improvements have been observed. However, this effort simplifies many of the internals of the wgpu family of crates which we're hoping to build performance improvements upon in the future. By @wumpf in [#6069](https://github.com/gfx-rs/wgpu/pull/6069), [#6099](https://github.com/gfx-rs/wgpu/pull/6099), [#6100](https://github.com/gfx-rs/wgpu/pull/6100). #### `wgpu`'s resources no longer have `.global_id()` getters `wgpu-core`'s internals no longer use nor need IDs and we are moving towards removing IDs completely. This is a step in that direction. Current users of `.global_id()` are encouraged to make use of the `PartialEq`, `Eq`, `Hash`, `PartialOrd` and `Ord` traits that have now been implemented for `wgpu` resources. By @teoxoy in [#6134](https://github.com/gfx-rs/wgpu/pull/6134). #### `set_bind_group` now takes an `Option` for the bind group argument. https://gpuweb.github.io/gpuweb/#programmable-passes-bind-groups specifies that bindGroup is nullable. This change is the start of implementing this part of the spec. Callers that specify a `Some()` value should have unchanged behavior. Handling of `None` values still needs to be implemented by backends. For convenience, the `set_bind_group` on compute/render passes & encoders takes `impl Into>`, so most code should still work the same. By @bradwerth in [#6216](https://github.com/gfx-rs/wgpu/pull/6216). #### `entry_point`s are now `Option`al One of the changes in the WebGPU spec. (from [about this time last year][optional-entrypoint-in-spec] 😅) was to allow optional entry points in `GPUProgrammableStage`. In `wgpu`, this corresponds to a subset of fields in `FragmentState`, `VertexState`, and `ComputeState` as the `entry_point` member: ```wgsl let render_pipeline = device.createRenderPipeline(wgpu::RenderPipelineDescriptor { module, entry_point: Some("cs_main"), // This is now `Option`al. // … }); let compute_pipeline = device.createComputePipeline(wgpu::ComputePipelineDescriptor { module, entry_point: None, // This is now `Option`al. // … }); ``` When set to `None`, it's assumed that the shader only has a single entry point associated with the pipeline stage (i.e., `@compute`, `@fragment`, or `@vertex`). If there is not one and only one candidate entry point, then a validation error is returned. To continue the example, we might have written the above API usage with the following shader module: ```wgsl // We can't use `entry_point: None` for compute pipelines with this module, // because there are two `@compute` entry points. @compute fn cs_main() { /* … */ } @compute fn other_cs_main() { /* … */ } // The following entry points _can_ be inferred from `entry_point: None` in a // render pipeline, because they're the only `@vertex` and `@fragment` entry // points: @vertex fn vs_main() { /* … */ } @fragment fn fs_main() { /* … */ } ``` [optional-entrypoint-in-spec]: https://github.com/gpuweb/gpuweb/issues/4342 #### wgpu's DX12 backend is now based on the `windows` crate ecosystem, instead of the `d3d12` crate wgpu has retired the `d3d12` crate (based on `winapi`), and now uses the `windows` crate for interfacing with Windows. For many, this may not be a change that affects day-to-day work. However, for users who need to vet their dependencies, or who may vendor in dependencies, this may be a nontrivial migration. By @MarijnS95 in [#6006](https://github.com/gfx-rs/wgpu/pull/6006). ### New Features #### Wgpu - Added initial acceleration structure and ray query support into wgpu. By @expenses @daniel-keitel @Vecvec @JMS55 @atlv24 in [#6291](https://github.com/gfx-rs/wgpu/pull/6291) #### naga - Support constant evaluation for `firstLeadingBit` and `firstTrailingBit` numeric built-ins in WGSL. Front-ends that translate to these built-ins also benefit from constant evaluation. By @ErichDonGubler in [#5101](https://github.com/gfx-rs/wgpu/pull/5101). - Add `first` and `either` sampling types for `@interpolate(flat, …)` in WGSL. By @ErichDonGubler in [#6181](https://github.com/gfx-rs/wgpu/pull/6181). - Support for more atomic ops in the SPIR-V frontend. By @schell in [#5824](https://github.com/gfx-rs/wgpu/pull/5824). - Support local `const` declarations in WGSL. By @sagudev in [#6156](https://github.com/gfx-rs/wgpu/pull/6156). - Implemented `const_assert` in WGSL. By @sagudev in [#6198](https://github.com/gfx-rs/wgpu/pull/6198). - Support polyfilling `inverse` in WGSL. By @chyyran in [#6385](https://github.com/gfx-rs/wgpu/pull/6385). - Add base support for parsing `requires`, `enable`, and `diagnostic` directives. No extensions or diagnostic filters are yet supported, but diagnostics have improved dramatically. By @ErichDonGubler in [#6352](https://github.com/gfx-rs/wgpu/pull/6352), [#6424](https://github.com/gfx-rs/wgpu/pull/6424), [#6437](https://github.com/gfx-rs/wgpu/pull/6437). - Include error chain information as a message and notes in shader compilation messages. By @ErichDonGubler in [#6436](https://github.com/gfx-rs/wgpu/pull/6436). - Unify naga CLI error output with the format of shader compilation messages. By @ErichDonGubler in [#6436](https://github.com/gfx-rs/wgpu/pull/6436). #### General - Add `VideoFrame` to `ExternalImageSource` enum. By @jprochazk in [#6170](https://github.com/gfx-rs/wgpu/pull/6170). - Add `wgpu::util::new_instance_with_webgpu_detection` & `wgpu::util::is_browser_webgpu_supported` to make it easier to support WebGPU & WebGL in the same binary. By @wumpf in [#6371](https://github.com/gfx-rs/wgpu/pull/6371). #### Vulkan - Allow using [VK_GOOGLE_display_timing](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_GOOGLE_display_timing.html) unsafely with the `VULKAN_GOOGLE_DISPLAY_TIMING` feature. By @DJMcNab in [#6149](https://github.com/gfx-rs/wgpu/pull/6149). #### Metal - Implement `atomicCompareExchangeWeak`. By @AsherJingkongChen in [#6265](https://github.com/gfx-rs/wgpu/pull/6265). - Unless an explicit `CAMetalLayer` is provided, surfaces now render to a sublayer. This improves resizing behavior, fixing glitches during on window resize. By @madsmtm in [#6107](https://github.com/gfx-rs/wgpu/pull/6107). ### Bug Fixes - Fix incorrect hlsl image output type conversion. By @atlv24 in [#6123](https://github.com/gfx-rs/wgpu/pull/6123). #### naga - SPIR-V frontend splats depth texture sample and load results. Fixes [issue #4551](https://github.com/gfx-rs/wgpu/issues/4551). By @schell in [#6384](https://github.com/gfx-rs/wgpu/pull/6384). - Accept only `vec3` (not `vecN`) for the `cross` built-in. By @ErichDonGubler in [#6171](https://github.com/gfx-rs/wgpu/pull/6171). - Configure `SourceLanguage` when enabling debug info in SPV-out. By @kvark in [#6256](https://github.com/gfx-rs/wgpu/pull/6256). - Do not consider per-polygon and flat inputs subgroup uniform. By @magcius in [#6276](https://github.com/gfx-rs/wgpu/pull/6276). - Validate all swizzle components are either color (rgba) or dimension (xyzw) in WGSL. By @sagudev in [#6187](https://github.com/gfx-rs/wgpu/pull/6187). - Fix detection of shl overflows to detect arithmetic overflows. By @sagudev in [#6186](https://github.com/gfx-rs/wgpu/pull/6186). - Fix type parameters to vec/mat type constructors to also support aliases. By @sagudev in [#6189](https://github.com/gfx-rs/wgpu/pull/6189). - Accept global `var`s without explicit type. By @sagudev in [#6199](https://github.com/gfx-rs/wgpu/pull/6199). - Fix handling of phony statements, so they are actually emitted. By @sagudev in [#6328](https://github.com/gfx-rs/wgpu/pull/6328). - Added `gl_DrawID` to glsl and `DrawIndex` to spv. By @ChosenName in [#6325](https://github.com/gfx-rs/wgpu/pull/6325). - Matrices can now be indexed by value (#4337), and indexing arrays by value no longer causes excessive spilling (#6358). By @jimblandy in [#6390](https://github.com/gfx-rs/wgpu/pull/6390). - Add support for `textureQueryLevels` to the GLSL parser. By @magcius in [#6325](https://github.com/gfx-rs/wgpu/pull/6415). - Fix unescaped identifiers in the Metal backend shader I/O structures causing shader miscompilation. By @ErichDonGubler in [#6438](https://github.com/gfx-rs/wgpu/pull/6438). #### General - If GL context creation fails retry with GLES. By @Rapdorian in [#5996](https://github.com/gfx-rs/wgpu/pull/5996). - Bump MSRV for `d3d12`/`naga`/`wgpu-core`/`wgpu-hal`/`wgpu-types`' to 1.76. By @wumpf in [#6003](https://github.com/gfx-rs/wgpu/pull/6003). - Print requested and supported usages on `UnsupportedUsage` error. By @VladasZ in [#6007](https://github.com/gfx-rs/wgpu/pull/6007). - Deduplicate bind group layouts that are created from pipelines with "auto" layouts. By @teoxoy [#6049](https://github.com/gfx-rs/wgpu/pull/6049). - Document `wgpu_hal` bounds-checking promises, and adapt `wgpu_core`'s lazy initialization logic to the slightly weaker-than-expected guarantees. By @jimblandy in [#6201](https://github.com/gfx-rs/wgpu/pull/6201). - Raise validation error instead of panicking in `{Render,Compute}Pipeline::get_bind_group_layout` on native / WebGL. By @bgr360 in [#6280](https://github.com/gfx-rs/wgpu/pull/6280). - **BREAKING**: Remove the last exposed C symbols in project, located in `wgpu_core::render::bundle::bundle_ffi`, to allow multiple versions of wgpu to compile together. By @ErichDonGubler in [#6272](https://github.com/gfx-rs/wgpu/pull/6272). - Call `flush_mapped_ranges` when unmapping write-mapped buffers. By @teoxoy in [#6089](https://github.com/gfx-rs/wgpu/pull/6089). - When mapping buffers for reading, mark buffers as initialized only when they have `MAP_WRITE` usage. By @teoxoy in [#6178](https://github.com/gfx-rs/wgpu/pull/6178). - Add a separate pipeline constants error. By @teoxoy in [#6094](https://github.com/gfx-rs/wgpu/pull/6094). - Ensure safety of indirect dispatch by injecting a compute shader that validates the content of the indirect buffer. By @teoxoy in [#5714](https://github.com/gfx-rs/wgpu/pull/5714). - Add conversions between `TextureFormat` and `StorageFormat`. By @caelunshun in [#6185](https://github.com/gfx-rs/wgpu/pull/6185) #### GLES / OpenGL - Fix GL debug message callbacks not being properly cleaned up (causing UB). By @Imberflur in [#6114](https://github.com/gfx-rs/wgpu/pull/6114). - Fix calling `slice::from_raw_parts` with unaligned pointers in push constant handling. By @Imberflur in [#6341](https://github.com/gfx-rs/wgpu/pull/6341). - Optimise fence checking when `Queue::submit` is called many times per frame. By @dinnerbone in [#6427](https://github.com/gfx-rs/wgpu/pull/6427). #### WebGPU - Fix JS `TypeError` exception in `Instance::request_adapter` when browser doesn't support WebGPU but `wgpu` not compiled with `webgl` support. By @bgr360 in [#6197](https://github.com/gfx-rs/wgpu/pull/6197). #### Vulkan - Avoid undefined behaviour with adversarial debug label. By @DJMcNab in [#6257](https://github.com/gfx-rs/wgpu/pull/6257). - Add `.index_type(vk::IndexType::NONE_KHR)` when creating `AccelerationStructureGeometryTrianglesDataKHR` in the raytraced triangle example to prevent a validation error. By @Vecvec in [#6282](https://github.com/gfx-rs/wgpu/pull/6282). ### Changes - `wgpu_hal::gles::Adapter::new_external` now requires the context to be current when dropping the adapter and related objects. By @Imberflur in [#6114](https://github.com/gfx-rs/wgpu/pull/6114). - Reduce the amount of debug and trace logs emitted by wgpu-core and wgpu-hal. By @nical in [#6065](https://github.com/gfx-rs/wgpu/issues/6065). - Rename `Rg11b10Float` to `Rg11b10Ufloat`. By @sagudev in [#6108](https://github.com/gfx-rs/wgpu/pull/6108). - Invalidate the device when we encounter driver-induced device loss or on unexpected errors. By @teoxoy in [#6229](https://github.com/gfx-rs/wgpu/pull/6229). - Make Vulkan error handling more robust. By @teoxoy in [#6119](https://github.com/gfx-rs/wgpu/pull/6119). - Add bounds checking to Buffer slice method. By @beholdnec in [#6432](https://github.com/gfx-rs/wgpu/pull/6432). - Replace `impl From for ScalarKind` with `impl From for Scalar` so that byte width is included. By @atlv24 in [#6451](https://github.com/gfx-rs/wgpu/pull/6451). #### Internal - Tracker simplifications. By @teoxoy in [#6073](https://github.com/gfx-rs/wgpu/pull/6073) & [#6088](https://github.com/gfx-rs/wgpu/pull/6088). - D3D12 cleanup. By @teoxoy in [#6200](https://github.com/gfx-rs/wgpu/pull/6200). - Use `ManuallyDrop` in remaining places. By @teoxoy in [#6092](https://github.com/gfx-rs/wgpu/pull/6092). - Move out invalidity from the `Registry`. By @teoxoy in [#6243](https://github.com/gfx-rs/wgpu/pull/6243). - Remove `backend` from ID. By @teoxoy in [#6263](https://github.com/gfx-rs/wgpu/pull/6263). #### HAL - Change the inconsistent `DropGuard` based API on Vulkan and GLES to a consistent, callback-based one. By @jerzywilczek in [#6164](https://github.com/gfx-rs/wgpu/pull/6164). ### Documentation - Removed some OpenGL and Vulkan references from `wgpu-types` documentation. Fixed Storage texel types in examples. By @Nelarius in [#6271](https://github.com/gfx-rs/wgpu/pull/6271). - Used `wgpu::include_wgsl!(…)` more in examples and tests. By @ErichDonGubler in [#6326](https://github.com/gfx-rs/wgpu/pull/6326). ### Dependency Updates #### GLES - Replace `winapi` code in WGL wrapper to use the `windows` crate. By @MarijnS95 in [#6006](https://github.com/gfx-rs/wgpu/pull/6006). - Update `glutin` to `0.31` with `glutin-winit` crate. By @MarijnS95 in [#6150](https://github.com/gfx-rs/wgpu/pull/6150) and [#6176](https://github.com/gfx-rs/wgpu/pull/6176). - Implement `Adapter::new_external()` for WGL (just like EGL) to import an external OpenGL ES context. By @MarijnS95 in [#6152](https://github.com/gfx-rs/wgpu/pull/6152). #### DX12 - Replace `winapi` code to use the `windows` crate. By @MarijnS95 in [#5956](https://github.com/gfx-rs/wgpu/pull/5956) and [#6173](https://github.com/gfx-rs/wgpu/pull/6173). - Get `num_workgroups` builtin working for indirect dispatches. By @teoxoy in [#5730](https://github.com/gfx-rs/wgpu/pull/5730). #### HAL - Update `parking_lot` to `0.12`. By @mahkoh in [#6287](https://github.com/gfx-rs/wgpu/pull/6287). ## v22.1.0 (2024-07-17) This release includes `wgpu`, `wgpu-core` and `naga`. All other crates remain at 22.0.0. ### Added #### naga - Added back implementations of PartialEq for more IR types. By @teoxoy in [#6045](https://github.com/gfx-rs/wgpu/pull/6045) ### Bug Fixes #### General - Fix profiling with `tracy`. By @waywardmonkeys in [#5988](https://github.com/gfx-rs/wgpu/pull/5988) - Fix function for checking bind compatibility to error instead of panic. By @sagudev [#6012](https://github.com/gfx-rs/wgpu/pull/6012) - Fix crash when dropping the surface after the device. By @wumpf in [#6052](https://github.com/gfx-rs/wgpu/pull/6052) - Fix length of copy in `queue_write_texture`. By @teoxoy in [#6009](https://github.com/gfx-rs/wgpu/pull/6009) - Fix error message that is thrown in create_render_pass to no longer say `compute_pass`. By @matthew-wong1 [#6041](https://github.com/gfx-rs/wgpu/pull/6041) - As a workaround for [issue #4905](https://github.com/gfx-rs/wgpu/issues/4905), `wgpu-core` is undocumented unless `--cfg wgpu_core_doc` feature is enabled. By @kpreid in [#5987](https://github.com/gfx-rs/wgpu/pull/5987) ## 22.0.0 (2024-07-17) ### Overview ### Our first major version release! For the first time ever, wgpu is being released with a major version (i.e., 22.\* instead of 0.22.\*)! Maintainership has decided to fully adhere to [Semantic Versioning](https://semver.org/)'s recommendations for versioning production software. According to [SemVer 2.0.0's Q&A about when to use 1.0.0 versions (and beyond)](https://semver.org/spec/v2.0.0.html#how-do-i-know-when-to-release-100): > ### How do I know when to release 1.0.0? > > If your software is being used in production, it should probably already be 1.0.0. If you have a stable API on which users have come to depend, you should be 1.0.0. If you’re worrying a lot about backward compatibility, you should probably already be 1.0.0. It is a well-known fact that wgpu has been used for applications and platforms already in production for years, at this point. We are often concerned with tracking breaking changes, and affecting these consumers' ability to ship. By releasing our first major version, we publicly acknowledge that this is the case. We encourage other projects in the Rust ecosystem to follow suit. Note that while we start to use the major version number, wgpu is _not_ "going stable", as many Rust projects do. We anticipate many breaking changes before we fully comply with the WebGPU spec., which we expect to take a small number of years. ### Overview A major ([pun intended](#our-first-major-version-release)) theme of this release is incremental improvement. Among the typically large set of bug fixes, new features, and other adjustments to wgpu by the many contributors listed below, @wumpf and @teoxoy have merged a series of many simplifications to wgpu's internals and, in one case, to the render and compute pass recording APIs. Many of these change wgpu to use atomically reference-counted resource tracking (i.e., `Arc<…>`), rather than using IDs to manage the lifetimes of platform-specific graphics resources in a registry of separate reference counts. This has led us to diagnose and fix many long-standing bugs, and net some neat performance improvements on the order of 40% or more of some workloads. While the above is exciting, we acknowledge already finding and fixing some (easy-to-fix) regressions from the above work. If you migrate to wgpu 22 and encounter such bugs, please engage us in the issue tracker right away! ### Major Changes #### Lifetime bounds on `wgpu::RenderPass` & `wgpu::ComputePass` `wgpu::RenderPass` & `wgpu::ComputePass` recording methods (e.g. `wgpu::RenderPass:set_render_pipeline`) no longer impose a lifetime constraint to objects passed to a pass (like pipelines/buffers/bindgroups/query-sets etc.). This means the following pattern works now as expected: ```rust let mut pipelines: Vec = ...; // ... let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor::default()); cpass.set_pipeline(&pipelines[123]); // Change pipeline container - this requires mutable access to `pipelines` while one of the pipelines is in use. pipelines.push(/* ... */); // Continue pass recording. cpass.set_bindgroup(...); ``` Previously, a set pipeline (or other resource) had to outlive pass recording which often affected wider systems, meaning that users needed to prove to the borrow checker that `Vec` (or similar constructs) aren't accessed mutably for the duration of pass recording. Furthermore, you can now opt out of `wgpu::RenderPass`/`wgpu::ComputePass`'s lifetime dependency on its parent `wgpu::CommandEncoder` using `wgpu::RenderPass::forget_lifetime`/`wgpu::ComputePass::forget_lifetime`: ```rust fn independent_cpass<'enc>(encoder: &'enc mut wgpu::CommandEncoder) -> wgpu::ComputePass<'static> { let cpass: wgpu::ComputePass<'enc> = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor::default()); cpass.forget_lifetime() } ``` ⚠️ As long as a `wgpu::RenderPass`/`wgpu::ComputePass` is pending for a given `wgpu::CommandEncoder`, creation of a compute or render pass is an error and invalidates the `wgpu::CommandEncoder`. `forget_lifetime` can be very useful for library authors, but opens up an easy way for incorrect use, so use with care. This method doesn't add any additional overhead and has no side effects on pass recording. By @wumpf in [#5569](https://github.com/gfx-rs/wgpu/pull/5569), [#5575](https://github.com/gfx-rs/wgpu/pull/5575), [#5620](https://github.com/gfx-rs/wgpu/pull/5620), [#5768](https://github.com/gfx-rs/wgpu/pull/5768) (together with @kpreid), [#5671](https://github.com/gfx-rs/wgpu/pull/5671), [#5794](https://github.com/gfx-rs/wgpu/pull/5794), [#5884](https://github.com/gfx-rs/wgpu/pull/5884). #### Querying shader compilation errors Wgpu now supports querying [shader compilation info](https://www.w3.org/TR/webgpu/#dom-gpushadermodule-getcompilationinfo). This allows you to get more structured information about compilation errors, warnings and info: ```rust ... let lighting_shader = ctx.device.create_shader_module(include_wgsl!("lighting.wgsl")); let compilation_info = lighting_shader.get_compilation_info().await; for message in compilation_info .messages .iter() .filter(|m| m.message_type == wgpu::CompilationMessageType::Error) { let line = message.location.map(|l| l.line_number).unwrap_or(1); println!("Compile error at line {line}"); } ``` By @stefnotch in [#5410](https://github.com/gfx-rs/wgpu/pull/5410) #### 64 bit integer atomic support in shaders. Add support for 64 bit integer atomic operations in shaders. Add the following flags to `wgpu_types::Features`: - `SHADER_INT64_ATOMIC_ALL_OPS` enables all atomic operations on `atomic` and `atomic` values. - `SHADER_INT64_ATOMIC_MIN_MAX` is a subset of the above, enabling only `AtomicFunction::Min` and `AtomicFunction::Max` operations on `atomic` and `atomic` values in the `Storage` address space. These are the only 64-bit atomic operations available on Metal as of 3.1. Add corresponding flags to `naga::valid::Capabilities`. These are supported by the WGSL front end, and all naga backends. Platform support: - On Direct3d 12, in `D3D12_FEATURE_DATA_D3D12_OPTIONS9`, if `AtomicInt64OnTypedResourceSupported` and `AtomicInt64OnGroupSharedSupported` are both available, then both wgpu features described above are available. - On Metal, `SHADER_INT64_ATOMIC_MIN_MAX` is available on Apple9 hardware, and on hardware that advertises both Apple8 and Mac2 support. This also requires Metal Shading Language 2.4 or later. Metal does not yet support the more general `SHADER_INT64_ATOMIC_ALL_OPS`. - On Vulkan, if the `VK_KHR_shader_atomic_int64` extension is available with both the `shader_buffer_int64_atomics` and `shader_shared_int64_atomics` features, then both wgpu features described above are available. By @atlv24 in [#5383](https://github.com/gfx-rs/wgpu/pull/5383) #### A compatible surface is now required for `request_adapter()` on WebGL2 + `enumerate_adapters()` is now native only. When targeting WebGL2, it has always been the case that a surface had to be created before calling `request_adapter()`. We now make this requirement explicit. Validation was also added to prevent configuring the surface with a device that doesn't share the same underlying WebGL2 context since this has never worked. Calling `enumerate_adapters()` when targeting WebGPU used to return an empty `Vec` and since we now require users to pass a compatible surface when targeting WebGL2, having `enumerate_adapters()` doesn't make sense. By @teoxoy in [#5901](https://github.com/gfx-rs/wgpu/pull/5901) ### New features #### General - Added `as_hal` for `Buffer` to access wgpu created buffers form wgpu-hal. By @JasondeWolff in [#5724](https://github.com/gfx-rs/wgpu/pull/5724) - `include_wgsl!` is now callable in const contexts by @9SMTM6 in [#5872](https://github.com/gfx-rs/wgpu/pull/5872) - Added memory allocation hints to `DeviceDescriptor` by @nical in [#5875](https://github.com/gfx-rs/wgpu/pull/5875) - `MemoryHints::Performance`, the default, favors performance over memory usage and will likely cause large amounts of VRAM to be allocated up-front. This hint is typically good for games. - `MemoryHints::MemoryUsage` favors memory usage over performance. This hint is typically useful for smaller applications or UI libraries. - `MemoryHints::Manual` allows the user to specify parameters for the underlying GPU memory allocator. These parameters are subject to change. - These hints may be ignored by some backends. Currently only the Vulkan and D3D12 backends take them into account. - Add `HTMLImageElement` and `ImageData` as external source for copying images. By @Valaphee in [#5668](https://github.com/gfx-rs/wgpu/pull/5668) #### naga - Added -D, --defines option to naga CLI to define preprocessor macros by @theomonnom in [#5859](https://github.com/gfx-rs/wgpu/pull/5859) - Added type upgrades to SPIR-V atomic support. Added related infrastructure. Tracking issue is [here](https://github.com/gfx-rs/wgpu/issues/4489). By @schell in [#5775](https://github.com/gfx-rs/wgpu/pull/5775). - Implement `WGSL`'s `unpack4xI8`,`unpack4xU8`,`pack4xI8` and `pack4xU8`. By @VlaDexa in [#5424](https://github.com/gfx-rs/wgpu/pull/5424) - Began work adding support for atomics to the SPIR-V frontend. Tracking issue is [here](https://github.com/gfx-rs/wgpu/issues/4489). By @schell in [#5702](https://github.com/gfx-rs/wgpu/pull/5702). - In hlsl-out, allow passing information about the fragment entry point to omit vertex outputs that are not in the fragment inputs. By @Imberflur in [#5531](https://github.com/gfx-rs/wgpu/pull/5531) - In spv-out, allow passing `acceleration_structure` as a function argument. By @kvark in [#5961](https://github.com/gfx-rs/wgpu/pull/5961) ```diff let writer: naga::back::hlsl::Writer = /* ... */; -writer.write(&module, &module_info); +writer.write(&module, &module_info, None); ``` - HLSL & MSL output can now be added conditionally on the target via the `msl-out-if-target-apple` and `hlsl-out-if-target-windows` features. This is used in wgpu-hal to no longer compile with MSL output when `metal` is enabled & MacOS isn't targeted and no longer compile with HLSL output when `dx12` is enabled & Windows isn't targeted. By @wumpf in [#5919](https://github.com/gfx-rs/wgpu/pull/5919) #### Vulkan - Added a `PipelineCache` resource to allow using Vulkan pipeline caches. By @DJMcNab in [#5319](https://github.com/gfx-rs/wgpu/pull/5319) #### WebGPU - Added support for pipeline-overridable constants to the WebGPU backend by @DouglasDwyer in [#5688](https://github.com/gfx-rs/wgpu/pull/5688) ### Changes #### General - Unconsumed vertex outputs are now always allowed. Removed `StageError::InputNotConsumed`, `Features::SHADER_UNUSED_VERTEX_OUTPUT`, and associated validation. By @Imberflur in [#5531](https://github.com/gfx-rs/wgpu/pull/5531) - Avoid introducing spurious features for optional dependencies. By @bjorn3 in [#5691](https://github.com/gfx-rs/wgpu/pull/5691) - `wgpu::Error` is now `Sync`, making it possible to be wrapped in `anyhow::Error` or `eyre::Report`. By @nolanderc in [#5820](https://github.com/gfx-rs/wgpu/pull/5820) - Added benchmark suite. By @cwfitzgerald in [#5694](https://github.com/gfx-rs/wgpu/pull/5694), compute passes by @wumpf in [#5767](https://github.com/gfx-rs/wgpu/pull/5767) - Improve performance of `.submit()` by 39-64% (`.submit()` + `.poll()` by 22-32%). By @teoxoy in [#5910](https://github.com/gfx-rs/wgpu/pull/5910) - The `trace` wgpu feature has been temporarily removed. By @teoxoy in [#5975](https://github.com/gfx-rs/wgpu/pull/5975) #### Metal - Removed the `link` Cargo feature. This was used to allow weakly linking frameworks. This can be achieved with putting something like the following in your `.cargo/config.toml` instead: ```toml [target.'cfg(target_vendor = "apple")'] rustflags = ["-C", "link-args=-weak_framework Metal -weak_framework QuartzCore -weak_framework CoreGraphics"] ``` By @madsmtm in [#5752](https://github.com/gfx-rs/wgpu/pull/5752) ### Bug Fixes #### General - Ensure render pipelines have at least 1 target. By @ErichDonGubler in [#5715](https://github.com/gfx-rs/wgpu/pull/5715) - `wgpu::ComputePass` now internally takes ownership of `QuerySet` for both `wgpu::ComputePassTimestampWrites` as well as timestamp writes and statistics query, fixing crashes when destroying `QuerySet` before ending the pass. By @wumpf in [#5671](https://github.com/gfx-rs/wgpu/pull/5671) - Validate resources passed during compute pass recording for mismatching device. By @wumpf in [#5779](https://github.com/gfx-rs/wgpu/pull/5779) - Fix staging buffers being destroyed too early. By @teoxoy in [#5910](https://github.com/gfx-rs/wgpu/pull/5910) - Fix attachment byte cost validation panicking with native only formats. By @teoxoy in [#5934](https://github.com/gfx-rs/wgpu/pull/5934) - [wgpu] Fix leaks from auto layout pipelines. By @teoxoy in [#5971](https://github.com/gfx-rs/wgpu/pull/5971) - [wgpu-core] Fix length of copy in `queue_write_texture` (causing UB). By @teoxoy in [#5973](https://github.com/gfx-rs/wgpu/pull/5973) - Add missing same device checks. By @teoxoy in [#5980](https://github.com/gfx-rs/wgpu/pull/5980) #### GLES / OpenGL - Fix `ClearColorF`, `ClearColorU` and `ClearColorI` commands being issued before `SetDrawColorBuffers` [#5666](https://github.com/gfx-rs/wgpu/pull/5666) - Replace `glClear` with `glClearBufferF` because `glDrawBuffers` requires that the ith buffer must be `COLOR_ATTACHMENTi` or `NONE` [#5666](https://github.com/gfx-rs/wgpu/pull/5666) - Return the unmodified version in driver_info. By @Valaphee in [#5753](https://github.com/gfx-rs/wgpu/pull/5753) #### naga - In spv-out don't decorate a `BindingArray`'s type with `Block` if the type is a struct with a runtime array by @Vecvec in [#5776](https://github.com/gfx-rs/wgpu/pull/5776) - Add `packed` as a keyword for GLSL by @kjarosh in [#5855](https://github.com/gfx-rs/wgpu/pull/5855) ## v0.20.2 (2024-06-12) This release force-bumps transitive dependencies of `wgpu` on `wgpu-core` and `wgpu-hal` to 0.21.1, to resolve some undefined behavior observable in the DX12 backend after upgrading to Rust 1.79 or later. ### Bug Fixes #### General - Fix a `CommandBuffer` leak. By @cwfitzgerald and @nical in [#5141](https://github.com/gfx-rs/wgpu/pull/5141) #### DX12 - Do not feed `&""` to `D3DCompile`, by @workingjubilee in [#5812](https://github.com/gfx-rs/wgpu/issues/5812). ## v0.20.1 (2024-06-12) This release included v0.21.0 of `wgpu-core` and `wgpu-hal`, due to breaking changes needed to solve vulkan validation issues. ### Bug Fixes This release fixes the validation errors whenever a surface is used with the vulkan backend. By @cwfitzgerald in [#5681](https://github.com/gfx-rs/wgpu/pull/5681). #### General - Clean up weak references to texture views and bind groups to prevent memory leaks. By @xiaopengli89 in [#5595](https://github.com/gfx-rs/wgpu/pull/5595). - Fix segfault on exit is queue & device are dropped before surface. By @sagudev in [#5640](https://github.com/gfx-rs/wgpu/pull/5640). #### Metal - Fix unrecognized selector crash on iOS 12. By @vladasz in [#5744](https://github.com/gfx-rs/wgpu/pull/5744). #### Vulkan - Fix enablement of subgroup ops extension on Vulkan devices that don't support Vulkan 1.3. By @cwfitzgerald in [#5624](https://github.com/gfx-rs/wgpu/pull/5624). #### GLES / OpenGL - Fix regression on OpenGL (EGL) where non-sRGB still used sRGB [#5642](https://github.com/gfx-rs/wgpu/pull/5642) #### naga - Work around shader consumers that have bugs handling `switch` statements with a single body for all cases. These are now written as `do {} while(false);` loops in hlsl-out and glsl-out. By @Imberflur in [#5654](https://github.com/gfx-rs/wgpu/pull/5654) - In hlsl-out, defer `continue` statements in switches by setting a flag and breaking from the switch. This allows such constructs to work with FXC which does not support `continue` within a switch. By @Imberflur in [#5654](https://github.com/gfx-rs/wgpu/pull/5654) ## v0.20.0 (2024-04-28) ### Major Changes #### Pipeline overridable constants Wgpu supports now [pipeline-overridable constants](https://www.w3.org/TR/webgpu/#dom-gpuprogrammablestage-constants) This allows you to define constants in wgsl like this: ```rust override some_factor: f32 = 42.1337; // Specifies a default of 42.1337 if it's not set. ``` And then set them at runtime like so on your pipeline consuming this shader: ```rust // ... fragment: Some(wgpu::FragmentState { compilation_options: wgpu::PipelineCompilationOptions { constants: &[("some_factor".to_owned(), 0.1234)].into(), // Sets `some_factor` to 0.1234. ..Default::default() }, // ... }), // ... ``` By @teoxoy & @jimblandy in [#5500](https://github.com/gfx-rs/wgpu/pull/5500) #### Changed feature requirements for timestamps Due to a specification change `write_timestamp` is no longer supported on WebGPU. `wgpu::CommandEncoder::write_timestamp` requires now the new `wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS` feature which is available on all native backends but not on WebGPU. By @wumpf in [#5188](https://github.com/gfx-rs/wgpu/pull/5188) #### Wgsl const evaluation for many more built-ins Many numeric built-ins have had a constant evaluation implementation added for them, which allows them to be used in a `const` context: `abs`, `acos`, `acosh`, `asin`, `asinh`, `atan`, `atanh`, `cos`, `cosh`, `round`, `saturate`, `sin`, `sinh`, `sqrt`, `step`, `tan`, `tanh`, `ceil`, `countLeadingZeros`, `countOneBits`, `countTrailingZeros`, `degrees`, `exp`, `exp2`, `floor`, `fract`, `fma`, `inverseSqrt`, `log`, `log2`, `max`, `min`, `radians`, `reverseBits`, `sign`, `trunc` By @ErichDonGubler in [#4879](https://github.com/gfx-rs/wgpu/pull/4879), [#5098](https://github.com/gfx-rs/wgpu/pull/5098) #### New **native-only** wgsl features ##### Subgroup operations The following subgroup operations are available in wgsl now: `subgroupBallot`, `subgroupAll`, `subgroupAny`, `subgroupAdd`, `subgroupMul`, `subgroupMin`, `subgroupMax`, `subgroupAnd`, `subgroupOr`, `subgroupXor`, `subgroupExclusiveAdd`, `subgroupExclusiveMul`, `subgroupInclusiveAdd`, `subgroupInclusiveMul`, `subgroupBroadcastFirst`, `subgroupBroadcast`, `subgroupShuffle`, `subgroupShuffleDown`, `subgroupShuffleUp`, `subgroupShuffleXor` Availability is governed by the following feature flags: - `wgpu::Features::SUBGROUP` for all operations except `subgroupBarrier` in fragment & compute, supported on Vulkan, DX12 and Metal. - `wgpu::Features::SUBGROUP_VERTEX`, for all operations except `subgroupBarrier` general operations in vertex shaders, supported on Vulkan - `wgpu::Features::SUBGROUP_BARRIER`, for support of the `subgroupBarrier` operation, supported on Vulkan & Metal Note that there currently [some differences](https://github.com/gfx-rs/wgpu/issues/5555) between wgpu's native-only implementation and the [open WebGPU proposal](https://github.com/gpuweb/gpuweb/blob/main/proposals/subgroups.md). By @exrook and @lichtso in [#5301](https://github.com/gfx-rs/wgpu/pull/5301) ##### Signed and unsigned 64 bit integer support in shaders. `wgpu::Features::SHADER_INT64` enables 64 bit integer signed and unsigned integer variables in wgsl (`i64` and `u64` respectively). Supported on Vulkan, DX12 (requires DXC) and Metal (with MSL 2.3+ support). By @atlv24 and @cwfitzgerald in [#5154](https://github.com/gfx-rs/wgpu/pull/5154) ### New features #### General - Implemented the `Unorm10_10_10_2` VertexFormat by @McMackety in [#5477](https://github.com/gfx-rs/wgpu/pull/5477) - `wgpu-types`'s `trace` and `replay` features have been replaced by the `serde` feature. By @KirmesBude in [#5149](https://github.com/gfx-rs/wgpu/pull/5149) - `wgpu-core`'s `serial-pass` feature has been removed. Use `serde` instead. By @KirmesBude in [#5149](https://github.com/gfx-rs/wgpu/pull/5149) - Added `InstanceFlags::GPU_BASED_VALIDATION`, which enables GPU-based validation for shaders. This is currently only supported on the DX12 and Vulkan backends; other platforms ignore this flag, for now. By @ErichDonGubler in [#5146](https://github.com/gfx-rs/wgpu/pull/5146), [#5046](https://github.com/gfx-rs/wgpu/pull/5046). - When set, this flag implies `InstanceFlags::VALIDATION`. - This has been added to the set of flags set by `InstanceFlags::advanced_debugging`. Since the overhead is potentially very large, the flag is not enabled by default in debug builds when using `InstanceFlags::from_build_config`. - As with other instance flags, this flag can be changed in calls to `InstanceFlags::with_env` with the new `WGPU_GPU_BASED_VALIDATION` environment variable. - `wgpu::Instance` can now report which `wgpu::Backends` are available based on the build configuration. By @wumpf [#5167](https://github.com/gfx-rs/wgpu/pull/5167) ```diff -wgpu::Instance::any_backend_feature_enabled() +!wgpu::Instance::enabled_backend_features().is_empty() ``` - Breaking change: [`wgpu_core::pipeline::ProgrammableStageDescriptor`](https://docs.rs/wgpu-core/latest/wgpu_core/pipeline/struct.ProgrammableStageDescriptor.html#structfield.entry_point) is now optional. By @ErichDonGubler in [#5305](https://github.com/gfx-rs/wgpu/pull/5305). - `Features::downlevel{_webgl2,}_features` was made const by @MultisampledNight in [#5343](https://github.com/gfx-rs/wgpu/pull/5343) - Breaking change: [`wgpu_core::pipeline::ShaderError`](https://docs.rs/wgpu-core/latest/wgpu_core/pipeline/struct.ShaderError.html) has been moved to `naga`. By @stefnotch in [#5410](https://github.com/gfx-rs/wgpu/pull/5410) - More as_hal methods and improvements by @JMS55 in [#5452](https://github.com/gfx-rs/wgpu/pull/5452) - Added `wgpu::CommandEncoder::as_hal_mut` - Added `wgpu::TextureView::as_hal` - `wgpu::Texture::as_hal` now returns a user-defined type to match the other as_hal functions #### naga - Allow user to select which MSL version to use via `--metal-version` with naga CLI. By @pcleavelin in [#5392](https://github.com/gfx-rs/wgpu/pull/5392) - Support `arrayLength` for runtime-sized arrays inside binding arrays (for WGSL input and SPIR-V output). By @kvark in [#5428](https://github.com/gfx-rs/wgpu/pull/5428) - Added `--shader-stage` and `--input-kind` options to naga-cli for specifying vertex/fragment/compute shaders, and frontend. by @ratmice in [#5411](https://github.com/gfx-rs/wgpu/pull/5411) - Added a `create_validator` function to wgpu_core `Device` to create naga `Validator`s. By @atlv24 [#5606](https://github.com/gfx-rs/wgpu/pull/5606) #### WebGPU - Implement the `device_set_device_lost_callback` method for `ContextWebGpu`. By @suti in [#5438](https://github.com/gfx-rs/wgpu/pull/5438) - Add support for storage texture access modes `ReadOnly` and `ReadWrite`. By @JolifantoBambla in [#5434](https://github.com/gfx-rs/wgpu/pull/5434) #### GLES / OpenGL - Log an error when GLES texture format heuristics fail. By @PolyMeilex in [#5266](https://github.com/gfx-rs/wgpu/issues/5266) - Cache the sample count to keep `get_texture_format_features` cheap. By @Dinnerbone in [#5346](https://github.com/gfx-rs/wgpu/pull/5346) - Mark `DEPTH32FLOAT_STENCIL8` as supported in GLES. By @Dinnerbone in [#5370](https://github.com/gfx-rs/wgpu/pull/5370) - Desktop GL now also supports `TEXTURE_COMPRESSION_ETC2`. By @Valaphee in [#5568](https://github.com/gfx-rs/wgpu/pull/5568) - Don't create a program for shader-clearing if that workaround isn't required. By @Dinnerbone in [#5348](https://github.com/gfx-rs/wgpu/pull/5348). - OpenGL will now be preferred over OpenGL ES on EGL, making it consistent with WGL. By @valaphee in [#5482](https://github.com/gfx-rs/wgpu/pull/5482) - Fill out `driver` and `driver_info`, with the OpenGL flavor and version, similar to Vulkan. By @valaphee in [#5482](https://github.com/gfx-rs/wgpu/pull/5482) #### Metal - Metal 3.0 and 3.1 detection. By @atlv24 in [#5497](https://github.com/gfx-rs/wgpu/pull/5497) #### DX12 - Shader Model 6.1-6.7 detection. By @atlv24 in [#5498](https://github.com/gfx-rs/wgpu/pull/5498) ### Other performance improvements - Simplify and speed up the allocation of internal IDs. By @nical in [#5229](https://github.com/gfx-rs/wgpu/pull/5229) - Use memory pooling for UsageScopes to avoid frequent large allocations. by @robtfm in [#5414](https://github.com/gfx-rs/wgpu/pull/5414) - Eager release of GPU resources comes from device.trackers. By @bradwerth in [#5075](https://github.com/gfx-rs/wgpu/pull/5075) - Support disabling zero-initialization of workgroup local memory in compute shaders. By @DJMcNab in [#5508](https://github.com/gfx-rs/wgpu/pull/5508) ### Documentation - Improved `wgpu_hal` documentation. By @jimblandy in [#5516](https://github.com/gfx-rs/wgpu/pull/5516), [#5524](https://github.com/gfx-rs/wgpu/pull/5524), [#5562](https://github.com/gfx-rs/wgpu/pull/5562), [#5563](https://github.com/gfx-rs/wgpu/pull/5563), [#5566](https://github.com/gfx-rs/wgpu/pull/5566), [#5617](https://github.com/gfx-rs/wgpu/pull/5617), [#5618](https://github.com/gfx-rs/wgpu/pull/5618) - Add mention of primitive restart in the description of `PrimitiveState::strip_index_format`. By @cpsdqs in [#5350](https://github.com/gfx-rs/wgpu/pull/5350) - Document and tweak precise behaviour of `SourceLocation`. By @stefnotch in [#5386](https://github.com/gfx-rs/wgpu/pull/5386) and [#5410](https://github.com/gfx-rs/wgpu/pull/5410) - Give short example of WGSL `push_constant` syntax. By @waywardmonkeys in [#5393](https://github.com/gfx-rs/wgpu/pull/5393) - Fix incorrect documentation of `Limits::max_compute_workgroup_storage_size` default value. By @atlv24 in [#5601](https://github.com/gfx-rs/wgpu/pull/5601) ### Bug Fixes #### General - Fix `serde` feature not compiling for `wgpu-types`. By @KirmesBude in [#5149](https://github.com/gfx-rs/wgpu/pull/5149) - Fix the validation of vertex and index ranges. By @nical in [#5144](https://github.com/gfx-rs/wgpu/pull/5144) and [#5156](https://github.com/gfx-rs/wgpu/pull/5156) - Fix panic when creating a surface while no backend is available. By @wumpf [#5166](https://github.com/gfx-rs/wgpu/pull/5166) - Correctly compute minimum buffer size for array-typed `storage` and `uniform` vars. By @jimblandy [#5222](https://github.com/gfx-rs/wgpu/pull/5222) - Fix timeout when presenting a surface where no work has been done. By @waywardmonkeys in [#5200](https://github.com/gfx-rs/wgpu/pull/5200) - Fix registry leaks with de-duplicated resources. By @nical in [#5244](https://github.com/gfx-rs/wgpu/pull/5244) - Fix linking when targeting android. By @ashdnazg in [#5326](https://github.com/gfx-rs/wgpu/pull/5326). - Failing to set the device lost closure will call the closure before returning. By @bradwerth in [#5358](https://github.com/gfx-rs/wgpu/pull/5358). - Fix deadlocks caused by recursive read-write lock acquisitions [#5426](https://github.com/gfx-rs/wgpu/pull/5426). - Remove exposed C symbols (`extern "C"` + [no_mangle]) from RenderPass & ComputePass recording. By @wumpf in [#5409](https://github.com/gfx-rs/wgpu/pull/5409). - Fix surfaces being only compatible with first backend enabled on an instance, causing failures when manually specifying an adapter. By @Wumpf in [#5535](https://github.com/gfx-rs/wgpu/pull/5535). #### naga - In spv-in, remove unnecessary "gl_PerVertex" name check so unused builtins will always be skipped. Prevents validation errors caused by capability requirements of these builtins [#4915](https://github.com/gfx-rs/wgpu/issues/4915). By @Imberflur in [#5227](https://github.com/gfx-rs/wgpu/pull/5227). - In spv-out, check for acceleration and ray-query types when enabling ray-query extension to prevent validation error. By @Vecvec in [#5463](https://github.com/gfx-rs/wgpu/pull/5463) - Add a limit for curly brace nesting in WGSL parsing, plus a note about stack size requirements. By @ErichDonGubler in [#5447](https://github.com/gfx-rs/wgpu/pull/5447). - In hlsl-out, fix accesses on zero value expressions by generating helper functions for `Expression::ZeroValue`. By @Imberflur in [#5587](https://github.com/gfx-rs/wgpu/pull/5587). - Fix behavior of `extractBits` and `insertBits` when `offset + count` overflows the bit width. By @cwfitzgerald in [#5305](https://github.com/gfx-rs/wgpu/pull/5305) - Fix behavior of integer `clamp` when `min` argument > `max` argument. By @cwfitzgerald in [#5300](https://github.com/gfx-rs/wgpu/pull/5300). - Fix `TypeInner::scalar_width` to be consistent with the rest of the codebase and return values in bytes not bits. By @atlv24 in [#5532](https://github.com/gfx-rs/wgpu/pull/5532). #### GLES / OpenGL - GLSL 410 does not support layout(binding = ...), enable only for GLSL 420. By @bes in [#5357](https://github.com/gfx-rs/wgpu/pull/5357) - Fixes for being able to use an OpenGL 4.1 core context provided by macOS with wgpu. By @bes in [#5331](https://github.com/gfx-rs/wgpu/pull/5331). - Fix crash when holding multiple devices on wayland/surfaceless. By @ashdnazg in [#5351](https://github.com/gfx-rs/wgpu/pull/5351). - Fix `first_instance` getting ignored in draw indexed when `ARB_shader_draw_parameters` feature is present and `base_vertex` is 0. By @valaphee in [#5482](https://github.com/gfx-rs/wgpu/pull/5482) #### Vulkan - Set object labels when the DEBUG flag is set, even if the VALIDATION flag is disabled. By @DJMcNab in [#5345](https://github.com/gfx-rs/wgpu/pull/5345). - Add safety check to `wgpu_hal::vulkan::CommandEncoder` to make sure `discard_encoding` is not called in the closed state. By @villuna in [#5557](https://github.com/gfx-rs/wgpu/pull/5557) - Fix SPIR-V type capability requests to not depend on `LocalType` caching. By @atlv24 in [#5590](https://github.com/gfx-rs/wgpu/pull/5590) - Upgrade `ash` to `0.38`. By @MarijnS95 in [#5504](https://github.com/gfx-rs/wgpu/pull/5504). #### Tests - Fix intermittent crashes on Linux in the `multithreaded_compute` test. By @jimblandy in [#5129](https://github.com/gfx-rs/wgpu/pull/5129). - Refactor tests to read feature flags by name instead of a hardcoded hexadecimal u64. By @atlv24 in [#5155](https://github.com/gfx-rs/wgpu/pull/5155). - Add test that verifies that we can drop the queue before using the device to create a command encoder. By @Davidster in [#5211](https://github.com/gfx-rs/wgpu/pull/5211) ## 0.19.5 (2024-07-16) This release only releases `wgpu-hal` 0.19.5, which contains an important fix for DX12. ### Bug Fixes #### DX12 - Do not feed `&""` to `D3DCompile`, by @workingjubilee in [#5812](https://github.com/gfx-rs/wgpu/issues/5812), backported by @Elabajaba in [#5833](https://github.com/gfx-rs/wgpu/pull/5833). ## v0.19.4 (2024-04-17) ### Bug Fixes #### General - Don't depend on bind group and bind group layout entry order in backends. This caused incorrect severely incorrect command execution and, in some cases, crashes. By @ErichDonGubler in [#5421](https://github.com/gfx-rs/wgpu/pull/5421). - Properly clean up all write_buffer/texture temporary resources. By @robtfm in [#5413](https://github.com/gfx-rs/wgpu/pull/5413). - Fix deadlock in certain situations when mapping buffers using `wgpu-profiler`. By @cwfitzgerald in [#5517](https://github.com/gfx-rs/wgpu/pull/5517) #### WebGPU - Correctly pass through timestamp queries to WebGPU. By @cwfitzgerald in [#5527](https://github.com/gfx-rs/wgpu/pull/5527). ## v0.19.3 (2024-03-01) This release includes `wgpu`, `wgpu-core`, and `wgpu-hal`. All other crates are unchanged. ### Major Changes #### Vendored WebGPU Bindings from `web_sys` **`--cfg=web_sys_unstable_apis` is no longer needed in your `RUSTFLAGS` to compile for WebGPU!!!** While WebGPU's javascript api is stable in the browsers, the `web_sys` bindings for WebGPU are still improving. As such they are hidden behind the special cfg `--cfg=web_sys_unstable_apis` and are not available by default. Everyone who wanted to use our WebGPU backend needed to enable this cfg in their `RUSTFLAGS`. This was very inconvenient and made it hard to use WebGPU, especially when WebGPU is enabled by default. Additionally, the unstable APIs don't adhere to semver, so there were repeated breakages. To combat this problem we have decided to vendor the `web_sys` bindings for WebGPU within the crate. Notably we are not forking the bindings, merely vendoring, so any improvements we make to the bindings will be contributed directly to upstream `web_sys`. By @cwfitzgerald in [#5325](https://github.com/gfx-rs/wgpu/pull/5325). ### Bug Fixes #### General - Fix an issue where command encoders weren't properly freed if an error occurred during command encoding. By @ErichDonGubler in [#5251](https://github.com/gfx-rs/wgpu/pull/5251). - Fix incorrect validation causing all indexed draws on render bundles to fail. By @wumpf in [#5430](https://github.com/gfx-rs/wgpu/pull/5340). #### Android - Fix linking error when targeting android without `winit`. By @ashdnazg in [#5326](https://github.com/gfx-rs/wgpu/pull/5326). ## v0.19.2 (2024-02-29) This release includes `wgpu`, `wgpu-core`, `wgpu-hal`, `wgpu-types`, and `naga`. All other crates are unchanged. ### Added/New Features #### General - `wgpu::Id` now implements `PartialOrd`/`Ord` allowing it to be put in `BTreeMap`s. By @cwfitzgerald and @9291Sam in [#5176](https://github.com/gfx-rs/wgpu/pull/5176) #### OpenGL - Log an error when OpenGL texture format heuristics fail. By @PolyMeilex in [#5266](https://github.com/gfx-rs/wgpu/issues/5266) #### `wgsl-out` - Learned to generate acceleration structure types. By @JMS55 in [#5261](https://github.com/gfx-rs/wgpu/pull/5261) ### Documentation - Fix link in `wgpu::Instance::create_surface` documentation. By @HexoKnight in [#5280](https://github.com/gfx-rs/wgpu/pull/5280). - Fix typo in `wgpu::CommandEncoder::clear_buffer` documentation. By @PWhiddy in [#5281](https://github.com/gfx-rs/wgpu/pull/5281). - `Surface` configuration incorrectly claimed that `wgpu::Instance::create_surface` was unsafe. By @hackaugusto in [#5265](https://github.com/gfx-rs/wgpu/pull/5265). ### Bug Fixes #### General - Device lost callbacks are invoked when replaced and when global is dropped. By @bradwerth in [#5168](https://github.com/gfx-rs/wgpu/pull/5168) - Fix performance regression when allocating a large amount of resources of the same type. By @nical in [#5229](https://github.com/gfx-rs/wgpu/pull/5229) - Fix docs.rs wasm32 builds. By @cwfitzgerald in [#5310](https://github.com/gfx-rs/wgpu/pull/5310) - Improve error message when binding count limit hit. By @hackaugusto in [#5298](https://github.com/gfx-rs/wgpu/pull/5298) - Remove an unnecessary `clone` during GLSL shader ingestion. By @a1phyr in [#5118](https://github.com/gfx-rs/wgpu/pull/5118). - Fix missing validation for `Device::clear_buffer` where `offset + size > buffer.size` was not checked when `size` was omitted. By @ErichDonGubler in [#5282](https://github.com/gfx-rs/wgpu/pull/5282). #### DX12 - Fix `panic!` when dropping `Instance` without `InstanceFlags::VALIDATION`. By @hakolao in [#5134](https://github.com/gfx-rs/wgpu/pull/5134) #### OpenGL - Fix internal format for the `Etc2Rgba8Unorm` format. By @andristarr in [#5178](https://github.com/gfx-rs/wgpu/pull/5178) - Try to load `libX11.so.6` in addition to `libX11.so` on linux. [#5307](https://github.com/gfx-rs/wgpu/pull/5307) - Make use of `GL_EXT_texture_shadow_lod` to support sampling a cube depth texture with an explicit LOD. By @cmrschwarz in #[5171](https://github.com/gfx-rs/wgpu/pull/5171). #### `glsl-in` - Fix code generation from nested loops. By @cwfitzgerald and @teoxoy in [#5311](https://github.com/gfx-rs/wgpu/pull/5311) ## v0.19.1 (2024-01-22) This release includes `wgpu` and `wgpu-hal`. The rest of the crates are unchanged since 0.19.0. ### Bug Fixes #### DX12 - Properly register all swapchain buffers to prevent error on surface present. By @dtzxporter in [#5091](https://github.com/gfx-rs/wgpu/pull/5091) - Check for extra null states when creating resources. By @nical in [#5096](https://github.com/gfx-rs/wgpu/pull/5096) - Fix depth-only and stencil-only views causing crashes. By @teoxoy in [#5100](https://github.com/gfx-rs/wgpu/pull/5100) #### OpenGL - In Surface::configure and Surface::present on Windows, fix the current GL context not being unset when releasing the lock that guards access to making the context current. This was causing other threads to panic when trying to make the context current. By @Imberflur in [#5087](https://github.com/gfx-rs/wgpu/pull/5087). #### WebGPU - Improve error message when compiling WebGPU backend on wasm without the `web_sys_unstable_apis` set. By @rukai in [#5104](https://github.com/gfx-rs/wgpu/pull/5104) ### Documentation - Document Wayland specific behavior related to `SurfaceTexture::present`. By @i509VCB in [#5093](https://github.com/gfx-rs/wgpu/pull/5093). ## v0.19.0 (2024-01-17) This release includes: - `wgpu` - `wgpu-core` - `wgpu-hal` - `wgpu-types` - `wgpu-info` - `naga` (skipped from 0.14 to 0.19) - `naga-cli` (skipped from 0.14 to 0.19) - `d3d12` (skipped from 0.7 to 0.19) ### Improved Multithreading through internal use of Reference Counting Large refactoring of wgpu’s internals aiming at reducing lock contention, and providing better performance when using wgpu on multiple threads. [Check the blog post!](https://gfx-rs.github.io/2023/11/24/arcanization.html) By @gents83 in [#3626](https://github.com/gfx-rs/wgpu/pull/3626) and thanks also to @jimblandy, @nical, @Wumpf, @Elabajaba & @cwfitzgerald ### All Public Dependencies are Re-Exported All of wgpu's public dependencies are now re-exported at the top level so that users don't need to take their own dependencies. This includes: - wgpu-core - wgpu-hal - naga - raw_window_handle - web_sys ### Feature Flag Changes #### WebGPU & WebGL in the same Binary Enabling `webgl` no longer removes the `webgpu` backend. Instead, there's a new (default enabled) `webgpu` feature that allows to explicitly opt-out of `webgpu` if so desired. If both `webgl` & `webgpu` are enabled, `wgpu::Instance` decides upon creation whether to target wgpu-core/WebGL or WebGPU. This means that adapter selection is not handled as with regular adapters, but still allows to decide at runtime whether `webgpu` or the `webgl` backend should be used using a single wasm binary. By @wumpf in [#5044](https://github.com/gfx-rs/wgpu/pull/5044) #### `naga-ir` Dedicated Feature The `naga-ir` feature has been added to allow you to add naga module shaders without guessing about what other features needed to be enabled to get access to it. By @cwfitzgerald in [#5063](https://github.com/gfx-rs/wgpu/pull/5063). #### `expose-ids` Feature available unconditionally This feature allowed you to call `global_id` on any wgpu opaque handle to get a unique hashable identity for the given resource. This is now available without the feature flag. By @cwfitzgerald in [#4841](https://github.com/gfx-rs/wgpu/pull/4841). #### `dx12` and `metal` Backend Crate Features wgpu now exposes backend feature for the Direct3D 12 (`dx12`) and Metal (`metal`) backend. These are enabled by default, but don't do anything when not targeting the corresponding OS. By @daxpedda in [#4815](https://github.com/gfx-rs/wgpu/pull/4815). ### Direct3D 11 Backend Removal This backend had no functionality, and with the recent support for GL on Desktop, which allows wgpu to run on older devices, there was no need to keep this backend. By @valaphee in [#4828](https://github.com/gfx-rs/wgpu/pull/4828). ### `WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER` Environment Variable This adds a way to allow a Vulkan driver which is non-compliant per `VK_KHR_driver_properties` to be enumerated. This is intended for testing new Vulkan drivers which are not Vulkan compliant yet. By @i509VCB in [#4754](https://github.com/gfx-rs/wgpu/pull/4754). ### `DeviceExt::create_texture_with_data` allows Mip-Major Data Previously, `DeviceExt::create_texture_with_data` only allowed data to be provided in layer major order. There is now a `order` parameter which allows you to specify if the data is in layer major or mip major order. ```diff let tex = ctx.device.create_texture_with_data( &queue, &descriptor, + wgpu::util::TextureDataOrder::LayerMajor, src_data, ); ``` By @cwfitzgerald in [#4780](https://github.com/gfx-rs/wgpu/pull/4780). ### Safe & unified Surface Creation It is now possible to safely create a `wgpu::Surface` with `wgpu::Instance::create_surface()` by letting `wgpu::Surface` hold a lifetime to `window`. Passing an owned value `window` to `Surface` will return a `wgpu::Surface<'static>`. All possible safe variants (owned windows and web canvases) are grouped using `wgpu::SurfaceTarget`. Conversion to `wgpu::SurfaceTarget` is automatic for any type implementing `raw-window-handle`'s `HasWindowHandle` & `HasDisplayHandle` traits, i.e. most window types. For web canvas types this has to be done explicitly: ```rust let surface: wgpu::Surface<'static> = instance.create_surface(wgpu::SurfaceTarget::Canvas(my_canvas))?; ``` All unsafe variants are now grouped under `wgpu::Instance::create_surface_unsafe` which takes the `wgpu::SurfaceTargetUnsafe` enum and always returns `wgpu::Surface<'static>`. In order to create a `wgpu::Surface<'static>` without passing ownership of the window use `wgpu::SurfaceTargetUnsafe::from_window`: ```rust let surface = unsafe { instance.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::from_window(&my_window))? }; ``` The easiest way to make this code safe is to use shared ownership: ```rust let window: Arc; // ... let surface = instance.create_surface(window.clone())?; ``` All platform specific surface creation using points have moved into `SurfaceTargetUnsafe` as well. For example: Safety by @daxpedda in [#4597](https://github.com/gfx-rs/wgpu/pull/4597) Unification by @wumpf in [#4984](https://github.com/gfx-rs/wgpu/pull/4984) ### Add partial Support for WGSL Abstract Types Abstract types make numeric literals easier to use, by automatically converting literals and other constant expressions from abstract numeric types to concrete types when safe and necessary. For example, to build a vector of floating-point numbers, naga previously made you write: ```rust vec3(1.0, 2.0, 3.0) ``` With this change, you can now simply write: ```rust vec3(1, 2, 3) ``` Even though the literals are abstract integers, naga recognizes that it is safe and necessary to convert them to `f32` values in order to build the vector. You can also use abstract values as initializers for global constants and global and local variables, like this: ```rust var unit_x: vec2 = vec2(1, 0); ``` The literals `1` and `0` are abstract integers, and the expression `vec2(1, 0)` is an abstract vector. However, naga recognizes that it can convert that to the concrete type `vec2` to satisfy the given type of `unit_x`. The WGSL specification permits abstract integers and floating-point values in almost all contexts, but naga's support for this is still incomplete. Many WGSL operators and builtin functions are specified to produce abstract results when applied to abstract inputs, but for now naga simply concretizes them all before applying the operation. We will expand naga's abstract type support in subsequent pull requests. As part of this work, the public types `naga::ScalarKind` and `naga::Literal` now have new variants, `AbstractInt` and `AbstractFloat`. By @jimblandy in [#4743](https://github.com/gfx-rs/wgpu/pull/4743), [#4755](https://github.com/gfx-rs/wgpu/pull/4755). ### `Instance::enumerate_adapters` now returns `Vec` instead of an `ExactSizeIterator` This allows us to support WebGPU and WebGL in the same binary. ```diff - let adapters: Vec = instance.enumerate_adapters(wgpu::Backends::all()).collect(); + let adapters: Vec = instance.enumerate_adapters(wgpu::Backends::all()); ``` By @wumpf in [#5044](https://github.com/gfx-rs/wgpu/pull/5044) ### `device.poll()` now returns a `MaintainResult` instead of a `bool` This is a forward looking change, as we plan to add more information to the `MaintainResult` in the future. This enum has the same data as the boolean, but with some useful helper functions. ```diff - let queue_finished: bool = device.poll(wgpu::Maintain::Wait); + let queue_finished: bool = device.poll(wgpu::Maintain::Wait).is_queue_empty(); ``` By @cwfitzgerald in [#5053](https://github.com/gfx-rs/wgpu/pull/5053) ### New Features #### General - Added `DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW` to know if `@builtin(vertex_index)` and `@builtin(instance_index)` will respect the `first_vertex` / `first_instance` in indirect calls. If this is not present, both will always start counting from 0. Currently enabled on all backends except DX12. By @cwfitzgerald in [#4722](https://github.com/gfx-rs/wgpu/pull/4722). - Added support for the `FLOAT32_FILTERABLE` feature (web and native, corresponds to WebGPU's `float32-filterable`). By @almarklein in [#4759](https://github.com/gfx-rs/wgpu/pull/4759). - GPU buffer memory is released during "lose the device". By @bradwerth in [#4851](https://github.com/gfx-rs/wgpu/pull/4851). - wgpu and wgpu-core cargo feature flags are now documented on docs.rs. By @wumpf in [#4886](https://github.com/gfx-rs/wgpu/pull/4886). - DeviceLostClosure is guaranteed to be invoked exactly once. By @bradwerth in [#4862](https://github.com/gfx-rs/wgpu/pull/4862). - Log vulkan validation layer messages during instance creation and destruction: By @exrook in [#4586](https://github.com/gfx-rs/wgpu/pull/4586). - `TextureFormat::block_size` is deprecated, use `TextureFormat::block_copy_size` instead: By @wumpf in [#4647](https://github.com/gfx-rs/wgpu/pull/4647). - Rename of `DispatchIndirect`, `DrawIndexedIndirect`, and `DrawIndirect` types in the `wgpu::util` module to `DispatchIndirectArgs`, `DrawIndexedIndirectArgs`, and `DrawIndirectArgs`. By @cwfitzgerald in [#4723](https://github.com/gfx-rs/wgpu/pull/4723). - Make the size parameter of `encoder.clear_buffer` an `Option` instead of `Option>`. By @nical in [#4737](https://github.com/gfx-rs/wgpu/pull/4737). - Reduce the `info` log level noise. By @nical in [#4769](https://github.com/gfx-rs/wgpu/pull/4769), [#4711](https://github.com/gfx-rs/wgpu/pull/4711) and [#4772](https://github.com/gfx-rs/wgpu/pull/4772) - Rename `features` & `limits` fields of `DeviceDescriptor` to `required_features` & `required_limits`. By @teoxoy in [#4803](https://github.com/gfx-rs/wgpu/pull/4803). - `SurfaceConfiguration` now exposes `desired_maximum_frame_latency` which was previously hard-coded to 2. By setting it to 1 you can reduce latency under the risk of making GPU & CPU work sequential. Currently, on DX12 this affects the `MaximumFrameLatency`, on all other backends except OpenGL the size of the swapchain (on OpenGL this has no effect). By @emilk & @wumpf in [#4899](https://github.com/gfx-rs/wgpu/pull/4899) #### OpenGL - `@builtin(instance_index)` now properly reflects the range provided in the draw call instead of always counting from 0. By @cwfitzgerald in [#4722](https://github.com/gfx-rs/wgpu/pull/4722). - Desktop GL now supports `POLYGON_MODE_LINE` and `POLYGON_MODE_POINT`. By @valaphee in [#4836](https://github.com/gfx-rs/wgpu/pull/4836). #### naga - naga's WGSL front end now allows operators to produce values with abstract types, rather than concretizing their operands. By @jimblandy in [#4850](https://github.com/gfx-rs/wgpu/pull/4850) and [#4870](https://github.com/gfx-rs/wgpu/pull/4870). - naga's WGSL front and back ends now have experimental support for 64-bit floating-point literals: `1.0lf` denotes an `f64` value. There has been experimental support for an `f64` type for a while, but until now there was no syntax for writing literals with that type. As before, naga module validation rejects `f64` values unless `naga::valid::Capabilities::FLOAT64` is requested. By @jimblandy in [#4747](https://github.com/gfx-rs/wgpu/pull/4747). - naga constant evaluation can now process binary operators whose operands are both vectors. By @jimblandy in [#4861](https://github.com/gfx-rs/wgpu/pull/4861). - Add `--bulk-validate` option to naga CLI. By @jimblandy in [#4871](https://github.com/gfx-rs/wgpu/pull/4871). - naga's `cargo xtask validate` now runs validation jobs in parallel, using the [jobserver](https://crates.io/crates/jobserver) protocol to limit concurrency, and offers a `validate all` subcommand, which runs all available validation types. By @jimblandy in [#4902](https://github.com/gfx-rs/wgpu/pull/4902). - Remove `span` and `validate` features. Always fully validate shader modules, and always track source positions for use in error messages. By @teoxoy in [#4706](https://github.com/gfx-rs/wgpu/pull/4706). - Introduce a new `Scalar` struct type for use in naga's IR, and update all frontend, middle, and backend code appropriately. By @jimblandy in [#4673](https://github.com/gfx-rs/wgpu/pull/4673). - Add more metal keywords. By @fornwall in [#4707](https://github.com/gfx-rs/wgpu/pull/4707). - Add a new `naga::Literal` variant, `I64`, for signed 64-bit literals. [#4711](https://github.com/gfx-rs/wgpu/pull/4711). - Emit and init `struct` member padding always. By @ErichDonGubler in [#4701](https://github.com/gfx-rs/wgpu/pull/4701). - In WGSL output, always include the `i` suffix on `i32` literals. By @jimblandy in [#4863](https://github.com/gfx-rs/wgpu/pull/4863). - In WGSL output, always include the `f` suffix on `f32` literals. By @jimblandy in [#4869](https://github.com/gfx-rs/wgpu/pull/4869). ### Bug Fixes #### General - `BufferMappedRange` trait is now `WasmNotSendSync`, i.e. it is `Send`/`Sync` if not on wasm or `fragile-send-sync-non-atomic-wasm` is enabled. By @wumpf in [#4818](https://github.com/gfx-rs/wgpu/pull/4818). - Align `wgpu_types::CompositeAlphaMode` serde serialization to spec. By @littledivy in [#4940](https://github.com/gfx-rs/wgpu/pull/4940). - Fix error message of `ConfigureSurfaceError::TooLarge`. By @Dinnerbone in [#4960](https://github.com/gfx-rs/wgpu/pull/4960). - Fix dropping of `DeviceLostCallbackC` params. By @bradwerth in [#5032](https://github.com/gfx-rs/wgpu/pull/5032). - Fixed a number of panics. By @nical in [#4999](https://github.com/gfx-rs/wgpu/pull/4999), [#5014](https://github.com/gfx-rs/wgpu/pull/5014), [#5024](https://github.com/gfx-rs/wgpu/pull/5024), [#5025](https://github.com/gfx-rs/wgpu/pull/5025), [#5026](https://github.com/gfx-rs/wgpu/pull/5026), [#5027](https://github.com/gfx-rs/wgpu/pull/5027), [#5028](https://github.com/gfx-rs/wgpu/pull/5028) and [#5042](https://github.com/gfx-rs/wgpu/pull/5042). - No longer validate surfaces against their allowed extent range on configure. This caused warnings that were almost impossible to avoid. As before, the resulting behavior depends on the compositor. By @wumpf in [#4796](https://github.com/gfx-rs/wgpu/pull/4796). #### DX12 - Fixed D3D12_SUBRESOURCE_FOOTPRINT calculation for block compressed textures which caused a crash with `Queue::write_texture` on DX12. By @DTZxPorter in [#4990](https://github.com/gfx-rs/wgpu/pull/4990). #### Vulkan - Use `VK_EXT_robustness2` only when not using an outdated intel iGPU driver. By @TheoDulka in [#4602](https://github.com/gfx-rs/wgpu/pull/4602). #### WebGPU - Allow calling `BufferSlice::get_mapped_range` multiple times on the same buffer slice (instead of throwing a Javascript exception). By @DouglasDwyer in [#4726](https://github.com/gfx-rs/wgpu/pull/4726). #### WGL - Create a hidden window per `wgpu::Instance` instead of sharing a global one. By @Zoxc in [#4603](https://github.com/gfx-rs/wgpu/issues/4603) #### naga - Make module compaction preserve the module's named types, even if they are unused. By @jimblandy in [#4734](https://github.com/gfx-rs/wgpu/pull/4734). - Improve algorithm used by module compaction. By @jimblandy in [#4662](https://github.com/gfx-rs/wgpu/pull/4662). - When reading GLSL, fix the argument types of the double-precision floating-point overloads of the `dot`, `reflect`, `distance`, and `ldexp` builtin functions. Correct the WGSL generated for constructing 64-bit floating-point matrices. Add tests for all the above. By @jimblandy in [#4684](https://github.com/gfx-rs/wgpu/pull/4684). - Allow naga's IR types to represent matrices with elements elements of any scalar kind. This makes it possible for naga IR types to represent WGSL abstract matrices. By @jimblandy in [#4735](https://github.com/gfx-rs/wgpu/pull/4735). - Preserve the source spans for constants and expressions correctly across module compaction. By @jimblandy in [#4696](https://github.com/gfx-rs/wgpu/pull/4696). - Record the names of WGSL `alias` declarations in naga IR `Type`s. By @jimblandy in [#4733](https://github.com/gfx-rs/wgpu/pull/4733). #### Metal - Allow the `COPY_SRC` usage flag in surface configuration. By @Toqozz in [#4852](https://github.com/gfx-rs/wgpu/pull/4852). ### Examples - remove winit dependency from hello-compute example. By @psvri in [#4699](https://github.com/gfx-rs/wgpu/pull/4699) - hello-compute example fix failure with `wgpu error: Validation Error` if arguments are missing. By @vilcans in [#4939](https://github.com/gfx-rs/wgpu/pull/4939). - Made the examples page not crash on Chrome on Android, and responsive to screen sizes. By @Dinnerbone in [#4958](https://github.com/gfx-rs/wgpu/pull/4958). ## v0.18.2 (2023-12-06) This release includes `naga` version 0.14.2. The crates `wgpu-core`, `wgpu-hal` are still at `0.18.1` and the crates `wgpu` and `wgpu-types` are still at `0.18.0`. ### Bug Fixes #### naga - When evaluating const-expressions and generating SPIR-V, properly handle `Compose` expressions whose operands are `Splat` expressions. Such expressions are created and marked as constant by the constant evaluator. By @jimblandy in [#4695](https://github.com/gfx-rs/wgpu/pull/4695). ## v0.18.1 (2023-11-15) (naga version 0.14.1) ### Bug Fixes #### General - Fix panic in `Surface::configure` in debug builds. By @cwfitzgerald in [#4635](https://github.com/gfx-rs/wgpu/pull/4635) - Fix crash when all the following are true: By @teoxoy in #[#4642](https://github.com/gfx-rs/wgpu/pull/4642) - Passing a naga module directly to `Device::create_shader_module`. - `InstanceFlags::DEBUG` is enabled. #### DX12 - Always use HLSL 2018 when using DXC to compile HLSL shaders. By @daxpedda in [#4629](https://github.com/gfx-rs/wgpu/pull/4629) #### Metal - In Metal Shading Language output, fix issue where local variables were sometimes using variable names from previous functions. By @DJMcNab in [#4594](https://github.com/gfx-rs/wgpu/pull/4594) ## v0.18.0 (2023-10-25) For naga changelogs at or before v0.14.0. See [naga's changelog](naga/CHANGELOG.md). ### Desktop OpenGL 3.3+ Support on Windows We now support OpenGL on Windows! This brings support for a vast majority of the hardware that used to be covered by our DX11 backend. As of this writing we support OpenGL 3.3+, though there are efforts to reduce that further. This allows us to cover the last 12 years of Intel GPUs (starting with Ivy Bridge; aka 3xxx), and the last 16 years of AMD (starting with Terascale; aka HD 2000) / NVidia GPUs (starting with Tesla; aka GeForce 8xxx). By @Zoxc in [#4248](https://github.com/gfx-rs/wgpu/pull/4248) ### Timestamp Queries Supported on Metal and OpenGL Timestamp queries are now supported on both Metal and Desktop OpenGL. On Apple chips on Metal, they only support timestamp queries in command buffers or in the renderpass descriptor, they do not support them inside a pass. Metal: By @Wumpf in [#4008](https://github.com/gfx-rs/wgpu/pull/4008) OpenGL: By @Zoxc in [#4267](https://github.com/gfx-rs/wgpu/pull/4267) ### Render/Compute Pass Query Writes Addition of the `TimestampWrites` type to compute and render pass descriptors to allow profiling on tilers which do not support timestamps inside passes. Added [an example](https://github.com/gfx-rs/wgpu/tree/trunk/examples/timestamp-queries) to demonstrate the various kinds of timestamps. Additionally, metal now supports timestamp queries! By @FL33TW00D & @wumpf in [#3636](https://github.com/gfx-rs/wgpu/pull/3636). ### Occlusion Queries We now support binary occlusion queries! This allows you to determine if any of the draw calls within the query drew any pixels. Use the new `occlusion_query_set` field on `RenderPassDescriptor` to give a query set that occlusion queries will write to. ```diff let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { // ... + occlusion_query_set: Some(&my_occlusion_query_set), }); ``` Within the renderpass do the following to write the occlusion query results to the query set at the given index: ```rust rpass.begin_occlusion_query(index); rpass.draw(...); rpass.draw(...); rpass.end_occlusion_query(); ``` These are binary occlusion queries, so the result will be either 0 or an unspecified non-zero value. By @Valaphee in [#3402](https://github.com/gfx-rs/wgpu/pull/3402) ### Shader Improvements ```rust // WGSL constant expressions are now supported! const BLAH: u32 = 1u + 1u; // `rgb10a2uint` and `bgra8unorm` can now be used as a storage image format. var image: texture_storage_2d; var image: texture_storage_2d; // You can now use dual source blending! struct FragmentOutput{ @location(0) source1: vec4, @location(0) @second_blend_source source2: vec4, } // `modf`/`frexp` now return structures let result = modf(1.5); result.fract == 0.5; result.whole == 1.0; let result = frexp(1.5); result.fract == 0.75; result.exponent == 2i; // `modf`/`frexp` are currently disabled on GLSL and SPIR-V input. ``` ### Shader Validation Improvements ```rust // Cannot get pointer to a workgroup variable fn func(p: ptr); // ERROR // Cannot create Inf/NaN through constant expressions const INF: f32 = 3.40282347e+38 + 1.0; // ERROR const NAN: f32 = 0.0 / 0.0; // ERROR // `outerProduct` function removed // Error on repeated or missing `@workgroup_size()` @workgroup_size(1) @workgroup_size(2) // ERROR fn compute_main() {} // Error on repeated attributes. fn fragment_main(@location(0) @location(0) location_0: f32) // ERROR ``` ### RenderPass `StoreOp` is now Enumeration `wgpu::Operations::store` used to be an underdocumented boolean value, causing misunderstandings of the effect of setting it to `false`. The API now more closely resembles WebGPU which distinguishes between `store` and `discard`, see [WebGPU spec on GPUStoreOp](https://gpuweb.github.io/gpuweb/#enumdef-gpustoreop). ```diff // ... depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), - store: false, + store: wgpu::StoreOp::Discard, }), // ... ``` By @wumpf in [#4147](https://github.com/gfx-rs/wgpu/pull/4147) ### Instance Descriptor Settings The instance descriptor grew two more fields: `flags` and `gles_minor_version`. `flags` allow you to toggle the underlying api validation layers, debug information about shaders and objects in capture programs, and the ability to discard labels `gles_minor_version` is a rather niche feature that allows you to force the GLES backend to use a specific minor version, this is useful to get ANGLE to enable more than GLES 3.0. ```diff let instance = wgpu::Instance::new(InstanceDescriptor { ... + flags: wgpu::InstanceFlags::default() + gles_minor_version: wgpu::Gles3MinorVersion::Automatic, }); ``` `gles_minor_version`: By @PJB3005 in [#3998](https://github.com/gfx-rs/wgpu/pull/3998) `flags`: By @nical in [#4230](https://github.com/gfx-rs/wgpu/pull/4230) ### Many New Examples! - Added the following examples: By @JustAnotherCodemonkey in [#3885](https://github.com/gfx-rs/wgpu/pull/3885). - [repeated-compute](https://github.com/gfx-rs/wgpu/tree/trunk/examples/repeated-compute) - [storage-texture](https://github.com/gfx-rs/wgpu/tree/trunk/examples/storage-texture) - [render-to-texture](https://github.com/gfx-rs/wgpu/tree/trunk/examples/render-to-texture) - [uniform-values](https://github.com/gfx-rs/wgpu/tree/trunk/examples/uniform-values) - [hello-workgroups](https://github.com/gfx-rs/wgpu/tree/trunk/examples/hello-workgroups) - [hello-synchronization](https://github.com/gfx-rs/wgpu/tree/trunk/examples/hello-synchronization) ### Revamped Testing Suite Our testing harness was completely revamped and now automatically runs against all gpus in the system, shows the expected status of every test, and is tolerant to flakes. Additionally, we have filled out our CI to now run the latest versions of WARP and Mesa. This means we can test even more features on CI than before. By @cwfitzgerald in [#3873](https://github.com/gfx-rs/wgpu/pull/3873) ### The GLES backend is now optional on macOS The `angle` feature flag has to be set for the GLES backend to be enabled on Windows & macOS. By @teoxoy in [#4185](https://github.com/gfx-rs/wgpu/pull/4185) ### Added/New Features - Re-export naga. By @exrook in [#4172](https://github.com/gfx-rs/wgpu/pull/4172) - Add WinUI 3 SwapChainPanel support. By @ddrboxman in [#4191](https://github.com/gfx-rs/wgpu/pull/4191) ### Changes #### General - Omit texture store bound checks since they are no-ops if out of bounds on all APIs. By @teoxoy in [#3975](https://github.com/gfx-rs/wgpu/pull/3975) - Validate `DownlevelFlags::READ_ONLY_DEPTH_STENCIL`. By @teoxoy in [#4031](https://github.com/gfx-rs/wgpu/pull/4031) - Add validation in accordance with WebGPU `setViewport` valid usage for `x`, `y` and `this.[[attachment_size]]`. By @James2022-rgb in [#4058](https://github.com/gfx-rs/wgpu/pull/4058) - `wgpu::CreateSurfaceError` and `wgpu::RequestDeviceError` now give details of the failure, but no longer implement `PartialEq` and cannot be constructed. By @kpreid in [#4066](https://github.com/gfx-rs/wgpu/pull/4066) and [#4145](https://github.com/gfx-rs/wgpu/pull/4145) - Make `WGPU_POWER_PREF=none` a valid value. By @fornwall in [4076](https://github.com/gfx-rs/wgpu/pull/4076) - Support dual source blending in OpenGL ES, Metal, Vulkan & DX12. By @freqmod in [4022](https://github.com/gfx-rs/wgpu/pull/4022) - Add stub support for device destroy and device validity. By @bradwerth in [4163](https://github.com/gfx-rs/wgpu/pull/4163) and in [4212](https://github.com/gfx-rs/wgpu/pull/4212) - Add trace-level logging for most entry points in wgpu-core By @nical in [4183](https://github.com/gfx-rs/wgpu/pull/4183) - Add `Rgb10a2Uint` format. By @teoxoy in [4199](https://github.com/gfx-rs/wgpu/pull/4199) - Validate that resources are used on the right device. By @nical in [4207](https://github.com/gfx-rs/wgpu/pull/4207) - Expose instance flags. - Add support for the bgra8unorm-storage feature. By @jinleili and @nical in [#4228](https://github.com/gfx-rs/wgpu/pull/4228) - Calls to lost devices now return `DeviceError::Lost` instead of `DeviceError::Invalid`. By @bradwerth in [#4238]([https://github.com/gfx-rs/wgpu/pull/4238]) - Let the `"strict_asserts"` feature enable check that wgpu-core's lock-ordering tokens are unique per thread. By @jimblandy in [#4258]([https://github.com/gfx-rs/wgpu/pull/4258]) - Allow filtering labels out before they are passed to GPU drivers by @nical in [https://github.com/gfx-rs/wgpu/pull/4246](4246) - `DeviceLostClosure` callback mechanism provided so user agents can resolve `GPUDevice.lost` Promises at the appropriate time by @bradwerth in [#4645](https://github.com/gfx-rs/wgpu/pull/4645) #### Vulkan - Rename `wgpu_hal::vulkan::Instance::required_extensions` to `desired_extensions`. By @jimblandy in [#4115](https://github.com/gfx-rs/wgpu/pull/4115) - Don't bother calling `vkFreeCommandBuffers` when `vkDestroyCommandPool` will take care of that for us. By @jimblandy in [#4059](https://github.com/gfx-rs/wgpu/pull/4059) #### DX12 - Bump `gpu-allocator` to 0.23. By @Elabajaba in [#4198](https://github.com/gfx-rs/wgpu/pull/4198) ### Documentation - Use WGSL for VertexFormat example types. By @ScanMountGoat in [#4035](https://github.com/gfx-rs/wgpu/pull/4035) - Fix description of `Features::TEXTURE_COMPRESSION_ASTC_HDR` in [#4157](https://github.com/gfx-rs/wgpu/pull/4157) ### Bug Fixes #### General - Derive storage bindings via `naga::StorageAccess` instead of `naga::GlobalUse`. By @teoxoy in [#3985](https://github.com/gfx-rs/wgpu/pull/3985). - `Queue::on_submitted_work_done` callbacks will now always be called after all previous `BufferSlice::map_async` callbacks, even when there are no active submissions. By @cwfitzgerald in [#4036](https://github.com/gfx-rs/wgpu/pull/4036). - Fix `clear` texture views being leaked when `wgpu::SurfaceTexture` is dropped before it is presented. By @rajveermalviya in [#4057](https://github.com/gfx-rs/wgpu/pull/4057). - Add `Feature::SHADER_UNUSED_VERTEX_OUTPUT` to allow unused vertex shader outputs. By @Aaron1011 in [#4116](https://github.com/gfx-rs/wgpu/pull/4116). - Fix a panic in `surface_configure`. By @nical in [#4220](https://github.com/gfx-rs/wgpu/pull/4220) and [#4227](https://github.com/gfx-rs/wgpu/pull/4227) - Pipelines register their implicit layouts in error cases. By @bradwerth in [#4624](https://github.com/gfx-rs/wgpu/pull/4624) - Better handle explicit destruction of textures and buffers. By @nical in [#4657](https://github.com/gfx-rs/wgpu/pull/4657) #### Vulkan - Fix enabling `wgpu::Features::PARTIALLY_BOUND_BINDING_ARRAY` not being actually enabled in vulkan backend. By @39ali in[#3772](https://github.com/gfx-rs/wgpu/pull/3772). - Don't pass `vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR` unless the `VK_KHR_portability_enumeration` extension is available. By @jimblandy in[#4038](https://github.com/gfx-rs/wgpu/pull/4038). - Enhancement of [#4038], using ash's definition instead of hard-coded c_str. By @hybcloud in[#4044](https://github.com/gfx-rs/wgpu/pull/4044). - Enable vulkan presentation on (Linux) Intel Mesa >= v21.2. By @flukejones in[#4110](https://github.com/gfx-rs/wgpu/pull/4110) #### DX12 - DX12 doesn't support `Features::POLYGON_MODE_POINT``. By @teoxoy in [#4032](https://github.com/gfx-rs/wgpu/pull/4032). - Set `Features::VERTEX_WRITABLE_STORAGE` based on the right feature level. By @teoxoy in [#4033](https://github.com/gfx-rs/wgpu/pull/4033). #### Metal - Ensure that MTLCommandEncoder calls endEncoding before it is deallocated. By @bradwerth in [#4023](https://github.com/gfx-rs/wgpu/pull/4023) #### WebGPU - Ensure that limit requests and reporting is done correctly. By @OptimisticPeach in [#4107](https://github.com/gfx-rs/wgpu/pull/4107) - Validate usage of polygon mode. By @teoxoy in [#4196](https://github.com/gfx-rs/wgpu/pull/4196) #### GLES - enable/disable blending per attachment only when available (on ES 3.2 or higher). By @teoxoy in [#4234](https://github.com/gfx-rs/wgpu/pull/4234) ### Documentation - Add an overview of `RenderPass` and how render state works. By @kpreid in [#4055](https://github.com/gfx-rs/wgpu/pull/4055) ### Examples - Created `wgpu-example::utils` module to contain misc functions and such that are common code but aren't part of the example framework. Add to it the functions `output_image_wasm` and `output_image_native`, both for outputting `Vec` RGBA images either to the disc or the web page. By @JustAnotherCodemonkey in [#3885](https://github.com/gfx-rs/wgpu/pull/3885). - Removed `capture` example as it had issues (did not run on wasm) and has been replaced by `render-to-texture` (see above). By @JustAnotherCodemonkey in [#3885](https://github.com/gfx-rs/wgpu/pull/3885). ## v0.17.2 (2023-10-03) ### Bug Fixes #### Vulkan - Fix x11 hang while resizing on vulkan. @Azorlogh in [#4184](https://github.com/gfx-rs/wgpu/pull/4184). ## v0.17.1 (2023-09-27) ### Added/New Features - Add `get_mapped_range_as_array_buffer` for faster buffer read-backs in wasm builds. By @ryankaplan in [#4042] (https://github.com/gfx-rs/wgpu/pull/4042). ### Bug Fixes #### DX12 - Fix panic on resize when using DX12. By @cwfitzgerald in [#4106](https://github.com/gfx-rs/wgpu/pull/4106) #### Vulkan - Suppress validation error caused by OBS layer. This was also fixed upstream. By @cwfitzgerald in [#4002](https://github.com/gfx-rs/wgpu/pull/4002) - Work around bug in nvidia's vkCmdFillBuffer implementation. By @cwfitzgerald in [#4132](https://github.com/gfx-rs/wgpu/pull/4132). ## v0.17.0 (2023-07-20) This is the first release that featured `wgpu-info` as a binary crate for getting information about what devices wgpu sees in your system. It can dump the information in both human readable format and json. ### Major Changes This release was fairly minor as breaking changes go. #### `wgpu` types now `!Send` `!Sync` on wasm Up until this point, wgpu has made the assumption that threads do not exist on wasm. With the rise of libraries like [`wasm_thread`](https://crates.io/crates/wasm_thread) making it easier and easier to do wasm multithreading this assumption is no longer sound. As all wgpu objects contain references into the JS heap, they cannot leave the thread they started on. As we understand that this change might be very inconvenient for users who don't care about wasm threading, there is a crate feature which re-enables the old behavior: `fragile-send-sync-non-atomic-wasm`. So long as you don't compile your code with `-Ctarget-feature=+atomics`, `Send` and `Sync` will be implemented again on wgpu types on wasm. As the name implies, especially for libraries, this is very fragile, as you don't know if a user will want to compile with atomics (and therefore threads) or not. By @daxpedda in [#3691](https://github.com/gfx-rs/wgpu/pull/3691) #### Power Preference is now optional The `power_preference` field of `RequestAdapterOptions` is now optional. If it is `PowerPreference::None`, we will choose the first available adapter, preferring GPU adapters over CPU adapters. By @Aaron1011 in [#3903](https://github.com/gfx-rs/wgpu/pull/3903) #### `initialize_adapter_from_env` argument changes Removed the backend_bits parameter from `initialize_adapter_from_env` and `initialize_adapter_from_env_or_default`. If you want to limit the backends used by this function, only enable the wanted backends in the instance. Added a compatible surface parameter, to ensure the given device is able to be presented onto the given surface. ```diff - wgpu::util::initialize_adapter_from_env(instance, backend_bits); + wgpu::util::initialize_adapter_from_env(instance, Some(&compatible_surface)); ``` By @fornwall in [#3904](https://github.com/gfx-rs/wgpu/pull/3904) and [#3905](https://github.com/gfx-rs/wgpu/pull/3905) #### Misc Breaking Changes - Change `AdapterInfo::{device,vendor}` to be `u32` instead of `usize`. By @ameknite in [#3760](https://github.com/gfx-rs/wgpu/pull/3760) ### Changes - Added support for importing external buffers using `buffer_from_raw` (Dx12, Metal, Vulkan) and `create_buffer_from_hal`. By @AdrianEddy in [#3355](https://github.com/gfx-rs/wgpu/pull/3355) #### Vulkan - Work around [Vulkan-ValidationLayers#5671](https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/5671) by ignoring reports of violations of [VUID-vkCmdEndDebugUtilsLabelEXT-commandBuffer-01912](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkCmdEndDebugUtilsLabelEXT-commandBuffer-01912). By @jimblandy in [#3809](https://github.com/gfx-rs/wgpu/pull/3809). ### Added/New Features #### General - Empty scissor rects are allowed now, matching the specification. by @PJB3005 in [#3863](https://github.com/gfx-rs/wgpu/pull/3863). - Add back components info to `TextureFormat`s. By @teoxoy in [#3843](https://github.com/gfx-rs/wgpu/pull/3843). - Add `get_mapped_range_as_array_buffer` for faster buffer read-backs in wasm builds. By @ryankaplan in [#4042] (https://github.com/gfx-rs/wgpu/pull/4042). ### Documentation - Better documentation for draw, draw_indexed, set_viewport and set_scissor_rect. By @genusistimelord in [#3860](https://github.com/gfx-rs/wgpu/pull/3860) - Fix link to `GPUVertexBufferLayout`. By @fornwall in [#3906](https://github.com/gfx-rs/wgpu/pull/3906) - Document feature requirements for `DEPTH32FLOAT_STENCIL8` by @ErichDonGubler in [#3734](https://github.com/gfx-rs/wgpu/pull/3734). - Flesh out docs. for `AdapterInfo::{device,vendor}` by @ErichDonGubler in [#3763](https://github.com/gfx-rs/wgpu/pull/3763). - Spell out which sizes are in bytes. By @jimblandy in [#3773](https://github.com/gfx-rs/wgpu/pull/3773). - Validate that `descriptor.usage` is not empty in `create_buffer` by @nical in [#3928](https://github.com/gfx-rs/wgpu/pull/3928) - Update `max_bindings_per_bind_group` limit to reflect spec changes by @ErichDonGubler and @nical in [#3943](https://github.com/gfx-rs/wgpu/pull/3943) [#3942](https://github.com/gfx-rs/wgpu/pull/3942) - Add better docs for `Limits`, listing the actual limits returned by `downlevel_defaults` and `downlevel_webgl2_defaults` by @JustAnotherCodemonkey in [#3988](https://github.com/gfx-rs/wgpu/pull/3988) ### Bug Fixes #### General - Fix order of arguments to glPolygonOffset by @komadori in [#3783](https://github.com/gfx-rs/wgpu/pull/3783). - Fix OpenGL/EGL backend not respecting non-sRGB texture formats in `SurfaceConfiguration`. by @liquidev in [#3817](https://github.com/gfx-rs/wgpu/pull/3817) - Make write- and read-only marked buffers match non-readonly layouts. by @fornwall in [#3893](https://github.com/gfx-rs/wgpu/pull/3893) - Fix leaking X11 connections. by @wez in [#3924](https://github.com/gfx-rs/wgpu/pull/3924) - Fix ASTC feature selection in the webgl backend. by @expenses in [#3934](https://github.com/gfx-rs/wgpu/pull/3934) - Fix Multiview to disable validation of TextureViewDimension and ArrayLayerCount. By @MalekiRe in [#3779](https://github.com/gfx-rs/wgpu/pull/3779#issue-1713269437). #### Vulkan - Fix incorrect aspect in barriers when using emulated Stencil8 textures. By @cwfitzgerald in [#3833](https://github.com/gfx-rs/wgpu/pull/3833). - Implement depth-clip-control using depthClamp instead of VK_EXT_depth_clip_enable. By @AlbinBernhardssonARM [#3892](https://github.com/gfx-rs/wgpu/pull/3892). - Fix enabling `wgpu::Features::PARTIALLY_BOUND_BINDING_ARRAY` not being actually enabled in vulkan backend. By @39ali in[#3772](https://github.com/gfx-rs/wgpu/pull/3772). #### Metal - Fix renderpasses being used inside of renderpasses. By @cwfitzgerald in [#3828](https://github.com/gfx-rs/wgpu/pull/3828) - Support (simulated) visionOS. By @jinleili in [#3883](https://github.com/gfx-rs/wgpu/pull/3883) #### DX12 - Disable suballocation on Intel Iris(R) Xe. By @xiaopengli89 in [#3668](https://github.com/gfx-rs/wgpu/pull/3668) - Change the `max_buffer_size` limit from `u64::MAX` to `i32::MAX`. By @nical in [#4020](https://github.com/gfx-rs/wgpu/pull/4020) #### WebGPU - Use `get_preferred_canvas_format()` to fill `formats` of `SurfaceCapabilities`. By @jinleili in [#3744](https://github.com/gfx-rs/wgpu/pull/3744) ### Examples - Publish examples to wgpu.rs on updates to trunk branch instead of gecko. By @paul-hansen in [#3750](https://github.com/gfx-rs/wgpu/pull/3750) - Ignore the exception values generated by the winit resize event. By @jinleili in [#3916](https://github.com/gfx-rs/wgpu/pull/3916) ## v0.16.3 (2023-07-19) ### Changes #### General - Make the `Id` type that is exposed when using the `expose-ids` feature implement `Send` and `Sync` again. This was unintentionally changed by the v0.16.0 release and is now fixed. ## v0.16.2 (2023-07-09) ### Changes #### DX12 - Increase the `max_storage_buffers_per_shader_stage` and `max_storage_textures_per_shader_stage` limits based on what the hardware supports. by @Elabajaba in [#3798]https://github.com/gfx-rs/wgpu/pull/3798 ## v0.16.1 (2023-05-24) ### Bug Fixes - Fix missing 4X MSAA support on some OpenGL backends. By @emilk in [#3780](https://github.com/gfx-rs/wgpu/pull/3780) #### General - Fix crash on dropping `wgpu::CommandBuffer`. By @wumpf in [#3726](https://github.com/gfx-rs/wgpu/pull/3726). - Use `u32`s internally for bind group indices, rather than `u8`. By @ErichDonGubler in [#3743](https://github.com/gfx-rs/wgpu/pull/3743). #### WebGPU - Fix crash when calling `create_surface_from_canvas`. By @grovesNL in [#3718](https://github.com/gfx-rs/wgpu/pull/3718) ## v0.16.0 (2023-04-19) ### Major changes #### Shader Changes `type` has been replaced with `alias` to match with upstream WebGPU. ```diff - type MyType = vec4; + alias MyType = vec4; ``` #### TextureFormat info API The `TextureFormat::describe` function was removed in favor of separate functions: `block_dimensions`, `is_compressed`, `is_srgb`, `required_features`, `guaranteed_format_features`, `sample_type` and `block_size`. ```diff - let block_dimensions = format.describe().block_dimensions; + let block_dimensions = format.block_dimensions(); - let is_compressed = format.describe().is_compressed(); + let is_compressed = format.is_compressed(); - let is_srgb = format.describe().srgb; + let is_srgb = format.is_srgb(); - let required_features = format.describe().required_features; + let required_features = format.required_features(); ``` Additionally `guaranteed_format_features` now takes a set of features to assume are enabled. ```diff - let guaranteed_format_features = format.describe().guaranteed_format_features; + let guaranteed_format_features = format.guaranteed_format_features(device.features()); ``` Additionally `sample_type` and `block_size` now take an optional `TextureAspect` and return `Option`s. ```diff - let sample_type = format.describe().sample_type; + let sample_type = format.sample_type(None).expect("combined depth-stencil format requires specifying a TextureAspect"); - let block_size = format.describe().block_size; + let block_size = format.block_size(None).expect("combined depth-stencil format requires specifying a TextureAspect"); ``` By @teoxoy in [#3436](https://github.com/gfx-rs/wgpu/pull/3436) #### BufferUsages::QUERY_RESOLVE Buffers used as the `destination` argument of `CommandEncoder::resolve_query_set` now have to contain the `QUERY_RESOLVE` usage instead of the `COPY_DST` usage. ```diff let destination = device.create_buffer(&wgpu::BufferDescriptor { // ... - usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false, }); command_encoder.resolve_query_set(&query_set, query_range, &destination, destination_offset); ``` By @JolifantoBambla in [#3489](https://github.com/gfx-rs/wgpu/pull/3489) #### Renamed features The following `Features` have been renamed. - `SHADER_FLOAT16` -> `SHADER_F16` - `SHADER_FLOAT64` -> `SHADER_F64` - `SHADER_INT16` -> `SHADER_I16` - `TEXTURE_COMPRESSION_ASTC_LDR` -> `TEXTURE_COMPRESSION_ASTC` - `WRITE_TIMESTAMP_INSIDE_PASSES` -> `TIMESTAMP_QUERY_INSIDE_PASSES` By @teoxoy in [#3534](https://github.com/gfx-rs/wgpu/pull/3534) #### Anisotropic Filtering Anisotropic filtering has been brought in line with the spec. The anisotropic clamp is now a `u16` (was a `Option`) which must be at least 1. If the anisotropy clamp is not 1, all the filters in a sampler must be `Linear`. ```diff SamplerDescriptor { - anisotropic_clamp: None, + anisotropic_clamp: 1, } ``` By @cwfitzgerald in [#3610](https://github.com/gfx-rs/wgpu/pull/3610). #### TextureFormat Names Some texture format names have changed to get back in line with the spec. ```diff - TextureFormat::Bc6hRgbSfloat + TextureFormat::Bc6hRgbFloat ``` By @cwfitzgerald in [#3671](https://github.com/gfx-rs/wgpu/pull/3671). #### Misc Breaking Changes - Change type of `mip_level_count` and `array_layer_count` (members of `TextureViewDescriptor` and `ImageSubresourceRange`) from `Option` to `Option`. By @teoxoy in [#3445](https://github.com/gfx-rs/wgpu/pull/3445) - Change type of `bytes_per_row` and `rows_per_image` (members of `ImageDataLayout`) from `Option` to `Option`. By @teoxoy in [#3529](https://github.com/gfx-rs/wgpu/pull/3529) - On Web, `Instance::create_surface_from_canvas()` and `create_surface_from_offscreen_canvas()` now take the canvas by value. By @daxpedda in [#3690](https://github.com/gfx-rs/wgpu/pull/3690) ### Added/New Features #### General - Added feature flags for ray-tracing (currently only hal): `RAY_QUERY` and `RAY_TRACING` @daniel-keitel (started by @expenses) in [#3507](https://github.com/gfx-rs/wgpu/pull/3507) #### Vulkan - Implemented basic ray-tracing api for acceleration structures, and ray-queries @daniel-keitel (started by @expenses) in [#3507](https://github.com/gfx-rs/wgpu/pull/3507) #### Hal - Added basic ray-tracing api for acceleration structures, and ray-queries @daniel-keitel (started by @expenses) in [#3507](https://github.com/gfx-rs/wgpu/pull/3507) ### Changes #### General - Added `TextureFormatFeatureFlags::MULTISAMPLE_X16`. By @Dinnerbone in [#3454](https://github.com/gfx-rs/wgpu/pull/3454) - Added `BufferUsages::QUERY_RESOLVE`. By @JolifantoBambla in [#3489](https://github.com/gfx-rs/wgpu/pull/3489) - Support stencil-only views and copying to/from combined depth-stencil textures. By @teoxoy in [#3436](https://github.com/gfx-rs/wgpu/pull/3436) - Added `Features::SHADER_EARLY_DEPTH_TEST`. By @teoxoy in [#3494](https://github.com/gfx-rs/wgpu/pull/3494) - All `fxhash` dependencies have been replaced with `rustc-hash`. By @james7132 in [#3502](https://github.com/gfx-rs/wgpu/pull/3502) - Allow copying of textures with copy-compatible formats. By @teoxoy in [#3528](https://github.com/gfx-rs/wgpu/pull/3528) - Improve attachment related errors. By @cwfitzgerald in [#3549](https://github.com/gfx-rs/wgpu/pull/3549) - Make error descriptions all upper case. By @cwfitzgerald in [#3549](https://github.com/gfx-rs/wgpu/pull/3549) - Don't include ANSI terminal color escape sequences in shader module validation error messages. By @jimblandy in [#3591](https://github.com/gfx-rs/wgpu/pull/3591) - Report error messages from DXC compile. By @Davidster in [#3632](https://github.com/gfx-rs/wgpu/pull/3632) - Error in native when using a filterable `TextureSampleType::Float` on a multisample `BindingType::Texture`. By @mockersf in [#3686](https://github.com/gfx-rs/wgpu/pull/3686) - On Web, the size of the canvas is adjusted when using `Surface::configure()`. If the canvas was given an explicit size (via CSS), this will not affect the visual size of the canvas. By @daxpedda in [#3690](https://github.com/gfx-rs/wgpu/pull/3690) - Added `Global::create_render_bundle_error`. By @jimblandy in [#3746](https://github.com/gfx-rs/wgpu/pull/3746) #### WebGPU - Implement the new checks for readonly stencils. By @JCapucho in [#3443](https://github.com/gfx-rs/wgpu/pull/3443) - Reimplement `adapter|device_features`. By @jinleili in [#3428](https://github.com/gfx-rs/wgpu/pull/3428) - Implement `command_encoder_resolve_query_set`. By @JolifantoBambla in [#3489](https://github.com/gfx-rs/wgpu/pull/3489) - Add support for `Features::RG11B10UFLOAT_RENDERABLE`. By @mockersf in [#3689](https://github.com/gfx-rs/wgpu/pull/3689) #### Vulkan - Set `max_memory_allocation_size` via `PhysicalDeviceMaintenance3Properties`. By @jinleili in [#3567](https://github.com/gfx-rs/wgpu/pull/3567) - Silence false-positive validation error about surface resizing. By @seabassjh in [#3627](https://github.com/gfx-rs/wgpu/pull/3627) ### Bug Fixes #### General - `copyTextureToTexture` src/dst aspects must both refer to all aspects of src/dst format. By @teoxoy in [#3431](https://github.com/gfx-rs/wgpu/pull/3431) - Validate before extracting texture selectors. By @teoxoy in [#3487](https://github.com/gfx-rs/wgpu/pull/3487) - Fix fatal errors (those which panic even if an error handler is set) not including all of the details. By @kpreid in [#3563](https://github.com/gfx-rs/wgpu/pull/3563) - Validate shader location clashes. By @emilk in [#3613](https://github.com/gfx-rs/wgpu/pull/3613) - Fix surfaces not being dropped until exit. By @benjaminschaaf in [#3647](https://github.com/gfx-rs/wgpu/pull/3647) #### WebGPU - Fix handling of `None` values for `depth_ops` and `stencil_ops` in `RenderPassDescriptor::depth_stencil_attachment`. By @niklaskorz in [#3660](https://github.com/gfx-rs/wgpu/pull/3660) - Avoid using `WasmAbi` functions for WebGPU backend. By @grovesNL in [#3657](https://github.com/gfx-rs/wgpu/pull/3657) #### DX12 - Use typeless formats for textures that might be viewed as srgb or non-srgb. By @teoxoy in [#3555](https://github.com/gfx-rs/wgpu/pull/3555) #### GLES - Set FORCE_POINT_SIZE if it is vertex shader with mesh consist of point list. By @REASY in [3440](https://github.com/gfx-rs/wgpu/pull/3440) - Remove unwraps inside `surface.configure`. By @cwfitzgerald in [#3585](https://github.com/gfx-rs/wgpu/pull/3585) - Fix `copy_external_image_to_texture`, `copy_texture_to_texture` and `copy_buffer_to_texture` not taking the specified index into account if the target texture is a cube map, 2D texture array or cube map array. By @daxpedda [#3641](https://github.com/gfx-rs/wgpu/pull/3641) - Fix disabling of vertex attributes with non-consecutive locations. By @Azorlogh in [#3706](https://github.com/gfx-rs/wgpu/pull/3706) #### Metal - Fix metal erroring on an `array_stride` of 0. By @teoxoy in [#3538](https://github.com/gfx-rs/wgpu/pull/3538) - `create_texture` returns an error if `new_texture` returns NULL. By @jinleili in [#3554](https://github.com/gfx-rs/wgpu/pull/3554) - Fix shader bounds checking being ignored. By @FL33TW00D in [#3603](https://github.com/gfx-rs/wgpu/pull/3603) #### Vulkan - Treat `VK_SUBOPTIMAL_KHR` as `VK_SUCCESS` on Android due to rotation issues. By @James2022-rgb in [#3525](https://github.com/gfx-rs/wgpu/pull/3525) ### Examples - Use `BufferUsages::QUERY_RESOLVE` instead of `BufferUsages::COPY_DST` for buffers used in `CommandEncoder::resolve_query_set` calls in `mipmap` example. By @JolifantoBambla in [#3489](https://github.com/gfx-rs/wgpu/pull/3489) ## v0.15.3 (2023-03-22) ### Bug Fixes #### Metal - Fix incorrect mipmap being sampled when using `MinLod <= 0.0` and `MaxLod >= 32.0` or when the fragment shader samples different Lods in the same quad. By @cwfitzgerald in [#3610](https://github.com/gfx-rs/wgpu/pull/3610). #### GLES - Fix `Vertex buffer is not big enough for the draw call.` for ANGLE/Web when rendering with instance attributes on a single instance. By @wumpf in [#3596](https://github.com/gfx-rs/wgpu/pull/3596) - Reset all queue state between command buffers in a submit. By @jleibs [#3589](https://github.com/gfx-rs/wgpu/pull/3589) - Reset the state of `SAMPLE_ALPHA_TO_COVERAGE` on queue reset. By @jleibs [#3589](https://github.com/gfx-rs/wgpu/pull/3589) ## wgpu-0.15.2 (2023-03-08) ### Bug Fixes #### Metal - Fix definition of `NSOperatingSystemVersion` to avoid potential crashes. By @grovesNL in [#3557](https://github.com/gfx-rs/wgpu/pull/3557) #### GLES - Enable `WEBGL_debug_renderer_info` before querying unmasked vendor/renderer to avoid crashing on emscripten in [#3519](https://github.com/gfx-rs/wgpu/pull/3519) ## wgpu-0.15.1 (2023-02-09) ### Changes #### General - Fix for some minor issues in comments on some features. By @Wumpf in [#3455](https://github.com/gfx-rs/wgpu/pull/3455) #### Vulkan - Improve format MSAA capabilities detection. By @jinleili in [#3429](https://github.com/gfx-rs/wgpu/pull/3429) #### DX12 - Update gpu allocator to 0.22. By @Elabajaba in [#3447](https://github.com/gfx-rs/wgpu/pull/3447) #### WebGPU - Implement `CommandEncoder::clear_buffer`. By @raphlinus in [#3426](https://github.com/gfx-rs/wgpu/pull/3426) ### Bug Fixes #### General - Re-sort supported surface formats based on srgb-ness. By @cwfitzgerald in [#3444](https://github.com/gfx-rs/wgpu/pull/3444) #### Vulkan - Fix surface view formats validation error. By @jinleili in [#3432](https://github.com/gfx-rs/wgpu/pull/3432) #### DX12 - Fix DXC validation issues when using a custom `dxil_path`. By @Elabajaba in [#3434](https://github.com/gfx-rs/wgpu/pull/3434) #### GLES - Unbind vertex buffers at end of renderpass. By @cwfitzgerald in [#3459](https://github.com/gfx-rs/wgpu/pull/3459) #### WebGPU - Reimplement `{adapter|device}_features`. By @jinleili in [#3428](https://github.com/gfx-rs/wgpu/pull/3428) ### Documentation #### General - Build for Wasm on docs.rs. By @daxpedda in [#3462](https://github.com/gfx-rs/wgpu/pull/3428) ## wgpu-0.15.0 (2023-01-25) ### Major Changes #### WGSL Top-Level `let` is now `const` All top level constants are now declared with `const`, catching up with the wgsl spec. `let` is no longer allowed at the global scope, only within functions. ```diff -let SOME_CONSTANT = 12.0; +const SOME_CONSTANT = 12.0; ``` See https://github.com/gfx-rs/naga/blob/master/CHANGELOG.md#v011-2023-01-25 for smaller shader improvements. #### Surface Capabilities API The various surface capability functions were combined into a single call that gives you all the capabilities. ```diff - let formats = surface.get_supported_formats(&adapter); - let present_modes = surface.get_supported_present_modes(&adapter); - let alpha_modes = surface.get_supported_alpha_modes(&adapter); + let caps = surface.get_capabilities(&adapter); + let formats = caps.formats; + let present_modes = caps.present_modes; + let alpha_modes = caps.alpha_modes; ``` Additionally `Surface::get_default_config` now returns an Option and returns None if the surface isn't supported by the adapter. ```diff - let config = surface.get_default_config(&adapter); + let config = surface.get_default_config(&adapter).expect("Surface unsupported by adapter"); ``` #### Fallible surface creation `Instance::create_surface()` now returns `Result` instead of `Surface`. This allows an error to be returned if the given window is a HTML canvas and obtaining a WebGPU or WebGL 2 context fails. (No other platforms currently report any errors through this path.) By @kpreid in [#3052](https://github.com/gfx-rs/wgpu/pull/3052/) #### `Queue::copy_external_image_to_texture` on WebAssembly A new api, `Queue::copy_external_image_to_texture`, allows you to create wgpu textures from various web image primitives. Specifically from `HtmlVideoElement`, `HtmlCanvasElement`, `OffscreenCanvas`, and `ImageBitmap`. This provides multiple low-copy ways of interacting with the browser. WebGL is also supported, though WebGL has some additional restrictions, represented by the `UNRESTRICTED_EXTERNAL_IMAGE_COPIES` downlevel flag. By @cwfitzgerald in [#3288](https://github.com/gfx-rs/wgpu/pull/3288) #### Instance creation now takes `InstanceDescriptor` instead of `Backends` `Instance::new()` and `hub::Global::new()` now take an `InstanceDescriptor` struct which contains both the existing `Backends` selection as well as a new `Dx12Compiler` field for selecting which Dx12 shader compiler to use. ```diff - let instance = Instance::new(wgpu::Backends::all()); + let instance = Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::all(), + dx12_shader_compiler: wgpu::Dx12Compiler::Fxc, + }); ``` `Instance` now also also implements `Default`, which uses `wgpu::Backends::all()` and `wgpu::Dx12Compiler::Fxc` for `InstanceDescriptor` ```diff - let instance = Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), - dx12_shader_compiler: wgpu::Dx12Compiler::Fxc, - }); + let instance = Instance::default(); ``` By @Elabajaba in [#3356](https://github.com/gfx-rs/wgpu/pull/3356) #### Texture Format Reinterpretation The new `view_formats` field in the `TextureDescriptor` is used to specify a list of formats the texture can be re-interpreted to in a texture view. Currently only changing srgb-ness is allowed (ex. `Rgba8Unorm` <=> `Rgba8UnormSrgb`). ```diff let texture = device.create_texture(&wgpu::TextureDescriptor { // ... format: TextureFormat::Rgba8UnormSrgb, + view_formats: &[TextureFormat::Rgba8Unorm], }); ``` ```diff let config = wgpu::SurfaceConfiguration { // ... format: TextureFormat::Rgba8Unorm, + view_formats: vec![wgpu::TextureFormat::Rgba8UnormSrgb], }; surface.configure(&device, &config); ``` #### MSAA x2 and x8 Support Via the `TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES` feature, MSAA x2 and x8 are now supported on textures. To query for x2 or x8 support, enable the feature and look at the texture format flags for the texture format of your choice. By @39ali in [3140](https://github.com/gfx-rs/wgpu/pull/3140) #### DXC Shader Compiler Support for DX12 You can now choose to use the DXC compiler for DX12 instead of FXC. The DXC compiler is faster, less buggy, and allows for new features compared to the old, unmaintained FXC compiler. You can choose which compiler to use at `Instance` creation using the `dx12_shader_compiler` field in the `InstanceDescriptor` struct. Note that DXC requires both `dxcompiler.dll` and `dxil.dll`, which can be downloaded from https://github.com/microsoft/DirectXShaderCompiler/releases. Both .dlls need to be shipped with your application when targeting DX12 and using the `DXC` compiler. If the .dlls can't be loaded, then it will fall back to the FXC compiler. By @39ali and @Elabajaba in [#3356](https://github.com/gfx-rs/wgpu/pull/3356) #### Suballocate DX12 buffers and textures The DX12 backend can now suballocate buffers and textures from larger chunks of memory, which can give a significant increase in performance (in testing a 100x improvement has been seen in a simple scene with 200 `write_buffer` calls per frame, and a 1.4x improvement in [Bistro using Bevy](https://github.com/vleue/bevy_bistro_playground)). Previously `wgpu-hal`'s DX12 backend created a new heap on the GPU every time you called `write_buffer` (by calling `CreateCommittedResource`), whereas now it uses [`gpu_allocator`](https://crates.io/crates/gpu-allocator) to manage GPU memory (and calls `CreatePlacedResource` with a suballocated heap). By @Elabajaba in [#3163](https://github.com/gfx-rs/wgpu/pull/3163) #### Backend selection by features in wgpu-core Whereas `wgpu-core` used to automatically select backends to enable based on the target OS and architecture, it now has separate features to enable each backend: - "metal", for the Metal API on macOS and iOS - "vulkan", for the Vulkan API (Linux, some Android, and occasionally Windows) - "dx12", for Microsoft's Direct3D 12 API - "gles", OpenGL ES, available on many systems - "dx11", for Microsoft's Direct3D 11 API None are enabled by default, but the `wgpu` crate automatically selects these features based on the target operating system and architecture, using the same rules that `wgpu-core` used to, so users of `wgpu` should be unaffected by this change. However, other crates using `wgpu-core` directly will need to copy `wgpu`'s logic or write their own. See the `[target]` section of `wgpu/Cargo.toml` for details. Similarly, `wgpu-core` now has `emscripten` and `renderdoc` features that `wgpu` enables on appropriate platforms. In previous releases, the `wgpu-core` crate decided which backends to support. However, this left `wgpu-core`'s users with no way to override those choices. (Firefox doesn't want the GLES back end, for example.) There doesn't seem to be any way to have a crate select backends based on target OS and architecture that users of that crate can still override. Default features can't be selected based on the target, for example. That implies that we should do the selection as late in the dependency DAG as feasible. Having `wgpu` (and `wgpu-core`'s other dependents) choose backends seems like the best option. By @jimblandy in [#3254](https://github.com/gfx-rs/wgpu/pull/3254). ### Changes #### General - Convert all `Default` Implementations on Enums to `derive(Default)` - Implement `Default` for `CompositeAlphaMode` - New downlevel feature `UNRESTRICTED_INDEX_BUFFER` to indicate support for using `INDEX` together with other non-copy/map usages (unsupported on WebGL). By @Wumpf in [#3157](https://github.com/gfx-rs/wgpu/pull/3157) - Add missing `DEPTH_BIAS_CLAMP` and `FULL_DRAW_INDEX_UINT32` downlevel flags. By @teoxoy in [#3316](https://github.com/gfx-rs/wgpu/pull/3316) - Combine `Surface::get_supported_formats`, `Surface::get_supported_present_modes`, and `Surface::get_supported_alpha_modes` into `Surface::get_capabilities` and `SurfaceCapabilities`. By @cwfitzgerald in [#3157](https://github.com/gfx-rs/wgpu/pull/3157) - Make `Surface::get_default_config` return an Option to prevent panics. By @cwfitzgerald in [#3157](https://github.com/gfx-rs/wgpu/pull/3157) - Lower the `max_buffer_size` limit value for compatibility with Apple2 and WebGPU compliance. By @jinleili in [#3255](https://github.com/gfx-rs/wgpu/pull/3255) - Limits `min_uniform_buffer_offset_alignment` and `min_storage_buffer_offset_alignment` is now always at least 32. By @wumpf [#3262](https://github.com/gfx-rs/wgpu/pull/3262) - Dereferencing a buffer view is now marked inline. By @Wumpf in [#3307](https://github.com/gfx-rs/wgpu/pull/3307) - The `strict_assert` family of macros was moved to `wgpu-types`. By @i509VCB in [#3051](https://github.com/gfx-rs/wgpu/pull/3051) - Make `ObjectId` structure and invariants idiomatic. By @teoxoy in [#3347](https://github.com/gfx-rs/wgpu/pull/3347) - Add validation in accordance with WebGPU `GPUSamplerDescriptor` valid usage for `lodMinClamp` and `lodMaxClamp`. By @James2022-rgb in [#3353](https://github.com/gfx-rs/wgpu/pull/3353) - Remove panics in `Deref` implementations for `QueueWriteBufferView` and `BufferViewMut`. Instead, warnings are logged, since reading from these types is not recommended. By @botahamec in [#3336] - Implement `view_formats` in the TextureDescriptor to match the WebGPU spec. By @jinleili in [#3237](https://github.com/gfx-rs/wgpu/pull/3237) - Show more information in error message for non-aligned buffer bindings in WebGL [#3414](https://github.com/gfx-rs/wgpu/pull/3414) - Update `TextureView` validation according to the WebGPU spec. By @teoxoy in [#3410](https://github.com/gfx-rs/wgpu/pull/3410) - Implement `view_formats` in the SurfaceConfiguration to match the WebGPU spec. By @jinleili in [#3409](https://github.com/gfx-rs/wgpu/pull/3409) #### Vulkan - Set `WEBGPU_TEXTURE_FORMAT_SUPPORT` downlevel flag depending on the proper format support by @teoxoy in [#3367](https://github.com/gfx-rs/wgpu/pull/3367). - Set `COPY_SRC`/`COPY_DST` only based on Vulkan's `TRANSFER_SRC`/`TRANSFER_DST` by @teoxoy in [#3366](https://github.com/gfx-rs/wgpu/pull/3366). #### GLES - Browsers that support `OVR_multiview2` now report the `MULTIVIEW` feature by @expenses in [#3121](https://github.com/gfx-rs/wgpu/pull/3121). - `Limits::max_push_constant_size` on GLES is now 256 by @Dinnerbone in [#3374](https://github.com/gfx-rs/wgpu/pull/3374). - Creating multiple pipelines with the same shaders will now be faster, by @Dinnerbone in [#3380](https://github.com/gfx-rs/wgpu/pull/3380). #### WebGPU - Implement `queue_validate_write_buffer` by @jinleili in [#3098](https://github.com/gfx-rs/wgpu/pull/3098) - Sync depth/stencil copy restrictions with the spec by @teoxoy in [#3314](https://github.com/gfx-rs/wgpu/pull/3314) ### Added/New Features #### General - Implement `Hash` for `DepthStencilState` and `DepthBiasState` - Add the `"wgsl"` feature, to enable WGSL shaders in `wgpu-core` and `wgpu`. Enabled by default in `wgpu`. By @daxpedda in [#2890](https://github.com/gfx-rs/wgpu/pull/2890). - Implement `Clone` for `ShaderSource` and `ShaderModuleDescriptor` in `wgpu`. By @daxpedda in [#3086](https://github.com/gfx-rs/wgpu/pull/3086). - Add `get_default_config` for `Surface` to simplify user creation of `SurfaceConfiguration`. By @jinleili in [#3034](https://github.com/gfx-rs/wgpu/pull/3034) - Improve compute shader validation error message. By @haraldreingruber in [#3139](https://github.com/gfx-rs/wgpu/pull/3139) - Native adapters can now use MSAA x2 and x8 if it's supported , previously only x1 and x4 were supported . By @39ali in [3140](https://github.com/gfx-rs/wgpu/pull/3140) - Implemented correleation between user timestamps and platform specific presentation timestamps via [`Adapter::get_presentation_timestamp`]. By @cwfitzgerald in [#3240](https://github.com/gfx-rs/wgpu/pull/3240) - Added support for `Features::SHADER_PRIMITIVE_INDEX` on all backends. By @cwfitzgerald in [#3272](https://github.com/gfx-rs/wgpu/pull/3272) - Implemented `TextureFormat::Stencil8`, allowing for stencil testing without depth components. By @Dinnerbone in [#3343](https://github.com/gfx-rs/wgpu/pull/3343) - Implemented `add_srgb_suffix()` for `TextureFormat` for converting linear formats to sRGB. By @Elabajaba in [#3419](https://github.com/gfx-rs/wgpu/pull/3419) - Zero-initialize workgroup memory. By @teoxoy in [#3174](https://github.com/gfx-rs/wgpu/pull/3174) #### GLES - Surfaces support now `TextureFormat::Rgba8Unorm` and (non-web only) `TextureFormat::Bgra8Unorm`. By @Wumpf in [#3070](https://github.com/gfx-rs/wgpu/pull/3070) - Support alpha to coverage. By @Wumpf in [#3156](https://github.com/gfx-rs/wgpu/pull/3156) - Support filtering f32 textures. By @expenses in [#3261](https://github.com/gfx-rs/wgpu/pull/3261) #### Vulkan - Add `SHADER_INT16` feature to enable the `shaderInt16` VkPhysicalDeviceFeature. By @Elabajaba in [#3401](https://github.com/gfx-rs/wgpu/pull/3401) #### WebGPU - Add `MULTISAMPLE_X2`, `MULTISAMPLE_X4` and `MULTISAMPLE_X8` to `TextureFormatFeatureFlags`. By @39ali in [3140](https://github.com/gfx-rs/wgpu/pull/3140) - Sync `TextureFormat.describe` with the spec. By @teoxoy in [3312](https://github.com/gfx-rs/wgpu/pull/3312) #### Metal - Add a way to create `Device` and `Queue` from raw Metal resources in wgpu-hal. By @AdrianEddy in [#3338](https://github.com/gfx-rs/wgpu/pull/3338) ### Bug Fixes #### General - Update ndk-sys to v0.4.1+23.1.7779620, to fix checksum failures. By @jimblandy in [#3232](https://github.com/gfx-rs/wgpu/pull/3232). - Bother to free the `hal::Api::CommandBuffer` when a `wgpu_core::command::CommandEncoder` is dropped. By @jimblandy in [#3069](https://github.com/gfx-rs/wgpu/pull/3069). - Fixed the mipmap example by adding the missing WRITE_TIMESTAMP_INSIDE_PASSES feature. By @Olaroll in [#3081](https://github.com/gfx-rs/wgpu/pull/3081). - Avoid panicking in some interactions with invalid resources by @nical in (#3094)[https://github.com/gfx-rs/wgpu/pull/3094] - Fixed an integer overflow in `copy_texture_to_texture` by @nical [#3090](https://github.com/gfx-rs/wgpu/pull/3090) - Remove `wgpu_types::Features::DEPTH24PLUS_STENCIL8`, making `wgpu::TextureFormat::Depth24PlusStencil8` available on all backends. By @Healthire in (#3151)[https://github.com/gfx-rs/wgpu/pull/3151] - Fix an integer overflow in `queue_write_texture` by @nical in (#3146)[https://github.com/gfx-rs/wgpu/pull/3146] - Make `RenderPassCompatibilityError` and `CreateShaderModuleError` not so huge. By @jimblandy in (#3226)[https://github.com/gfx-rs/wgpu/pull/3226] - Check for invalid bitflag bits in wgpu-core and allow them to be captured/replayed by @nical in (#3229)[https://github.com/gfx-rs/wgpu/pull/3229] - Evaluate `gfx_select!`'s `#[cfg]` conditions at the right time. By @jimblandy in [#3253](https://github.com/gfx-rs/wgpu/pull/3253) - Improve error messages when binding bind group with dynamic offsets. By @cwfitzgerald in [#3294](https://github.com/gfx-rs/wgpu/pull/3294) - Allow non-filtering sampling of integer textures. By @JMS55 in [#3362](https://github.com/gfx-rs/wgpu/pull/3362). - Validate texture ids in `Global::queue_texture_write`. By @jimblandy in [#3378](https://github.com/gfx-rs/wgpu/pull/3378). - Don't panic on mapped buffer in queue_submit. By @crowlKats in [#3364](https://github.com/gfx-rs/wgpu/pull/3364). - Fix being able to sample a depth texture with a filtering sampler. By @teoxoy in [#3394](https://github.com/gfx-rs/wgpu/pull/3394). - Make `make_spirv_raw` and `make_spirv` handle big-endian binaries. By @1e1001 in [#3411](https://github.com/gfx-rs/wgpu/pull/3411). #### Vulkan - Update ash to 0.37.1+1.3.235 to fix CI breaking by changing a call to the deprecated `debug_utils_set_object_name()` function to `set_debug_utils_object_name()` by @elabajaba in [#3273](https://github.com/gfx-rs/wgpu/pull/3273) - Document and improve extension detection. By @teoxoy in [#3327](https://github.com/gfx-rs/wgpu/pull/3327) - Don't use a pointer to a local copy of a `PhysicalDeviceDriverProperties` struct after it has gone out of scope. In fact, don't make a local copy at all. Introduce a helper function for building `CStr`s from C character arrays, and remove some `unsafe` blocks. By @jimblandy in [#3076](https://github.com/gfx-rs/wgpu/pull/3076). #### DX12 - Fix `depth16Unorm` formats by @teoxoy in [#3313](https://github.com/gfx-rs/wgpu/pull/3313) - Don't re-use `GraphicsCommandList` when `close` or `reset` fails. By @xiaopengli89 in [#3204](https://github.com/gfx-rs/wgpu/pull/3204) #### Metal - Fix texture view creation with full-resource views when using an explicit `mip_level_count` or `array_layer_count`. By @cwfitzgerald in [#3323](https://github.com/gfx-rs/wgpu/pull/3323) #### GLES - Fixed WebGL not displaying srgb targets correctly if a non-screen filling viewport was previously set. By @Wumpf in [#3093](https://github.com/gfx-rs/wgpu/pull/3093) - Fix disallowing multisampling for float textures if otherwise supported. By @Wumpf in [#3183](https://github.com/gfx-rs/wgpu/pull/3183) - Fix a panic when creating a pipeline with opaque types other than samplers (images and atomic counters). By @James2022-rgb in [#3361](https://github.com/gfx-rs/wgpu/pull/3361) - Fix uniform buffers being empty on some vendors. By @Dinnerbone in [#3391](https://github.com/gfx-rs/wgpu/pull/3391) - Fix a panic allocating a new buffer on webgl. By @Dinnerbone in [#3396](https://github.com/gfx-rs/wgpu/pull/3396) #### WebGPU - Use `log` instead of `println` in hello example by @JolifantoBambla in [#2858](https://github.com/gfx-rs/wgpu/pull/2858) #### deno-webgpu - Let `setVertexBuffer` and `setIndexBuffer` calls on `GPURenderBundleEncoder` throw an error if the `size` argument is zero, rather than treating that as "until the end of the buffer". By @jimblandy in [#3171](https://github.com/gfx-rs/wgpu/pull/3171) #### Emscripten - Let the wgpu examples `framework.rs` compile again under Emscripten. By @jimblandy in [#3246](https://github.com/gfx-rs/wgpu/pull/3246) ### Examples - Log adapter info in hello example on wasm target by @JolifantoBambla in [#2858](https://github.com/gfx-rs/wgpu/pull/2858) - Added new example `stencil-triangles` to show basic use of stencil testing. By @Dinnerbone in [#3343](https://github.com/gfx-rs/wgpu/pull/3343) ### Testing/Internal - Update the `minimum supported rust version` to 1.64 - Move `ResourceMetadata` into its own module. By @jimblandy in [#3213](https://github.com/gfx-rs/wgpu/pull/3213) - Add WebAssembly testing infrastructure. By @haraldreingruber in [#3238](https://github.com/gfx-rs/wgpu/pull/3238) - Error message when you forget to use cargo-nextest. By @cwfitzgerald in [#3293](https://github.com/gfx-rs/wgpu/pull/3293) - Fix all suggestions from `cargo clippy` ## wgpu-0.14.2 (2022-11-28) ### Bug Fixes - Fix incorrect offset in `get_mapped_range` by @nical in [#3233](https://github.com/gfx-rs/wgpu/pull/3233) ## wgpu-0.14.1 (2022-11-02) ### Bug Fixes - Make `wgpu::TextureFormat::Depth24PlusStencil8` available on all backends by making the feature unconditionally available and the feature unneeded to use the format. By @Healthire and @cwfitzgerald in [#3165](https://github.com/gfx-rs/wgpu/pull/3165) ## wgpu-0.14.0 (2022-10-05) ### Major Changes #### @invariant Warning When using CompareFunction::Equal or CompareFunction::NotEqual on a pipeline, there is now a warning logged if the vertex shader does not have a @invariant tag on it. On some machines, rendering the same triangles multiple times without an @invariant tag will result in slightly different depths for every pixel. Because the \*Equal functions rely on depth being the same every time it is rendered, we now warn if it is missing. ```diff -@vertex -fn vert_main(v_in: VertexInput) -> @builtin(position) vec4 {...} +@vertex +fn vert_main(v_in: VertexInput) -> @builtin(position) @invariant vec4 {...} ``` #### Surface Alpha and PresentModes Surface supports `alpha_mode` now. When alpha_mode is equal to `PreMultiplied` or `PostMultiplied`, the alpha channel of framebuffer is respected in the compositing process, but which mode is available depends on the different API and `Device`. If don't care about alpha_mode, you can set it to `Auto`. ```diff SurfaceConfiguration { // ... + alpha_mode: surface.get_supported_alpha_modes(&adapter)[0], } ``` The function to enumerate supported presentation modes changed: ```diff - pub fn wgpu::Surface::get_supported_modes(&self, adapter: &wgpu::Adapter) -> Vec + pub fn wgpu::Surface::get_supported_present_modes(&self, adapter: &wgpu::Adapter) -> Vec ``` #### Updated raw-window-handle to 0.5 This will allow use of the latest version of winit. As such the bound on create_surface is now RWH 0.5 and requires both `raw_window_handle::HasRawWindowHandle` and `raw_window_handle::HasRawDisplayHandle`. ### Added/New Features - Add `Buffer::size()` and `Buffer::usage()`; by @kpreid in [#2923](https://github.com/gfx-rs/wgpu/pull/2923) - Split Blendability and Filterability into Two Different TextureFormatFeatureFlags; by @stakka in [#3012](https://github.com/gfx-rs/wgpu/pull/3012) - Expose `alpha_mode` on SurfaceConfiguration, by @jinleili in [#2836](https://github.com/gfx-rs/wgpu/pull/2836) - Introduce fields for driver name and info in `AdapterInfo`, by @i509VCB in [#3037](https://github.com/gfx-rs/wgpu/pull/3037) - Add way to create gles hal textures from raw gl names to allow externally managed textures. By @i509VCB [#3046](https://github.com/gfx-rs/wgpu/pull/3046) - Implemented `copy_external_image_to_texture` on WebGPU, by @ybiletskyi in [#2781](https://github.com/gfx-rs/wgpu/pull/2781) ### Bug Fixes #### General - Free `StagingBuffers` even when an error occurs in the operation that consumes them. By @jimblandy in [#2961](https://github.com/gfx-rs/wgpu/pull/2961) - Avoid overflow when checking that texture copies fall within bounds. By @jimblandy in [#2963](https://github.com/gfx-rs/wgpu/pull/2963) - Improve the validation and error reporting of buffer mappings by @nical in [#2848](https://github.com/gfx-rs/wgpu/pull/2848) - Fix compilation errors when using wgpu-core in isolation while targeting `wasm32-unknown-unknown` by @Seamooo in [#2922](https://github.com/gfx-rs/wgpu/pull/2922) - Fixed opening of RenderDoc library by @abuffseagull in [#2930](https://github.com/gfx-rs/wgpu/pull/2930) - Added missing validation for `BufferUsages` mismatches when `Features::MAPPABLE_PRIMARY_BUFFERS` is not enabled. By @imberflur in [#3023](https://github.com/gfx-rs/wgpu/pull/3023) - Fixed `CommandEncoder` not being `Send` and `Sync` on web by @i509VCB in [#3025](https://github.com/gfx-rs/wgpu/pull/3025) - Document meaning of `vendor` in `AdapterInfo` if the vendor has no PCI id. - Fix missing resource labels from some Errors by @scoopr in [#3066](https://github.com/gfx-rs/wgpu/pull/3066) #### Metal - Add the missing `msg_send![view, retain]` call within `from_view` by @jinleili in [#2976](https://github.com/gfx-rs/wgpu/pull/2976) - Fix `max_buffer` `max_texture` and `max_vertex_buffers` limits by @jinleili in [#2978](https://github.com/gfx-rs/wgpu/pull/2978) - Remove PrivateCapabilities's `format_rgb10a2_unorm_surface` field by @jinleili in [#2981](https://github.com/gfx-rs/wgpu/pull/2981) - Fix validation error when copying into a subset of a single-layer texture by @nical in [#3063](https://github.com/gfx-rs/wgpu/pull/3063) - Fix `_buffer_sizes` encoding by @dtiselice in [#3047](https://github.com/gfx-rs/wgpu/pull/3047) #### Vulkan - Fix `astc_hdr` formats support by @jinleili in [#2971]](https://github.com/gfx-rs/wgpu/pull/2971) - Update to naga b209d911 (2022-9-1) to avoid generating SPIR-V that violates Vulkan valid usage rules `VUID-StandaloneSpirv-Flat-06202` and `VUID-StandaloneSpirv-Flat-04744`. By @jimblandy in [#3008](https://github.com/gfx-rs/wgpu/pull/3008) - Fix bug where the Vulkan backend would panic when using a supported window and display handle but the dependent extensions are not available by @i509VCB in [#3054](https://github.com/gfx-rs/wgpu/pull/3054). #### GLES - Report vendor id for Mesa and Apple GPUs. By @i509VCB [#3036](https://github.com/gfx-rs/wgpu/pull/3036) - Report Apple M2 gpu as integrated. By @i509VCB [#3036](https://github.com/gfx-rs/wgpu/pull/3036) #### WebGPU - When called in a web worker, `Context::init()` now uses `web_sys::WorkerGlobalContext` to create a `wgpu::Instance` instead of trying to access the unavailable `web_sys::Window` by @JolifantoBambla in [#2858](https://github.com/gfx-rs/wgpu/pull/2858) ### Changes #### General - Changed wgpu-hal and wgpu-core implementation to pass RawDisplayHandle and RawWindowHandle as separate parameters instead of passing an impl trait over both HasRawDisplayHandle and HasRawWindowHandle. By @i509VCB in [#3022](https://github.com/gfx-rs/wgpu/pull/3022) - Changed `Instance::as_hal` to just return an `Option<&A::Instance>` rather than taking a callback. By @jimb in [#2991](https://github.com/gfx-rs/wgpu/pull/2991) - Added downlevel restriction error message for `InvalidFormatUsages` error by @Seamooo in [#2886](https://github.com/gfx-rs/wgpu/pull/2886) - Add warning when using CompareFunction::\*Equal with vertex shader that is missing @invariant tag by @cwfitzgerald in [#2887](https://github.com/gfx-rs/wgpu/pull/2887) - Update Winit to version 0.27 and raw-window-handle to 0.5 by @wyatt-herkamp in [#2918](https://github.com/gfx-rs/wgpu/pull/2918) - Address Clippy 0.1.63 complaints. By @jimblandy in [#2977](https://github.com/gfx-rs/wgpu/pull/2977) - Don't use `PhantomData` for `IdentityManager`'s `Input` type. By @jimblandy in [#2972](https://github.com/gfx-rs/wgpu/pull/2972) - Changed naga variant in ShaderSource to `Cow<'static, Module>`, to allow loading global variables by @daxpedda in [#2903](https://github.com/gfx-rs/wgpu/pull/2903) - Updated the maximum binding index to match the WebGPU specification by @nical in [#2957](https://github.com/gfx-rs/wgpu/pull/2957) - Add `unsafe_op_in_unsafe_fn` to Clippy lints in the entire workspace. By @ErichDonGubler in [#3044](https://github.com/gfx-rs/wgpu/pull/3044). #### Metal - Extract the generic code into `get_metal_layer` by @jinleili in [#2826](https://github.com/gfx-rs/wgpu/pull/2826) #### Vulkan - Remove use of Vulkan12Features/Properties types. By @i509VCB in [#2936](https://github.com/gfx-rs/wgpu/pull/2936) - Provide a means for `wgpu` users to access `vk::Queue` and the queue index. By @anlumo in [#2950](https://github.com/gfx-rs/wgpu/pull/2950) - Use the use effective api version for determining device features instead of wrongly assuming `VkPhysicalDeviceProperties.apiVersion` is the actual version of the device. By @i509VCB in [#3011](https://github.com/gfx-rs/wgpu/pull/3011) - `DropGuard` has been moved to the root of the wgpu-hal crate. By @i509VCB [#3046](https://github.com/gfx-rs/wgpu/pull/3046) #### GLES - Add `Rgba16Float` format support for color attachments. By @jinleili in [#3045](https://github.com/gfx-rs/wgpu/pull/3045) - `TEXTURE_COMPRESSION_ASTC_HDR` feature detection by @jinleili in [#3042](https://github.com/gfx-rs/wgpu/pull/3042) ### Performance - Made `StagingBelt::write_buffer()` check more thoroughly for reusable memory; by @kpreid in [#2906](https://github.com/gfx-rs/wgpu/pull/2906) ### Documentation - Add WGSL examples to complement existing examples written in GLSL by @norepimorphism in [#2888](https://github.com/gfx-rs/wgpu/pull/2888) - Document `wgpu_core` resource allocation. @jimblandy in [#2973](https://github.com/gfx-rs/wgpu/pull/2973) - Expanded `StagingBelt` documentation by @kpreid in [#2905](https://github.com/gfx-rs/wgpu/pull/2905) - Fixed documentation for `Instance::create_surface_from_canvas` and `Instance::create_surface_from_offscreen_canvas` regarding their safety contract. These functions are not unsafe. By @jimblandy [#2990](https://github.com/gfx-rs/wgpu/pull/2990) - Document that `write_buffer_with()` is sound but unwise to read from by @kpreid in [#3006](https://github.com/gfx-rs/wgpu/pull/3006) - Explain why `Adapter::as_hal` and `Device::as_hal` have to take callback functions. By @jimblandy in [#2992](https://github.com/gfx-rs/wgpu/pull/2992) ### Dependency Updates #### WebGPU - Update wasm32 dependencies, set `alpha_mode` on web target by @jinleili in [#3040](https://github.com/gfx-rs/wgpu/pull/3040) ### Build Configuration - Add the `"strict_asserts"` feature, to enable additional internal run-time validation in `wgpu-core`. By @jimblandy in [#2872](https://github.com/gfx-rs/wgpu/pull/2872). ### Full API Diff Manual concatenation of `cargo public-api --diff-git-checkouts v0.13.2 v0.14.0 -p wgpu` and `cargo public-api --diff-git-checkouts v0.13.2 v0.14.0 -p wgpu-types` ```diff Removed items from the public API ================================= -pub fn wgpu::Surface::get_supported_modes(&self, adapter: &wgpu::Adapter) -> Vec -pub const wgpu::Features::DEPTH24UNORM_STENCIL8: Self -pub enum variant wgpu::TextureFormat::Depth24UnormStencil8 Changed items in the public API =============================== -pub unsafe fn wgpu::Instance::as_hal::Instance>) -> R, R>(&self, hal_instance_callback: F) -> R +pub unsafe fn wgpu::Instance::as_hal(&self) -> Option<&::Instance> -pub unsafe fn wgpu::Instance::create_surface(&self, window: &W) -> wgpu::Surface +pub unsafe fn wgpu::Instance::create_surface(&self, window: &W) -> wgpu::Surface Added items to the public API ============================= +pub fn wgpu::Buffer::size(&self) -> wgt::BufferAddress +pub fn wgpu::Buffer::usage(&self) -> BufferUsages +pub fn wgpu::Surface::get_supported_alpha_modes(&self, adapter: &wgpu::Adapter) -> Vec +pub fn wgpu::Surface::get_supported_present_modes(&self, adapter: &wgpu::Adapter) -> Vec +#[repr(C)] pub enum wgpu::CompositeAlphaMode +impl RefUnwindSafe for wgpu::CompositeAlphaMode +impl Send for wgpu::CompositeAlphaMode +impl Sync for wgpu::CompositeAlphaMode +impl Unpin for wgpu::CompositeAlphaMode +impl UnwindSafe for wgpu::CompositeAlphaMode +pub const wgpu::Features::DEPTH24PLUS_STENCIL8: Self +pub const wgpu::TextureFormatFeatureFlags::BLENDABLE: Self +pub enum variant wgpu::CompositeAlphaMode::Auto = 0 +pub enum variant wgpu::CompositeAlphaMode::Inherit = 4 +pub enum variant wgpu::CompositeAlphaMode::Opaque = 1 +pub enum variant wgpu::CompositeAlphaMode::PostMultiplied = 3 +pub enum variant wgpu::CompositeAlphaMode::PreMultiplied = 2 +pub enum variant wgpu::TextureFormat::Depth16Unorm +pub fn wgpu::CompositeAlphaMode::clone(&self) -> wgpu::CompositeAlphaMode +pub fn wgpu::CompositeAlphaMode::eq(&self, other: &wgpu::CompositeAlphaMode) -> bool +pub fn wgpu::CompositeAlphaMode::fmt(&self, f: &mut $crate::fmt::Formatter<'_>) -> $crate::fmt::Result +pub fn wgpu::CompositeAlphaMode::hash<__H: $crate::hash::Hasher>(&self, state: &mut __H) -> () +pub struct field wgpu::AdapterInfo::driver: String +pub struct field wgpu::AdapterInfo::driver_info: String +pub struct field wgpu::SurfaceConfiguration::alpha_mode: wgpu_types::CompositeAlphaMode ``` ## wgpu-0.13.2 (2022-07-13) ### Bug Fixes #### General - Prefer `DeviceType::DiscreteGpu` over `DeviceType::Other` for `PowerPreference::LowPower` so Vulkan is preferred over OpenGL again by @Craig-Macomber in [#2853](https://github.com/gfx-rs/wgpu/pull/2853) - Allow running `get_texture_format_features` on unsupported texture formats (returning no flags) by @cwfitzgerald in [#2856](https://github.com/gfx-rs/wgpu/pull/2856) - Allow multi-sampled textures that are supported by the device but not WebGPU if `TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES` is enabled by @cwfitzgerald in [#2856](https://github.com/gfx-rs/wgpu/pull/2856) - `get_texture_format_features` only lists the COPY\_\* usages if the adapter actually supports that usage by @cwfitzgerald in [#2856](https://github.com/gfx-rs/wgpu/pull/2856) - Fix bind group / pipeline deduplication not taking into account RenderBundle execution resetting these values by @shoebe [#2867](https://github.com/gfx-rs/wgpu/pull/2867) - Fix panics that occur when using `as_hal` functions when the hal generic type does not match the hub being looked up in by @i509VCB [#2871](https://github.com/gfx-rs/wgpu/pull/2871) - Add some validation in map_async by @nical in [#2876](https://github.com/gfx-rs/wgpu/pull/2876) - Fix bugs when mapping/unmapping zero-sized buffers and ranges by @nical in [#2877](https://github.com/gfx-rs/wgpu/pull/2877) - Fix out-of-bound write in `map_buffer` with non-zero offset by @nical in [#2916](https://github.com/gfx-rs/wgpu/pull/2916) - Validate the number of color attachments in `create_render_pipeline` by @nical in [#2913](https://github.com/gfx-rs/wgpu/pull/2913) - Validate against the maximum binding index in `create_bind_group_layout` by @nical in [#2892](https://github.com/gfx-rs/wgpu/pull/2892) - Validate that map_async's range is not negative by @nical in [#2938](https://github.com/gfx-rs/wgpu/pull/2938) - Fix calculation/validation of layer/mip ranges in create_texture_view by @nical in [#2955](https://github.com/gfx-rs/wgpu/pull/2955) - Validate the sample count and mip level in `copy_texture_to_buffer` by @nical in [#2958](https://github.com/gfx-rs/wgpu/pull/2958) - Expose the cause of the error in the `map_async` callback in [#2939](https://github.com/gfx-rs/wgpu/pull/2939) #### DX12 - `DownlevelCapabilities::default()` now returns the `ANISOTROPIC_FILTERING` flag set to true so DX12 lists `ANISOTROPIC_FILTERING` as true again by @cwfitzgerald in [#2851](https://github.com/gfx-rs/wgpu/pull/2851) - Properly query format features for UAV/SRV usages of depth formats by @cwfitzgerald in [#2856](https://github.com/gfx-rs/wgpu/pull/2856) #### Vulkan - Vulkan 1.0 drivers that support `VK_KHR_multiview` now properly report the `MULTIVIEW` feature as supported by @i509VCB in [#2934](https://github.com/gfx-rs/wgpu/pull/2934). - Stop using `VkPhysicalDevice11Features` in Vulkan 1.1 which is confusingly provided in Vulkan 1.2 by @i509VCB in [#2934](https://github.com/gfx-rs/wgpu/pull/2934). #### GLES - Fix depth stencil texture format capability by @jinleili in [#2854](https://github.com/gfx-rs/wgpu/pull/2854) - `get_texture_format_features` now only returns usages for formats it actually supports by @cwfitzgerald in [#2856](https://github.com/gfx-rs/wgpu/pull/2856) #### Hal - Allow access to queue family index in Vulkan hal by @i509VCB in [#2859](https://github.com/gfx-rs/wgpu/pull/2859) - Allow access to the EGLDisplay and EGLContext pointer in Gles hal Adapter and Device by @i509VCB in [#2860](https://github.com/gfx-rs/wgpu/pull/2860) ### Documentation - Update present_mode docs as most of them don't automatically fall back to Fifo anymore. by @Elabajaba in [#2855](https://github.com/gfx-rs/wgpu/pull/2855) #### Hal - Document safety requirements for `Adapter::from_external` in gles hal by @i509VCB in [#2863](https://github.com/gfx-rs/wgpu/pull/2863) - Make `AdapterContext` a publicly accessible type in the gles hal by @i509VCB in [#2870](https://github.com/gfx-rs/wgpu/pull/2870) ## wgpu-0.13.1 (2022-07-02) ### Bug Fixes #### General - Fix out of bounds access when surface texture is written to by multiple command buffers by @cwfitzgerald in [#2843](https://github.com/gfx-rs/wgpu/pull/2843) #### GLES - AutoNoVSync now correctly falls back to Fifo by @simbleau in [#2842](https://github.com/gfx-rs/wgpu/pull/2842) - Fix GL_EXT_color_buffer_float detection on native by @cwfitzgerald in [#2843](https://github.com/gfx-rs/wgpu/pull/2843) ## wgpu-0.13 (2022-06-30) ### Major Changes #### WGSL Syntax WGSL syntax has changed in a couple ways. The new syntax is easier to read and work with. Attribute declarations are written differently: ```diff - [[group(1), binding(0)]] + @group(1) @binding(0) ``` Stage declarations are now separate attributes rather than part of the `stage` attribute: ```diff - [[stage(vertex)]] + @vertex ``` Structs now use `,` as field separator and no longer need semicolons after the declaration: ```diff - struct MyStruct { - my_member: u32; - }; + struct MyStruct { + my_member: u32, + } ``` #### Surface API The method of getting the preferred swapchain format has changed to allow viewing all formats supported by the surface. ```diff - let format = surface.get_preferred_format(&adapter).unwrap(); + let format = surface.get_supported_formats(&adapter)[0]; ``` Presentation modes now need to match exactly what the surface supports. `FIFO` is _always_ supported, but all other modes vary from API to API and `Device` to `Device`. To get a list of all supported modes, call the following. The order does not indicate preference. ```rust let modes = surface.get_supported_present_modes(&adapter); ``` #### Timestamp Queries Timestamp queries are now restricted behind multiple features to allow implementation on TBDR (Tile-Based Deferred Rendering) based GPUs, such as mobile devices and Apple's M chips. `Features::TIMESTAMP_QUERIES` now allows for calling `write_timestamp` only on `CommandEncoder`s. `Features::WRITE_TIMESTAMP_INSIDE_PASSES` is needed to call `write_timestamp` on `RenderPassEncoder`s or `ComputePassEncoder`s. #### map_async The function for mapping buffers no longer returns a future, and instead calls a callback when the buffer is mapped. This aligns with the use of the API more clearly - you aren't supposed to block and wait on the future to resolve, you are supposed to keep rendering and wait until the buffer maps on its own. Mapping and the flow of mapping is an under-documented area that we hope to improve in the future. ```diff - let future = buffer.slice(..).map_async(MapMode::Read); + buffer.slice(..).map_async(MapMode::Read, || { + // Called when buffer is mapped. + }) ``` #### Submission Indexes Calling `queue.submit` now returns an opaque submission index that can be used as an argument to `device.poll` to say which submission to wait to complete. ### Other Breaking Changes `Device::create_shader_module` now takes the shader descriptor by value: ```diff - device.create_shader_module(&shader_module_descriptor) + device.create_shader_module(shader_module_descriptor) ``` Color attachments can be sparse, so they are now optional: ```diff FragmentState { - targets: &[color_target_state] + targets: &[Some(color_target_state)] // .. } ``` ```diff RenderPassDescriptor { - color_attachments: &[render_pass_color_attachment] + color_attachments: &[Some(render_pass_color_attachment)] // .. } ``` ```diff RenderBundleEncoderDescriptor { - color_formats: &[texture_format] + color_formats: &[Some(texture_format)] // .. } ``` `Extent3d::max_mips` now requires you to pass a TextureDimension to specify whether or not depth_or_array_layers should be ignored: ```diff Extent3d { width: 1920, height: 1080, depth_or_array_layers: 6, - }.max_mips() + }.max_mips(wgpu::TextureDimension::D3) ``` `Limits` has a new field, [`max_buffer_size`](https://docs.rs/wgpu/0.13.0/wgpu/struct.Limits.html#structfield.max_buffer_size) (not an issue if you don't define limits manually): ```diff Limits { // ... + max_buffer_size: 256 * 1024 * 1024, // adjust as you see fit } ``` `Features::CLEAR_COMMANDS` is now unnecessary and no longer exists. The feature to clear buffers and textures is now part of upstream WebGPU. ```diff DeviceDescriptor { // ... features: wgpu::Features::VERTEX_WRITABLE_STORAGE | wgpu::Features::MAPPABLE_PRIMARY_BUFFERS | wgpu::Features::TEXTURE_BINDING_ARRAY | wgpu::Features::BUFFER_BINDING_ARRAY | wgpu::Features::STORAGE_RESOURCE_BINDING_ARRAY - | wgpu::Features::CLEAR_COMMANDS , } ``` `ComputePass::dispatch` has been renamed to `ComputePass::dispatch_workgroups` ```diff - cpass.dispatch(self.work_group_count, 1, 1) + cpass.dispatch_workgroups(self.work_group_count, 1, 1) ``` ### Added/New Features #### General - Add `util::indirect::*` helper structs by @IcanDivideBy0 in [#2365](https://github.com/gfx-rs/wgpu/pull/2365) - Add `AddressMode::ClampToZero` by @laptou in [#2364](https://github.com/gfx-rs/wgpu/pull/2364) - Add MULTISAMPLED_SHADING downlevel flag by @jinleili in [#2425](https://github.com/gfx-rs/wgpu/pull/2425) - Allow non struct buffers in wgsl by @IcanDivideBy0 in [#2451](https://github.com/gfx-rs/wgpu/pull/2451) - Prefix every wgpu-generated label with `(wgpu)`. by @kpreid in [#2590](https://github.com/gfx-rs/wgpu/pull/2590) - Permit non-struct, non-array types as buffers. by @jimblandy in [#2584](https://github.com/gfx-rs/wgpu/pull/2584) - Return `queue_empty` for Device::poll by @xiaopengli89 in [#2643](https://github.com/gfx-rs/wgpu/pull/2643) - Add `SHADER_FLOAT16` feature by @jinleili in [#2646](https://github.com/gfx-rs/wgpu/pull/2646) - Add DEPTH32FLOAT_STENCIL8 feature by @jinleili in [#2664](https://github.com/gfx-rs/wgpu/pull/2664) - Add DEPTH24UNORM_STENCIL8 feature by @jinleili in [#2689](https://github.com/gfx-rs/wgpu/pull/2689) - Implement submission indexes by @cwfitzgerald in [#2700](https://github.com/gfx-rs/wgpu/pull/2700) - [WebGL] Add a downlevel capability for rendering to floating point textures by @expenses in [#2729](https://github.com/gfx-rs/wgpu/pull/2729) - allow creating wgpu::Instance from wgpu_core::Instance by @i509VCB in [#2763](https://github.com/gfx-rs/wgpu/pull/2763) - Force binding sizes to be multiples of 16 on webgl by @cwfitzgerald in [#2808](https://github.com/gfx-rs/wgpu/pull/2808) - Add naga variant to ShaderSource by @rttad in [#2801](https://github.com/gfx-rs/wgpu/pull/2801) - Implement Queue::write_buffer_with by @teoxoy in [#2777](https://github.com/gfx-rs/wgpu/pull/2777) #### Vulkan - Re-allow vk backend on Apple platforms via `vulkan-portability` feature by @jinleili in [#2488](https://github.com/gfx-rs/wgpu/pull/2488) - vulkan: HDR ASTC formats support by @jinleili in [#2496](https://github.com/gfx-rs/wgpu/pull/2496) #### Metal - Implement push constants for metal backend by @TheOnlyMrCat in [#2314](https://github.com/gfx-rs/wgpu/pull/2314) - Metal backend ASTC HDR formats support by @jinleili in [#2477](https://github.com/gfx-rs/wgpu/pull/2477) - Add COPY_DST to Metal's surface usage bits by @vl4dimir in [#2491](https://github.com/gfx-rs/wgpu/pull/2491) - Add `Features::MULTI_DRAW_INDIRECT` to Metal by @expenses in [#2737](https://github.com/gfx-rs/wgpu/pull/2737) #### GLES - Support externally initialized contexts by @kvark in [#2350](https://github.com/gfx-rs/wgpu/pull/2350) - Angle support on macOS by @jinleili in [#2461](https://github.com/gfx-rs/wgpu/pull/2461) - Use EGL surfaceless platform when windowing system is not found by @sh7dm in [#2339](https://github.com/gfx-rs/wgpu/pull/2339) - Do a downlevel check for anisotrophy and enable it in the webgl backend by @expenses in [#2616](https://github.com/gfx-rs/wgpu/pull/2616) - OffscreenCanvas Support for WebGL Backend by @haraldreingruber-dedalus in [#2603](https://github.com/gfx-rs/wgpu/pull/2603) #### DX12 - Support to create surface from visual on Windows by @xiaopengli89 in [#2434](https://github.com/gfx-rs/wgpu/pull/2434) - Add raw_queue for d3d12 device by @xiaopengli89 in [#2600](https://github.com/gfx-rs/wgpu/pull/2600) #### DX11 - Skeleton of a DX11 backend - not working yet by @cwfitzgerald in [#2443](https://github.com/gfx-rs/wgpu/pull/2443) #### Hal - Adapter and Instance as_hal functions by @i509VCB in [#2663](https://github.com/gfx-rs/wgpu/pull/2663) - expose some underlying types in Vulkan hal by @i509VCB in [#2667](https://github.com/gfx-rs/wgpu/pull/2667) - Add raw_device method for dx12, vulkan hal by @xiaopengli89 in [#2360](https://github.com/gfx-rs/wgpu/pull/2360) - expose egl display in gles Instance hal by @i509VCB in [#2670](https://github.com/gfx-rs/wgpu/pull/2670) - Add raw_adapter method for dx12 hal adapter by @xiaopengli89 in [#2714](https://github.com/gfx-rs/wgpu/pull/2714) - Acquire texture: `Option` timeouts by @rib in [#2724](https://github.com/gfx-rs/wgpu/pull/2724) - expose vulkan physical device capabilities, enabled device extensions by @i509VCB in [#2688](https://github.com/gfx-rs/wgpu/pull/2688) #### Emscripten - feature: emscripten by @caiiiycuk in [#2422](https://github.com/gfx-rs/wgpu/pull/2422) - feature = emscripten, compatibility fixes for wgpu-native by @caiiiycuk in [#2450](https://github.com/gfx-rs/wgpu/pull/2450) ### Changes #### General - Make ShaderSource #[non_exhaustive] by @fintelia in [#2312](https://github.com/gfx-rs/wgpu/pull/2312) - Make `execute_bundles()` receive IntoIterator by @maku693 in [#2410](https://github.com/gfx-rs/wgpu/pull/2410) - Raise `wgpu_hal::MAX_COLOR_TARGETS` to 8. by @jimblandy in [#2640](https://github.com/gfx-rs/wgpu/pull/2640) - Rename dispatch -> dispatch_workgroups by @jinleili in [#2619](https://github.com/gfx-rs/wgpu/pull/2619) - Update texture_create_view logic to match spec by @jinleili in [#2621](https://github.com/gfx-rs/wgpu/pull/2621) - Move TEXTURE_COMPRESSION_ETC2 | ASTC_LDR to web section to match spec by @jinleili in [#2671](https://github.com/gfx-rs/wgpu/pull/2671) - Check that all vertex outputs are consumed by the fragment shader by @cwfitzgerald in [#2704](https://github.com/gfx-rs/wgpu/pull/2704) - Convert map_async from being async to being callback based by @cwfitzgerald in [#2698](https://github.com/gfx-rs/wgpu/pull/2698) - Align the validation of Device::create_texture with the WebGPU spec by @nical in [#2759](https://github.com/gfx-rs/wgpu/pull/2759) - Add InvalidGroupIndex validation at create_shader_module by @jinleili in [#2775](https://github.com/gfx-rs/wgpu/pull/2775) - Rename MAX_COLOR_TARGETS to MAX_COLOR_ATTACHMENTS to match spec by @jinleili in [#2780](https://github.com/gfx-rs/wgpu/pull/2780) - Change get_preferred_format to get_supported_formats by @stevenhuyn in [#2783](https://github.com/gfx-rs/wgpu/pull/2783) - Restrict WriteTimestamp Inside Passes by @cwfitzgerald in [#2802](https://github.com/gfx-rs/wgpu/pull/2802) - Flip span labels to work better with tools by @cwfitzgerald in [#2820](https://github.com/gfx-rs/wgpu/pull/2820) #### Gles - Make GLES DeviceType unknown by default by @PolyMeilex in [#2647](https://github.com/gfx-rs/wgpu/pull/2647) #### Metal - metal: check if in the main thread when calling `create_surface` by @jinleili in [#2736](https://github.com/gfx-rs/wgpu/pull/2736) #### Hal - limit binding sizes to i32 by @kvark in [#2363](https://github.com/gfx-rs/wgpu/pull/2363) ### Bug Fixes #### General - Fix trac(y/ing) compile issue by @cwfitzgerald in [#2333](https://github.com/gfx-rs/wgpu/pull/2333) - Improve detection and validation of cubemap views by @kvark in [#2331](https://github.com/gfx-rs/wgpu/pull/2331) - Don't create array layer trackers for 3D textures. by @ElectronicRU in [#2348](https://github.com/gfx-rs/wgpu/pull/2348) - Limit 1D texture mips to 1 by @kvark in [#2374](https://github.com/gfx-rs/wgpu/pull/2374) - Texture format MSAA capabilities by @kvark in [#2377](https://github.com/gfx-rs/wgpu/pull/2377) - Fix write_buffer to surface texture @kvark in [#2385](https://github.com/gfx-rs/wgpu/pull/2385) - Improve some error messages by @cwfitzgerald in [#2446](https://github.com/gfx-rs/wgpu/pull/2446) - Don't recycle indices that reach EOL by @kvark in [#2462](https://github.com/gfx-rs/wgpu/pull/2462) - Validated render usages for 3D textures by @kvark in [#2482](https://github.com/gfx-rs/wgpu/pull/2482) - Wrap all validation logs with catch_unwinds by @cwfitzgerald in [#2511](https://github.com/gfx-rs/wgpu/pull/2511) - Fix clippy lints by @a1phyr in [#2560](https://github.com/gfx-rs/wgpu/pull/2560) - Free the raw device when `wgpu::Device` is dropped. by @jimblandy in [#2567](https://github.com/gfx-rs/wgpu/pull/2567) - wgpu-core: Register new pipelines with device's tracker. by @jimblandy in [#2565](https://github.com/gfx-rs/wgpu/pull/2565) - impl Debug for StagingBelt by @kpreid in [#2572](https://github.com/gfx-rs/wgpu/pull/2572) - Use fully qualified syntax for some calls. by @jimblandy in [#2655](https://github.com/gfx-rs/wgpu/pull/2655) - fix: panic in `Storage::get` by @SparkyPotato in [#2657](https://github.com/gfx-rs/wgpu/pull/2657) - Report invalid pipelines in render bundles as errors, not panics. by @jimblandy in [#2666](https://github.com/gfx-rs/wgpu/pull/2666) - Perform "valid to use with" checks when recording render bundles. by @jimblandy in [#2690](https://github.com/gfx-rs/wgpu/pull/2690) - Stop using storage usage for sampling by @cwfitzgerald in [#2703](https://github.com/gfx-rs/wgpu/pull/2703) - Track depth and stencil writability separately. by @jimblandy in [#2693](https://github.com/gfx-rs/wgpu/pull/2693) - Improve InvalidScissorRect error message by @jinleili in [#2713](https://github.com/gfx-rs/wgpu/pull/2713) - Improve InvalidViewport error message by @jinleili in [#2723](https://github.com/gfx-rs/wgpu/pull/2723) - Don't dirty the vertex buffer for stride/rate changes on bundles. by @jimblandy in [#2744](https://github.com/gfx-rs/wgpu/pull/2744) - Clean up render bundle index buffer tracking. by @jimblandy in [#2743](https://github.com/gfx-rs/wgpu/pull/2743) - Improve read-write and read-only texture storage error message by @jinleili in [#2745](https://github.com/gfx-rs/wgpu/pull/2745) - Change `WEBGPU_TEXTURE_FORMAT_SUPPORT` to `1 << 14` instead of `1 << 15` by @expenses in [#2772](https://github.com/gfx-rs/wgpu/pull/2772) - fix BufferMapCallbackC & SubmittedWorkDoneClosureC by @rajveermalviya in [#2787](https://github.com/gfx-rs/wgpu/pull/2787) - Fix formatting of `TextureDimensionError::LimitExceeded`. by @kpreid in [#2799](https://github.com/gfx-rs/wgpu/pull/2799) - Remove redundant `#[cfg]` conditions from `backend/direct.rs`. by @jimblandy in [#2811](https://github.com/gfx-rs/wgpu/pull/2811) - Replace android-properties with android_system_properties. by @nical in [#2815](https://github.com/gfx-rs/wgpu/pull/2815) - Relax render pass color_attachments validation by @jinleili in [#2778](https://github.com/gfx-rs/wgpu/pull/2778) - Properly Barrier Compute Indirect Buffers by @cwfitzgerald in [#2810](https://github.com/gfx-rs/wgpu/pull/2810) - Use numeric constants to define `wgpu_types::Features` values. by @jimblandy in [#2817](https://github.com/gfx-rs/wgpu/pull/2817) #### Metal - Fix surface texture clear view by @kvark in [#2341](https://github.com/gfx-rs/wgpu/pull/2341) - Set preserveInvariance for shader options by @scoopr in [#2372](https://github.com/gfx-rs/wgpu/pull/2372) - Properly set msl version to 2.3 if supported by @cwfitzgerald in [#2418](https://github.com/gfx-rs/wgpu/pull/2418) - Identify Apple M1 GPU as integrated by @superdump in [#2429](https://github.com/gfx-rs/wgpu/pull/2429) - Fix M1 in macOS incorrectly reports supported compressed texture formats by @superdump in [#2453](https://github.com/gfx-rs/wgpu/pull/2453) - Msl: support unsized array not in structures by @kvark in [#2459](https://github.com/gfx-rs/wgpu/pull/2459) - Fix `Surface::from_uiview` can not guarantee set correct `contentScaleFactor` by @jinleili in [#2470](https://github.com/gfx-rs/wgpu/pull/2470) - Set `max_buffer_size` by the correct physical device restriction by @jinleili in [#2502](https://github.com/gfx-rs/wgpu/pull/2502) - Refactor `PrivateCapabilities` creation by @jinleili in [#2509](https://github.com/gfx-rs/wgpu/pull/2509) - Refactor texture_format_capabilities function by @jinleili in [#2522](https://github.com/gfx-rs/wgpu/pull/2522) - Improve `push | pop_debug_marker` by @jinleili in [#2537](https://github.com/gfx-rs/wgpu/pull/2537) - Fix some supported limits by @jinleili in [#2608](https://github.com/gfx-rs/wgpu/pull/2608) - Don't skip incomplete binding resources. by @dragostis in [#2622](https://github.com/gfx-rs/wgpu/pull/2622) - Fix `Rgb9e5Ufloat` capabilities and `sampler_lod_average` support by @jinleili in [#2656](https://github.com/gfx-rs/wgpu/pull/2656) - Fix Depth24Plus | Depth24PlusStencil8 capabilities by @jinleili in [#2686](https://github.com/gfx-rs/wgpu/pull/2686) - Get_supported_formats: sort like the old get_preferred_format and simplify return type by @victorvde in [#2786](https://github.com/gfx-rs/wgpu/pull/2786) - Restrict hal::TextureUses::COLOR_TARGET condition within create_texture by @jinleili in [#2818](https://github.com/gfx-rs/wgpu/pull/2818) #### DX12 - Fix UMA check by @kvark in [#2305](https://github.com/gfx-rs/wgpu/pull/2305) - Fix partial texture barrier not affecting stencil aspect by @Wumpf in [#2308](https://github.com/gfx-rs/wgpu/pull/2308) - Improve RowPitch computation by @kvark in [#2409](https://github.com/gfx-rs/wgpu/pull/2409) #### Vulkan - Explicitly set Vulkan debug message types instead of !empty() by @victorvde in [#2321](https://github.com/gfx-rs/wgpu/pull/2321) - Use stencil read/write masks by @kvark in [#2382](https://github.com/gfx-rs/wgpu/pull/2382) - Vulkan: correctly set INDEPENDENT_BLEND,make runnable on Android 8.x by @jinleili in [#2498](https://github.com/gfx-rs/wgpu/pull/2498) - Fix ASTC format mapping by @kvark in [#2476](https://github.com/gfx-rs/wgpu/pull/2476) - Support flipped Y on VK 1.1 devices by @cwfitzgerald in [#2512](https://github.com/gfx-rs/wgpu/pull/2512) - Fixed builtin(primitive_index) for vulkan backend by @kwillemsen in [#2716](https://github.com/gfx-rs/wgpu/pull/2716) - Fix PIPELINE_STATISTICS_QUERY feature support by @jinleili in [#2750](https://github.com/gfx-rs/wgpu/pull/2750) - Add a vulkan workaround for large buffers. by @nical in [#2796](https://github.com/gfx-rs/wgpu/pull/2796) #### GLES - Fix index buffer state not being reset in reset_state by @rparrett in [#2391](https://github.com/gfx-rs/wgpu/pull/2391) - Allow push constants trough emulation by @JCapucho in [#2400](https://github.com/gfx-rs/wgpu/pull/2400) - Hal/gles: fix dirty vertex buffers that are unused by @kvark in [#2427](https://github.com/gfx-rs/wgpu/pull/2427) - Fix texture description for bgra formats by @JCapucho in [#2520](https://github.com/gfx-rs/wgpu/pull/2520) - Remove a `log::error!` debugging statement from the gles queue by @expenses in [#2630](https://github.com/gfx-rs/wgpu/pull/2630) - Fix clearing depth and stencil at the same time by @expenses in [#2675](https://github.com/gfx-rs/wgpu/pull/2675) - Handle cubemap copies by @expenses in [#2725](https://github.com/gfx-rs/wgpu/pull/2725) - Allow clearing index buffers by @grovesNL in [#2740](https://github.com/gfx-rs/wgpu/pull/2740) - Fix buffer-texture copy for 2d arrays by @tuchs in [#2809](https://github.com/gfx-rs/wgpu/pull/2809) #### Wayland - Search for different versions of libwayland by @sh7dm in [#2336](https://github.com/gfx-rs/wgpu/pull/2336) #### WebGPU - Fix compilation on wasm32-unknown-unknown without `webgl` feature by @jakobhellermann in [#2355](https://github.com/gfx-rs/wgpu/pull/2355) - Solve crash on WebGPU by @cwfitzgerald in [#2807](https://github.com/gfx-rs/wgpu/pull/2807) #### Emscripten - Fix emscripten by @cwfitzgerald in [#2494](https://github.com/gfx-rs/wgpu/pull/2494) ### Performance - Do texture init via clear passes when possible by @Wumpf in [#2307](https://github.com/gfx-rs/wgpu/pull/2307) - Bind group deduplication by @cwfitzgerald in [#2623](https://github.com/gfx-rs/wgpu/pull/2623) - Tracking Optimization and Rewrite by @cwfitzgerald in [#2662](https://github.com/gfx-rs/wgpu/pull/2662) ### Documentation - Add defaults to new limits and correct older ones by @MultisampledNight in [#/2303](https://github.com/gfx-rs/wgpu/pull/2303) - Improve shader source documentation by @grovesNL in [#2315](https://github.com/gfx-rs/wgpu/pull/2315) - Fix typo by @rustui in [#2393](https://github.com/gfx-rs/wgpu/pull/2393) - Add a :star: to the feature matrix of examples README by @yutannihilation in [#2457](https://github.com/gfx-rs/wgpu/pull/2457) - Fix get_timestamp_period type in docs by @superdump in [#2478](https://github.com/gfx-rs/wgpu/pull/2478) - Fix mistake in Access doc comment by @nical in [#2479](https://github.com/gfx-rs/wgpu/pull/2479) - Improve shader support documentation by @cwfitzgerald in [#2501](https://github.com/gfx-rs/wgpu/pull/2501) - Document the gfx_select! macro. by @jimblandy in [#2555](https://github.com/gfx-rs/wgpu/pull/2555) - Add Windows 11 to section about DX12 by @HeavyRain266 in [#2552](https://github.com/gfx-rs/wgpu/pull/2552) - Document some aspects of resource tracking. by @jimblandy in [#2558](https://github.com/gfx-rs/wgpu/pull/2558) - Documentation for various things. by @jimblandy in [#2566](https://github.com/gfx-rs/wgpu/pull/2566) - Fix doc links. by @jimblandy in [#2579](https://github.com/gfx-rs/wgpu/pull/2579) - Fixed misspelling in documentation by @zenitopires in [#2634](https://github.com/gfx-rs/wgpu/pull/2634) - Update push constant docs to reflect the API by @Noxime in [#2637](https://github.com/gfx-rs/wgpu/pull/2637) - Exclude dependencies from documentation by @yutannihilation in [#2642](https://github.com/gfx-rs/wgpu/pull/2642) - Document `GpuFuture`. by @jimblandy in [#2644](https://github.com/gfx-rs/wgpu/pull/2644) - Document random bits and pieces. by @jimblandy in [#2651](https://github.com/gfx-rs/wgpu/pull/2651) - Add cross-references to each wgpu type's documentation. by @kpreid in [#2653](https://github.com/gfx-rs/wgpu/pull/2653) - RenderPassDescriptor: make label lifetime match doc, and make names descriptive. by @kpreid in [#2654](https://github.com/gfx-rs/wgpu/pull/2654) - Document `VertexStepMode`. by @jimblandy in [#2685](https://github.com/gfx-rs/wgpu/pull/2685) - Add links for SpirV documents. by @huandzh in [#2697](https://github.com/gfx-rs/wgpu/pull/2697) - Add symlink LICENSE files into crates. by @dskkato in [#2604](https://github.com/gfx-rs/wgpu/pull/2604) - Fix documentation links. by @jimblandy in [#2756](https://github.com/gfx-rs/wgpu/pull/2756) - Improve push constant documentation, including internal docs. by @jimblandy in [#2764](https://github.com/gfx-rs/wgpu/pull/2764) - Clarify docs for `wgpu_core`'s `Id` and `gfx_select!`. by @jimblandy in [#2766](https://github.com/gfx-rs/wgpu/pull/2766) - Update the Supported Platforms table in README by @jinleili in [#2770](https://github.com/gfx-rs/wgpu/pull/2770) - Remove depth image from readme - we don't dictate direction of depth by @cwfitzgerald in [#2812](https://github.com/gfx-rs/wgpu/pull/2812) ### Dependency Updates - Update `ash` to `0.37` by @a1phyr in [#2557](https://github.com/gfx-rs/wgpu/pull/2557) - Update parking_lot to 0.12. by @emilio in [#2639](https://github.com/gfx-rs/wgpu/pull/2639) - Accept both parking-lot 0.11 and 0.12, to avoid windows-rs. by @jimblandy in [#2660](https://github.com/gfx-rs/wgpu/pull/2660) - Update web-sys to 0.3.58, sparse attachments support by @jinleili in [#2813](https://github.com/gfx-rs/wgpu/pull/2813) - Remove use of inplace_it by @mockersf in [#2889](https://github.com/gfx-rs/wgpu/pull/2889) ### deno-webgpu - Clean up features in deno by @crowlKats in [#2445](https://github.com/gfx-rs/wgpu/pull/2445) - Dont panic when submitting same commandbuffer multiple times by @crowlKats in [#2449](https://github.com/gfx-rs/wgpu/pull/2449) - Handle error sources to display full errors by @crowlKats in [#2454](https://github.com/gfx-rs/wgpu/pull/2454) - Pull changes from deno repo by @crowlKats in [#2455](https://github.com/gfx-rs/wgpu/pull/2455) - Fix cts_runner by @crowlKats in [#2456](https://github.com/gfx-rs/wgpu/pull/2456) - Update deno_webgpu by @crowlKats in [#2539](https://github.com/gfx-rs/wgpu/pull/2539) - Custom op arity by @crowlKats in [#2542](https://github.com/gfx-rs/wgpu/pull/2542) ### Examples - Fix conserative-raster low res target getting zero sized on resize by @Wumpf in [#2318](https://github.com/gfx-rs/wgpu/pull/2318) - Replace run-wasm-example.sh with aliased rust crate (xtask) by @rukai in [#2346](https://github.com/gfx-rs/wgpu/pull/2346) - Get cargo-run-wasm from crates.io by @rukai in [#2415](https://github.com/gfx-rs/wgpu/pull/2415) - Fix msaa-line example's unnecessary MSAA data store by @jinleili in [#2421](https://github.com/gfx-rs/wgpu/pull/2421) - Make shadow example runnable on iOS Android devices by @jinleili in [#2433](https://github.com/gfx-rs/wgpu/pull/2433) - Blit should only draw one triangle by @CurryPseudo in [#2474](https://github.com/gfx-rs/wgpu/pull/2474) - Fix wasm examples failing to compile by @Liamolucko in [#2524](https://github.com/gfx-rs/wgpu/pull/2524) - Fix incorrect filtering used in mipmap generation by @LaylBongers in [#2525](https://github.com/gfx-rs/wgpu/pull/2525) - Correct program output ("Steps", not "Times") by @skierpage in [#2535](https://github.com/gfx-rs/wgpu/pull/2535) - Fix resizing behaviour of hello-triangle example by @FrankenApps in [#2543](https://github.com/gfx-rs/wgpu/pull/2543) - Switch from `cgmath` to `glam` in examples by @a1phyr in [#2544](https://github.com/gfx-rs/wgpu/pull/2544) - Generate 1x1 mip level by @davidar in [#2551](https://github.com/gfx-rs/wgpu/pull/2551) - Wgpu/examples/shadow: Don't run on llvmpipe. by @jimblandy in [#2595](https://github.com/gfx-rs/wgpu/pull/2595) - Avoid new WGSL reserved words in wgpu examples. by @jimblandy in [#2606](https://github.com/gfx-rs/wgpu/pull/2606) - Move texture-array example over to wgsl by @cwfitzgerald in [#2618](https://github.com/gfx-rs/wgpu/pull/2618) - Remove the default features from wgpu-info by @jinleili in [#2753](https://github.com/gfx-rs/wgpu/pull/2753) - Fix bunnymark test screenshot and replace rand with nanorand by @stevenhuyn in [#2746](https://github.com/gfx-rs/wgpu/pull/2746) - Use FIFO swapchain in examples by @cwfitzgerald in [#2790](https://github.com/gfx-rs/wgpu/pull/2790) ### Testing/Internal - Test WebGPU backend with extra features by @kvark in [#2362](https://github.com/gfx-rs/wgpu/pull/2362) - Lint deno_webgpu & wgpu-core by @AaronO in [#2403](https://github.com/gfx-rs/wgpu/pull/2403) - IdentityManager: `from_index` method is unneeded. by @jimblandy in [#2424](https://github.com/gfx-rs/wgpu/pull/2424) - Added id32 feature by @caiiiycuk in [#2464](https://github.com/gfx-rs/wgpu/pull/2464) - Update dev deps by @rukai in [#2493](https://github.com/gfx-rs/wgpu/pull/2493) - Use cargo nextest for running our tests by @cwfitzgerald in [#2495](https://github.com/gfx-rs/wgpu/pull/2495) - Many Steps Towards GL Testing Working by @cwfitzgerald in [#2504](https://github.com/gfx-rs/wgpu/pull/2504) - Rename ci.txt to ci.yml by @simon446 in [#2510](https://github.com/gfx-rs/wgpu/pull/2510) - Re-enable GL testing in CI by @cwfitzgerald in [#2508](https://github.com/gfx-rs/wgpu/pull/2508) - Expect shadow example to pass on GL by @kvark in [#2541](https://github.com/gfx-rs/wgpu/pull/2541) - Simplify implementation of RefCount and MultiRefCount. by @jimblandy in [#2548](https://github.com/gfx-rs/wgpu/pull/2548) - Provide a proper `new` method for `RefCount`. by @jimblandy in [#2570](https://github.com/gfx-rs/wgpu/pull/2570) - Add logging to LifetimeTracker::triage_suspected. by @jimblandy in [#2569](https://github.com/gfx-rs/wgpu/pull/2569) - wgpu-hal: Work around cbindgen bug: ignore `gles::egl` module. by @jimblandy in [#2576](https://github.com/gfx-rs/wgpu/pull/2576) - Specify an exact wasm-bindgen-cli version in publish.yml. by @jimblandy in [#2624](https://github.com/gfx-rs/wgpu/pull/2624) - Rename `timeout_us` to `timeout_ns`, to match actual units. by @jimblandy in [#2645](https://github.com/gfx-rs/wgpu/pull/2645) - Move set_index_buffer FFI functions back into wgpu. by @jimblandy in [#2661](https://github.com/gfx-rs/wgpu/pull/2661) - New function: `Global::create_buffer_error`. by @jimblandy in [#2673](https://github.com/gfx-rs/wgpu/pull/2673) - Actually use RenderBundleEncoder::set_bind_group in tests. by @jimblandy in [#2678](https://github.com/gfx-rs/wgpu/pull/2678) - Eliminate wgpu_core::commands::bundle::State::raw_dynamic_offsets. by @jimblandy in [#2684](https://github.com/gfx-rs/wgpu/pull/2684) - Move RenderBundleEncoder::finish's pipeline layout id into the state. by @jimblandy in [#2755](https://github.com/gfx-rs/wgpu/pull/2755) - Expect shader_primitive_index tests to fail on AMD RADV POLARIS12. by @jimblandy in [#2754](https://github.com/gfx-rs/wgpu/pull/2754) - Introduce `VertexStep`: a stride and a step mode. by @jimblandy in [#2768](https://github.com/gfx-rs/wgpu/pull/2768) - Increase max_outliers on wgpu water example reftest. by @jimblandy in [#2767](https://github.com/gfx-rs/wgpu/pull/2767) - wgpu_core::command::bundle: Consolidate pipeline and vertex state. by @jimblandy in [#2769](https://github.com/gfx-rs/wgpu/pull/2769) - Add type annotation to render pass code, for rust-analyzer. by @jimblandy in [#2773](https://github.com/gfx-rs/wgpu/pull/2773) - Expose naga span location helpers by @nical in [#2752](https://github.com/gfx-rs/wgpu/pull/2752) - Add create_texture_error by @nical in [#2800](https://github.com/gfx-rs/wgpu/pull/2800) ## wgpu-hal 0.12.5 (2022-04-19) - fix crashes when logging in debug message callbacks - fix program termination when dx12 or gles error messages happen. - implement validation canary - DX12: - Ignore erroneous validation error from DXGI debug layer. ## wgpu-hal-0.12.4 (2022-01-24) - Metal: - check for MSL-2.3 ## wgpu-hal-0.12.3, deno-webgpu-? (2022-01-20) - Metal: - preserve vertex invariance - Vulkan - fix stencil read/write masks - Gles: - reset index binding properly - DX12: - fix copies into 1D textures ## wgpu-core-0.12.2, wgpu-hal-0.12.2 (2022-01-10) - fix tracy compile error - fix buffer binding limits beyond 2Gb - fix zero initialization of 3D textures - Metal: - fix surface texture views - Gles: - extend `libwayland` search paths ## wgpu-core-0.12.1, wgpu-hal-0.12.1 (2021-12-29) - zero initialization uses now render target clears when possible (faster and doesn't enforce COPY_DST internally if not necessary) - fix use of MSAA targets in WebGL - fix not providing `COPY_DST` flag for textures causing assertions in some cases - fix surface textures not getting zero initialized - clear_texture supports now depth/stencil targets - error message on creating depth/stencil volume texture - Vulkan: - fix validation error on debug message types - DX12: - fix check for integrated GPUs - fix stencil subresource transitions - Metal: - implement push constants ## wgpu-0.12 (2021-12-18) - API: - `MULTIVIEW` feature - `DEPTH_CLIP_CONTROL` feature to replace the old `DEPTH_CLAMP` - `TEXTURE_FORMAT_16BIT_NORM` feature - push/pop error scopes on the device - more limits for compute shaders - `SamplerBindingType` instead of booleans - sampler arrays are supported by `TEXTURE_BINDING_ARRAY` feature - "glsl" cargo feature for accepting GLSL shader code - enforced MSRV-1.53 - correctness: - textures are zero-initialized - lots and lots of fixes - validation: - match texture-sampler pairs - check `min_binding_size` late at draw - check formats to match in `copy_texture_to_texture` - allow `strip_index_format` to be none if unused - check workgroup sizes and counts - shaders: - please refer to [naga-0.8 changelog](https://github.com/gfx-rs/naga/pull/1610/files) - nice error messages ### wgpu-core-0.11.3, wgpu-hal-0.11.5, wgpu-0.11.1 (2021-12-01) - Core: - validate device descriptor before actually creating it - fix validation of texture-sampler pairs - Vulkan: - fix running on Vulkan-1.1 instance - improve detection of workaround for Intel+Nvidia on Linux - fix resource limits on Vulkan-1.2 - fix the check for storage buffer requirement - change internal semaphore logic to work around Linux+Intel bugs - fix enabling extension-provided features - GLES: - fix running on old and bogus drivers - fix stale samplers on bindings change - fix integer textures - fix querying work group parameters - fix stale PBO bindings caused by resource copies - fix rendering to cubemap faces - fix `Rgba16Float` format - fix stale vertex attributes when changing the pipeline - Metal: - fix window resizing for running in multiple processes - Web: - fix `set_index_buffer` and `set_vertex_buffer` to have optional sizes ### wgpu-core-0.11.2, wgpu-hal-0.11.4 (2021-10-22) - fix buffer transition barriers - Metal: - disable RW buffers on macOS 10.11 - fix memory leaks in render pass descriptor - WebGL: - fix surface reconfiguration - GLES: - fix mapping when persistent mapping isn't supported - allow presentation in Android emulator - fix sRGB attributes on EGL-1.4 contexts ### wgpu-hal-0.11.3 (2021-10-16) - GL: - fix mapping flags and buffer initialization - fix context creation when sRGB is available ### wgpu-core-0.11.1 (2021-10-15) - fix bind group layout lifetime with regard to bind groups ### wgpu-hal-0.11.2 (2021-10-12) - GL/WebGL: fix vertex buffer bindings with non-zero first instance - DX12: fix cube array view construction ### wgpu-hal-0.11.1 (2021-10-09) - Vulkan: fix NV optimus detection on Linux - GL: - fix indirect dispatch buffers - WebGL: - fix querying storage-related limits - work around a browser bug in the clear shader ## wgpu-0.11 (2021-10-07) - Infrastructure: - Deno WebGPU plugin is a part of the repository - WebGPU CTS is ran on CI via Deno - API: - initial WebGL support - `SwapchainFrame` is removed. `SurfaceTexture::present()` needs to be called instead of dropping. - better SPIR-V control flow processing - ability to request a software (fallback) adapter - new limits for `min_uniform_buffer_offset_alignment` and `min_storage_buffer_offset_alignment` - features: - new `PARTIALLY_BOUND_BINDING_ARRAY` - `NON_FILL_POLYGON_MODE` is split into `POLYGON_MODE_LINE` and `POLYGON_MODE_POINT` - fixes: - many shader-related fixes in naga-0.7 - fix a panic in resource cleanup happening when they are dropped on another thread - Vulkan: - create SPIR-V per entry point to work around driver bugs - expose higher descriptor limits based on descriptor indexing capabilities - GL and Vulkan: - Fix renderdoc device pointers - optimization: - on Vulkan, bounds checks are omitted if the platform can do them natively ### wgpu-core-0.10.4, wgpu-0.10.2 (2021-09-23) - fix `write_texture` for array textures - fix closing an encoder on validation error - expose Metal surface creation - panic with an actual error message in the default handler ### wgpu-hal-0.10.7 (2021-09-14) - Metal: - fix stencil back-face state - fix the limit on command buffer count ### wgpu-hal-0.10.6 (2021-09-12) - Metal: - fix stencil operations - fix memory leak on M1 when out of focus - fix depth clamping checks - fix unsized storage buffers beyond the first ### wgpu-core-0.10.3, wgpu-hal-0.10.4 (2021-09-08) - Vulkan: - fix read access barriers for writable storage buffers - fix shaders using cube array textures - work around Linux Intel+Nvidia driver conflicts - work around Adreno bug with `OpName` - DX12: - fix storage binding offsets - Metal: - fix compressed texture copies ### wgpu-core-0.10.2, wgpu-hal-0.10.3 (2021-09-01) - All: - fix querying the size of storage textures - Vulkan: - use render pass labels - Metal: - fix moving the surface between displays - DX12: - enable BC compressed textures - GL: - fix vertex-buffer and storage related limits ### wgpu-core-0.10.1, wgpu-hal-0.10.2 (2021-08-24) - All: - expose more formats via adapter-specific feature - fix creation of depth+stencil views - validate cube textures to not be used as storage - fix mip level count check for storage textures - Metal: - fix usage of work group memory - DX12: - critical fix of pipeline layout ## v0.10 (2021-08-18) - Infrastructure: - `gfx-hal` is replaced by the in-house graphics abstraction `wgpu-hal`. Backends: Vulkan, Metal, D3D-12, and OpenGL ES-3. - examples are tested automatically for image snapshots. - API: - `cross` feature is removed entirely. Only Rust code from now on. - processing SPIR-V inputs for later translation now requires `spirv` compile feature enabled - new `Features::SPIRV_SHADER_PASSTHROUGH` run-time feature allows providing pass-through SPIR-V (orthogonal to the compile feature) - several bitflag names are renamed to plural: `TextureUsage`, `BufferUsage`, `ColorWrite`. - the `SwapChain` is merged into `Surface`. Returned frames are `Texture` instead of `TextureView`. - renamed `TextureUsage` bits: `SAMPLED` -> `TEXTURE_BINDING`, `STORAGE` -> `STORAGE_BINDING`. - renamed `InputStepMode` to `VertexStepMode`. - readable storage textures are no longer a part of the base API. Only exposed via format-specific features, non-portably. - implemented `Rgb9e5Ufloat` format. - added limits for binding sizes, vertex data, per-stage bindings, and others. - reworked downlevel flags, added downlevel limits. - `resolver = "2"` is now required in top-level cargo manifests - Fixed: - `Device::create_query_set` would return an error when creating exactly `QUERY_SET_MAX_QUERIES` (8192) queries. Now it only returns an error when trying to create _more_ than `QUERY_SET_MAX_QUERIES` queries. ### wgpu-core-0.9.2 - fix `Features::TEXTURE_SPECIFIC_FORMAT_FEATURES` not being supported for rendertargets ### wgpu-core-0.9.1 (2021-07-13) - fix buffer inits delayed by a frame - fix query resolves to initialize buffers - fix pipeline statistics stride - fix the check for maximum query count ## v0.9 (2021-06-18) - Updated: - naga to `v0.5`. - Added: - `Features::VERTEX_WRITABLE_STORAGE`. - `Features::CLEAR_COMMANDS` which allows you to use `cmd_buf.clear_texture` and `cmd_buf.clear_buffer`. - Changed: - Updated default storage buffer/image limit to `8` from `4`. - Fixed: - `Buffer::get_mapped_range` can now have a range of zero. - Fixed output spirv requiring the "kernel" capability. - Fixed segfault due to improper drop order. - Fixed incorrect dynamic stencil reference for Replace ops. - Fixed tracking of temporary resources. - Stopped unconditionally adding cubemap flags when the backend doesn't support cubemaps. - Validation: - Ensure that if resources are viewed from the vertex stage, they are read only unless `Features::VERTEX_WRITABLE_STORAGE` is true. - Ensure storage class (i.e. storage vs uniform) is consistent between the shader and the pipeline layout. - Error when a color texture is used as a depth/stencil texture. - Check that pipeline output formats are logical - Added shader label to log messages if validation fails. - Tracing: - Make renderpasses show up in the trace before they are run. - Docs: - Fix typo in `PowerPreference::LowPower` description. - Player: - Automatically start and stop RenderDoc captures. - Examples: - Handle winit's unconditional exception. - Internal: - Merged wgpu-rs and wgpu back into a single repository. - The tracker was split into two different stateful/stateless trackers to reduce overhead. - Added code coverage testing - CI can now test on lavapipe - Add missing extern "C" in wgpu-core on `wgpu_render_pass_execute_bundles` - Fix incorrect function name `wgpu_render_pass_bundle_indexed_indirect` to `wgpu_render_bundle_draw_indexed_indirect`. ### wgpu-types-0.8.1 (2021-06-08) - fix dynamic stencil reference for Replace ops ### v0.8.1 (2021-05-06) - fix SPIR-V generation from WGSL, which was broken due to "Kernel" capability - validate buffer storage classes - Added support for storage texture arrays for Vulkan and Metal. ## v0.8 (2021-04-29) - naga is used by default to translate shaders, SPIRV-Cross is optional behind `cross` feature - Features: - buffers are zero-initialized - downlevel limits for DX11/OpenGL support - conservative rasterization (native-only) - buffer resource indexing (native-only) - API adjustments to the spec: - Renamed `RenderPassColorAttachmentDescriptor` to `RenderPassColorAttachment`: - Renamed the `attachment` member to `view` - Renamed `RenderPassDepthStencilAttachmentDescriptor` to `RenderPassDepthStencilAttachment`: - Renamed the `attachment` member to `view` - Renamed `VertexFormat` values - Examples: `Float3` -> `Float32x3`, `Ushort2` -> `Uint16x2` - Renamed the `depth` value of `Extent3d` to `depth_or_array_layers` - Updated blending options in `ColorTargetState`: - Renamed `BlendState` to `BlendComponent` - Added `BlendState` struct to hold color and alpha blend state - Moved `color_blend` and `alpha_blend` members into `blend` member - Moved `clamp_depth` from `RastizerState` to `PrimitiveState` - Updated `PrimitiveState`: - Added `conservative` member for enabling conservative rasterization - Updated copy view structs: - Renamed `TextureCopyView` to `ImageCopyTexture` - Renamed `TextureDataLayout` to `ImageDataLayout` - Changed `bytes_per_row` and `rows_per_image` members of `ImageDataLayout` from `u32` to `Option` - Changed `BindingResource::Binding` from containing fields directly to containing a `BufferBinding` - Added `BindingResource::BufferArray` - Infrastructure: - switch from `tracing` to `profiling` - more concrete and detailed errors - API traces include the command that crashed/panicked - Vulkan Portability support is removed from Apple platforms - Validation: - texture bindings - filtering of textures by samplers - interpolation qualifiers - allow vertex components to be underspecified ### wgpu-core-0.7.1 (2021-02-25) - expose `wgc::device::queue` sub-module in public - fix the indexed buffer check - fix command allocator race condition ## v0.7 (2021-01-31) - Major API changes: - `RenderPipelineDescriptor` - `BindingType` - new `ShaderModuleDescriptor` - new `RenderEncoder` - Features: - (beta) WGSL support, including the ability to bypass SPIR-V entirely - (beta) implicit bind group layout support - better error messages - timestamp and pipeline statistics queries - ETC2 and ASTC compressed textures - (beta) targeting Wasm with WebGL backend - reduced dependencies - Native-only: - clamp-to-border addressing - polygon fill modes - query a format for extra capabilities - `f64` support in shaders - Validation: - shader interface - render pipeline descriptor - vertex buffers ### wgpu-0.6.2 (2020-11-24) - don't panic in the staging belt if the channel is dropped ## v0.6 (2020-08-17) - Crates: - C API is moved to [another repository](https://github.com/gfx-rs/wgpu-native) - `player`: standalone API replayer and tester - Features: - Proper error handling with all functions returning `Result` - Graceful handling of "error" objects - API tracing [infrastructure](http://kvark.github.io/wgpu/debug/test/ron/2020/07/18/wgpu-api-tracing.html) - uploading data with `write_buffer`/`write_texture` queue operations - reusable render bundles - read-only depth/stencil attachments - bind group layout deduplication - Cows, cows everywhere - Web+Native features: - Depth clamping (feature) - BC texture compression - Native-only features: - mappable primary buffers - texture array bindings - push constants - multi-draw indirect - Validation: - all transfer operations - all resource creation - bind group matching to the layout - experimental shader interface matching with naga ### wgpu-core-0.5.6 (2020-07-09) - add debug markers support ### wgpu-core-0.5.5 (2020-05-20) - fix destruction of adapters, swap chains, and bind group layouts - fix command pool leak with temporary threads - improve assertion messages - implement `From` for `TextureComponentType` ### wgpu-core-0.5.4 (2020-04-24) - fix memory management of staging buffers ### wgpu-core-0.5.3 (2020-04-18) - fix reading access to storage textures - another fix to layout transitions for swapchain images ### wgpu-core-0.5.2 (2020-04-15) - fix read-only storage flags - fix pipeline layout life time - improve various assert messages ### wgpu-core-0.5.1 (2020-04-10) - fix tracking of swapchain images that are used multiple times in a command buffer - fix tracking of initial usage of a resource across a command buffer ## v0.5 (2020-04-06) - Crates: - `wgpu-types`: common types between native and web targets - `wgpu-core`: internal API for the native and remote wrappers - Features: - based on gfx-hal-0.5 - moved from Rendy to the new `gfx-memory` and `gfx-descriptor` crates - passes are now recorded on the client side. The user is also responsible to keep all resources referenced in the pass up until it ends recording. - coordinate system is changed to have Y up in the rendering space - revised GPU lifetime tracking of all resources - revised usage tracking logic - all IDs are now non-zero - Mailbox present mode - Validation: - active pipeline - Fixes: - lots of small API changes to closely match upstream WebGPU - true read-only storage bindings - unmapping dropped buffers - better error messages on misused swapchain frames ### wgpu-core-0.4.3 (2020-01-20) - improved swap chain error handling ### wgpu-core-0.4.2 (2019-12-15) - fixed render pass transitions ### wgpu-core-0.4.1 (2019-11-28) - fixed depth/stencil transitions - fixed dynamic offset iteration ## v0.4 (2019-11-03) - Platforms: removed OpenGL/WebGL support temporarily - Features: - based on gfx-hal-0.4 with the new swapchain model - exposing adapters from all available backends on a system - tracking of samplers - cube map support with an example - Validation: - buffer and texture usage ### wgpu-core-0.3.3 (2019-08-22) - fixed instance creation on Windows ### wgpu-core-0.3.1 (2019-08-21) - fixed pipeline barriers that aren't transitions ## v0.3 (2019-08-21) - Platforms: experimental OpenGL/WebGL - Crates: - Rust API is moved out to [another repository](https://github.com/gfx-rs/wgpu-rs) - Features: - based on gfx-hal-0.3 with help of `rendy-memory` and `rendy-descriptor` - type-system-assisted deadlock prevention (for locking internal structures) - texture sub-resource tracking - `raw-window-handle` integration instead of `winit` - multisampling with an example - indirect draws and dispatches - stencil masks and reference values - native "compute" example - everything implements `Debug` - Validation - vertex/index/instance ranges at draw calls - bing groups vs their expected layouts - bind group buffer ranges - required stencil reference, blend color ### wgpu-core-0.2.6 (2019-04-04) - fixed frame acquisition GPU waits ### wgpu-core-0.2.5 (2019-03-31) - fixed submission tracking - added support for blend colors - fixed bind group compatibility at the gfx-hal level - validating the bind groups and blend colors ### wgpu-core-0.2.3 (2019-03-20) - fixed vertex format mapping - fixed building with "empty" backend on Windows - bumped the default descriptor pool size - fixed host mapping alignments - validating the uniform buffer offset ## v0.2 (2019-03-06) - Platforms: iOS/Metal, D3D11 - Crates: - `wgpu-remote`: remoting layer for the cross-process boundary - `gfx-examples`: selected gfx pre-ll examples ported over - Features: - native example for compute - "gfx-cube" and "gfx-shadow" examples - copies between buffers and textures - separate object identity for the remote client - texture view tracking - native swapchain resize support - buffer mapping - object index epochs - comprehensive list of vertex and texture formats - validation of pipeline compatibility with the pass - Fixes - fixed resource destruction ## v0.1 (2019-01-24) - Platforms: Linux/Vulkan, Windows/Vulkan, D3D12, macOS/Metal - Crates: - `wgpu-native`: C API implementation of WebGPU, based on gfx-hal - `wgpu-bindings`: auto-generated C headers - `wgpu`: idiomatic Rust wrapper - `examples`: native C examples - Features: - native examples for triangle rendering - basic native swapchain integration - concept of the storage hub - basic recording of passes and command buffers - submission-based lifetime tracking and command buffer recycling - automatic resource transitions ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct _This Code of Conduct is based on the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct), which is adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) and the [Contributor Covenant](https://www.contributor-covenant.org)._ ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards In this community we strive to go the extra step to look out for each other. Don’t just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they’re off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely. And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could’ve communicated better — remember that it’s your responsibility to make your fellow Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust. * Please avoid using overtly sexual aliases or other nicknames that might detract from a friendly, safe and welcoming environment for all. * Please be kind and courteous. There’s no need to be mean or rude. * Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. * Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. * We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term “harassment” as including the definition in the [Citizen Code of Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don’t tolerate behavior that excludes people in socially marginalized groups. * Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact the maintainers immediately. Whether you’re a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back. * Do not make casual mention of slavery or indentured servitude and/or false comparisons of one's occupation or situation to slavery. Please consider using or asking about alternate terminology when referring to such metaphors in technology. * Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome. ## Moderation These are the policies for upholding [our community’s standards of conduct](#our-standards). If you feel that a thread needs moderation, please contact the maintainers. 1. Remarks that violate the community standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner). 2. Remarks that maintainers find inappropriate, whether listed in the code of conduct or not, are also not allowed. 3. Maintainers will first respond to such remarks with a warning. 4. If the warning is unheeded, the user will be “kicked,” i.e., kicked out of the communication channel to cool off. 5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded. 6. Maintainers may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology. 7. If a maintainer bans someone and you think it was unjustified, please take it up with that maintainer, or with a different maintainer, in private. Complaints about bans in-channel are not allowed. 8. Maintainers are held to a higher standard than other community members. If a maintainer creates an inappropriate situation, they should expect less leeway than others. The enforcement policies in the code of conduct apply to all official venues, including Discord channels, GitHub repositories, the Twitter/Mastodon/Bluesky community and all other forums. ================================================ FILE: CONTRIBUTING.md ================================================ This document is a guide for contributions to the wgpu project. ## Welcome! First of all, welcome to the wgpu community! 👋 We're glad you want to contribute. If you are unfamiliar with the wgpu project, we recommend you read [`GOVERNANCE.md`] for an overview of its goals, and how it's governed. ## Table of Contents - [Documentation Overview](#documentation-overview) - [Talking to other humans in the wgpu project](#talking-to-other-humans-in-the-wgpu-project) - ["What can I work on?" as a new contributor](#what-can-i-work-on-as-a-new-contributor) - [Setting up a wgpu development environment](#setting-up-a-wgpu-development-environment) - [What to expect when you file an issue](#what-to-expect-when-you-file-an-issue) - [Pull requests](#pull-requests) - [Change Ownership](#change-ownership) - [LLMs (AI)](#llms-ai) - [Designing new features](#designing-new-features) - [Undue Burden](#undue-burden) - [Large pull requests are risky](#large-pull-requests-are-risky) ## Documentation Overview: - [`GOVERNANCE.md`]: An overview of the wgpu project's goals and governance. - [`CODE_OF_CONDUCT.md`]: The code of conduct for the wgpu project. - [`docs/release-checklist.md`]: Checklist for creating a new release of wgpu. - [`docs/review-checklist.md`]: Checklist for reviewing a pull request in wgpu. - [`docs/testing.md`]: Information on the test suites in wgpu and naga. [`GOVERNANCE.md`]: ./GOVERNANCE.md [`CODE_OF_CONDUCT.md`]: ./CODE_OF_CONDUCT.md [`docs/release-checklist.md`]: ./docs/release-checklist.md [`docs/review-checklist.md`]: ./docs/review-checklist.md [`docs/testing.md`]: ./docs/testing.md ## Talking to other humans in the wgpu project The wgpu project has multiple official platforms for community engagement: - The Matrix channel [`wgpu:matrix.org`](https://matrix.to/#/#wgpu:matrix.org) is dedicated to informal chat about contributions the project. It is particularly useful for: - Saying hello, and introducing yourself. - Validating contributions (i.e., determining if they'll be accepted, ensuring your approach is correct, making sure you aren't wasting effort, etc.). - Setting expectations for contributions. Notification in Matrix can sometimes be unreliable. Feel free to explicitly tag people from whom you would like attention, esp. to follow-up after a day or so if you do not get a response to your contributions. - The [#wgpu channel on the Rust Gamedev Discord](https://discord.gg/X3MYBNXUMJ) is dedicated to information chat about both contributing and using the project. Not all of the developers are on Discord, but this is monitored by the maintainers. Similar in place to the Matrix channels. - [GitHub issues] are used to discuss open development questions and track work the community intends to complete; this might include: - Work that needs resolution via pull requests (see below) - Bug reports - Feature requests - Creating new releases of crates - Recording project decisions formally. - Architectural discussion - ??? - Compiling sets of other issues needed for a specific feature or use case (AKA `[meta]` issues). - [GitHub pull requests]: Modifications to the contents of this repository are done through pull requests. - `wgpu` Maintainership Meetings: Every week, the maintainership of the wgpu project meets to discuss the project's direction and review ongoing work. These meetings are open to the public, and you are welcome to attend. They happen on Google Meet and happen on Wednesday at 11:00 US Eastern Standard Time and last approximately an hour. Remember to obey the [`CODE_OF_CONDUCT.md`] in the meeting. - [Meeting Notes] - [Meeting Link] - [GitHub discussions]: TODO: Experimentally used by some enthusiastic members of our community. Not supported officially. [GitHub discussions]: https://github.com/gfx-rs/wgpu/discussions [GitHub issues]: https://github.com/gfx-rs/wgpu/issues [GitHub pull requests]: https://github.com/gfx-rs/wgpu/pulls [Meeting Notes]: https://docs.google.com/document/d/1Z3qjy3m7eAYaTsh2n-iKxLV4Hjc6wZxgukzdQOgVH1c/edit?usp=sharing [Meeting Link]: https://meet.google.com/ubo-ztcw-gwf [`CODE_OF_CONDUCT.md`]: ./CODE_OF_CONDUCT.md ### "What can I work on?" as a new contributor TODO We discourage new contributors from submitting large changes or opinionated refactors unless they have been specifically validated by wgpu maintainership. These are likely to be rejected on basis of needing discussion before a formal review. ### Setting up a wgpu development environment We use the following components in a wgpu development environment: - [A Rust toolchain][install-rust] matching the version specified in [`rust-toolchain.toml`](./rust-toolchain.toml), to compile wgpu's code. If you use `rustup`, this will be automatically installed when you first run a `cargo` command in the repository. - [Taplo](https://taplo.tamasfe.dev/) to keep TOML files formatted. - [Vulkan SDK](https://vulkan.lunarg.com/) to provide Vulkan validation layers and other Vulkan/SPIR-V tools for testing. Once these are done, you should be ready to hack on wgpu! Drop into your favorite editor, make some changes to the repository's code, and test that wgpu has been changed the way you expect. Take a look at [`docs/testing.md`] for more info on testing. When testing your own code against your patch, we recommend [using a `path` dependency][path-deps] in Cargo for local testing of changes, and a [`git` dependency][git-deps] pointing to your own fork to share changes with other contributors. Once you are ready to request a review of your changes so they become part of wgpu public history, create a pull request with your changes committed to a branch in your own fork of wgpu in GitHub. See documentation for that [here](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork). [install-rust]: https://www.rust-lang.org/tools/install [path-deps]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-path-dependencies [git-deps]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-dependencies-from-git-repositories ### What to expect when you file an issue TODO - Describe the filing process - Link to new issue page - Describe how to socialize the issue effectively - Feel free to ping us if it's a blocker! - Suggesting tags is helpful. - Describe how the project will handle the issue - Our ability to respond to an issue depends entirely on whether it is _actionable_ (viz., that there is a course of action that is reasonable for a volunteer to take the time to do). If it's not actionable, we reserve the right to close it. - Being responsive to requests for further information is important. - Understanding what point in the repository's history an issue began is also important. Maybe link to `git bisect` or something similar? - In particular, expecting others to fix something hardware- or driver-specific that current maintainership (1) can't mentor you into fixing and (2) otherwise isn't being prioritized are likely to be closed. ### Pull requests You can see some common things that PR reviewers are going to look for in [`docs/review-checklist.md`]. A draft pull request is taken to be not yet ready for review and as such is not included in our weekly triage meetings. If you need a review before being taken out of draft, please let one of us know. The `Assigned` field on a pull request indicates who has taken responsibility for shepherding it through the review process, not who is responsible for authoring it. The assignee is usually the reviewer, but they can also delegate the review to someone else. The intent of assignment is simply to ensure that pull requests don't get neglected. #### Change Ownership PR authors must be able to understand, justify, and explain all proposed changes. After a PR is accepted, both the reviewer and author must understand it as a positive change to the codebase. #### LLMs (AI) Using LLMs and AIs to generate code that is part of a contribution is allowed. However, the author submitting the PR must fully adhere to [Change Ownership](#change-ownership) rules. The author is responsible for the code, regardless of how it was created. Do not use "LLM generated" as a justification for low quality code. #### Designing new features As an open source project, wgpu wants to serve a broad audience. This helps us cast a wide net for contributors, and widens the impact of their work. However, wgpu does not promise to incorporate every proposed feature. Large efforts that are ultimately rejected tend to burn contributors out on both sides of a review. To avoid this, we strongly encourage you to validate time-consuming contributions by engaging maintainership before you invest yourself too heavily. Try to build a consensus on the approach, including API changes, shader language extensions, implementation architecture, error handling, testing plans, benchmarking, and so on. #### Undue Burden We reserve the right to close any PRs that cause an undue burden on the maintainership. This could include, but is not limited to, [massive PRs](#large-pull-requests-are-risky), [LLM slop](#llms-ai), or contributions not in good faith. #### Large pull requests are risky Contributors should anticipate that the larger and more complex a pull request is, the less likely it is that reviewers will accept it, regardless of its merits. The wgpu project has had poor experiences with large, complex pull requests: - Complex pull requests are difficult to review effectively. It is common for us to debug a problem in wgpu and find that it was introduced by some massive pull request that we had reviewed and accepted, showing that we obviously hadn't understood it as well as we'd thought. - A large, complex pull request obviously represents a significant effort on the part of the author. At a personal level, it is quite stressful to question its design decisions, knowing that changing them will require the author to essentially reimplement the project from scratch. Such pull requests make it hard for maintainers to uphold their responsibility to keep wgpu maintainable. Incremental changes are easier to discuss and revise without drama. These problems are serious enough that maintainers may choose to reject large, complex pull requests, regardless of the value of the feature or the technical merit of the code. The problem isn't really the *size* of the pull request: a simple rename, with no changes to functionality, might touch hundreds of files, but be easy to review. Or, a change to naga might affect dozens of snapshot test output files, without being hard to understand. Rather, the problem is the *complexity* of the pull request: how many moving pieces does the reviewer need to assess at once? In our experience, almost every large change can be pared down by separating out: - Preparatory refactors that are at least harmless in isolation, and perhaps beneficial. - Helpers and utilities that can be used elsewhere in the code base, even if they don't show their full value until the whole thing is merged. - Renames and code motion with no semantic effect, like changes to types or behavior. When putting these in a separate pull request would be awkward, they should at least be segregated into their own commits within a pull request. Brevity for brevity's sake is not the goal. Rather, the goal is to help the reviewer anticipate the changes' consequences. When a pull request addresses only a single issue, even if it is textually large, a trustworthy review becomes more achievable. ================================================ FILE: Cargo.toml ================================================ [workspace] resolver = "2" members = [ "cts_runner", "deno_webgpu", # default members "benches", "examples/features", "examples/standalone/*", "examples/bug-repro/*", "lock-analyzer", "naga-cli", "naga-test", "naga", "naga/fuzz", "naga/hlsl-snapshots", "naga/xtask", "player", "tests", "wgpu-core", "wgpu-core/platform-deps/*", "wgpu-hal", "wgpu-info", "wgpu-macros", "wgpu-naga-bridge", "wgpu-types", "wgpu", "xtask", ] exclude = [] default-members = [ "benches", "examples/features", "examples/standalone/*", "examples/bug-repro/*", "lock-analyzer", "naga-cli", "naga-test", "naga", "naga/fuzz", "naga/hlsl-snapshots", "naga/xtask", "player", "tests", "wgpu-core", "wgpu-core/platform-deps/*", "wgpu-hal", "wgpu-info", "wgpu-macros", "wgpu-naga-bridge", "wgpu-types", "wgpu", "xtask", ] [workspace.lints.clippy] ref_as_ptr = "warn" # NOTE: clippy configuration values (disallowed-types and # large-error-threshold) are in other file: clippy.toml [workspace.package] edition = "2021" rust-version = "1.93" keywords = ["graphics"] license = "MIT OR Apache-2.0" homepage = "https://wgpu.rs/" repository = "https://github.com/gfx-rs/wgpu" version = "29.0.0" authors = ["gfx-rs developers"] [workspace.dependencies] naga = { version = "29.0.0", path = "./naga" } naga-test = { path = "./naga-test" } wgpu = { version = "29.0.0", path = "./wgpu", default-features = false, features = [ "std", "serde", "wgsl", "vulkan", "gles", "dx12", "metal", "vulkan-portability", "angle", "static-dxc", "noop", # This should be removed if we ever have non-test crates that depend on wgpu ] } wgpu-core = { version = "29.0.0", path = "./wgpu-core" } wgpu-hal = { version = "29.0.0", path = "./wgpu-hal" } wgpu-macros = { version = "29.0.0", path = "./wgpu-macros" } wgpu-naga-bridge = { version = "29.0.0", path = "./wgpu-naga-bridge" } wgpu-test = { version = "29.0.0", path = "./tests" } wgpu-types = { version = "29.0.0", path = "./wgpu-types", default-features = false } # These _cannot_ have a version specified. If it does, crates.io will look # for a version of the package on crates when we publish naga. Path dependencies # are allowed through though. hlsl-snapshots = { path = "naga/hlsl-snapshots" } wgpu-core-deps-windows-linux-android = { version = "29.0.0", path = "./wgpu-core/platform-deps/windows-linux-android" } wgpu-core-deps-apple = { version = "29.0.0", path = "./wgpu-core/platform-deps/apple" } wgpu-core-deps-wasm = { version = "29.0.0", path = "./wgpu-core/platform-deps/wasm" } wgpu-core-deps-emscripten = { version = "29.0.0", path = "./wgpu-core/platform-deps/emscripten" } anyhow = { version = "1.0.87", default-features = false } approx = "0.5" arbitrary = "1.4.2" argh = "0.1.13" arrayvec = { version = "0.7.1", default-features = false } bincode = "2" bit-set = { version = "0.9", default-features = false } bit-vec = { version = "0.9", default-features = false } bitflags = "2.9" bytemuck = { version = "1.22", features = [ "extern_crate_alloc", "min_const_generics", ] } cargo_metadata = "0.23" cfg_aliases = "0.2.1" cfg-if = "1" codespan-reporting = { version = "0.13", default-features = false } ctor = "0.6" diff = "0.1" document-features = "0.2.10" encase = "0.12" env_logger = { version = "0.11", default-features = false } fern = "0.7" flume = "0.12" futures-lite = "2" glam = "0.32.0" glob = "0.3" half = { version = "2.5", default-features = false } # We require 2.5 to have `Arbitrary` support. hashbrown = { version = "0.16", default-features = false, features = [ "default-hasher", "inline-more", ] } heck = "0.5" hexf-parse = "0.2" image = { version = "0.25", default-features = false, features = ["png"] } indexmap = { version = "2.11.4", default-features = false } indicatif = "0.18" itertools = { version = "0.14" } jobserver = "0.1" ktx2 = "0.4" libc = { version = "0.2.172", default-features = false } # See https://github.com/rust-fuzz/libfuzzer/issues/126 libfuzzer-sys = ">0.4.0,<=0.4.7" libloading = "0.8" libm = { version = "0.2.6", default-features = false } libtest-mimic = "0.8" log = "0.4.29" macro_rules_attribute = "0.2" nanoserde = "0.2" nanorand = { version = "0.8", default-features = false, features = ["wyrand"] } noise = "0.9" num_cpus = "1" # `half` requires 0.2.16 for `FromBytes` and `ToBytes`. num-traits = { version = "0.2.16", default-features = false } nv-flip = "0.1" obj = "0.10" # NOTE: once_cell/std is *required* for some commonly-used features, selecting this per crate once_cell = { version = "1.21", default-features = false } # Firefox has 3.4.0 vendored, so we allow that version in our dependencies ordered-float = { version = ">=3, <6.0", default-features = false } parking_lot = "0.12.3" petgraph = { version = "0.8", default-features = false } pico-args = { version = "0.5", features = [ "eq-separator", "short-space-opt", "combined-flags", ] } png = "0.18" pollster = "0.4" portable-atomic = "1.10" portable-atomic-util = "0.2.4" pp-rs = "0.2.1" profiling = { version = "1.0.1", default-features = false } quote = "1.0.38" raw-window-handle = { version = "0.6.2", default-features = false } rayon = "1.3" regex-lite = "0.1" renderdoc-sys = "1" rspirv = "0.13" ron = "0.12" # NOTE: rustc-hash v2 is a completely different hasher with different performance characteristics # see discussion here (including with some other alternatives): https://github.com/gfx-rs/wgpu/issues/6999 # (using default-features = false to support no-std build, avoiding any extra features that may require std::collections) rustc-hash = { version = "1.1", default-features = false } serde_json = "1.0.143" serde = { version = "1.0.225", default-features = false } shell-words = "1" smallvec = "1.14" spirv = "0.4" static_assertions = "1.1" strum = { version = "0.27.1", default-features = false, features = ["derive"] } syn = "2.0.98" tempfile = "3" toml = "1.0.0" trybuild = "1" tracy-client = "0.18" thiserror = { version = "2.0.12", default-features = false } unicode-ident = "1.0.5" walkdir = "2.3" winit = { version = "0.30.8", features = ["android-native-activity"] } which = "8" xshell = "0.2.2" # Metal dependencies block2 = "0.6.2" objc2 = "0.6.3" objc2-core-foundation = { version = "0.3.2", default-features = false, features = [ "std", "CFCGTypes", ] } objc2-foundation = { version = "0.3.2", default-features = false, features = [ "std", "NSError", "NSProcessInfo", "NSRange", "NSString", ] } objc2-metal = { version = "0.3.2", default-features = false, features = [ "std", "block2", "MTLAllocation", "MTLBlitCommandEncoder", "MTLBlitPass", "MTLBuffer", "MTLCaptureManager", "MTLCaptureScope", "MTLCommandBuffer", "MTLCommandEncoder", "MTLCommandQueue", "MTLComputeCommandEncoder", "MTLComputePass", "MTLComputePipeline", "MTLCounters", "MTLDepthStencil", "MTLDevice", "MTLDrawable", "MTLEvent", "MTLLibrary", "MTLPipeline", "MTLPixelFormat", "MTLRenderCommandEncoder", "MTLRenderPass", "MTLRenderPipeline", "MTLResource", "MTLSampler", "MTLStageInputOutputDescriptor", "MTLTexture", "MTLTypes", "MTLVertexDescriptor", "MTLAccelerationStructure", "MTLAccelerationStructureTypes", "MTLAccelerationStructureCommandEncoder", "MTLResidencySet", ] } objc2-quartz-core = { version = "0.3.2", default-features = false, features = [ "std", "objc2-core-foundation", "CALayer", "CAMetalLayer", "objc2-metal", ] } raw-window-metal = "1.0" # Vulkan dependencies android_system_properties = "0.1.1" ash = "0.38" gpu-descriptor = "0.3.2" # DX12 dependencies range-alloc = "0.1" mach-dxcompiler-rs = { version = "0.1.4", default-features = false } # remember to increase max_shader_model if applicable windows-core = { version = "0.62", default-features = false } # DX12 and Vulkan dependencies gpu-allocator = { version = "0.28", default-features = false, features = [ "hashbrown", ] } # Gles dependencies khronos-egl = "6" glow = "0.17" glutin = { version = "0.32", default-features = false } glutin-winit = { version = "0.5", default-features = false } glutin_wgl_sys = "0.6" # DX12 and GLES dependencies windows = { version = "0.62", default-features = false } # wasm32 dependencies console_error_panic_hook = "0.1.5" console_log = "1" js-sys = { version = "0.3.85", default-features = false } wasm-bindgen = { version = "0.2.108", default-features = false } wasm-bindgen-futures = { version = "0.4.58", default-features = false } wasm-bindgen-test = "0.3" web-sys = { version = "0.3.85", default-features = false } web-time = "1.1.0" # deno dependencies deno_console = "0.214.0" deno_core = "0.355.0" deno_features = "0.11.0" deno_url = "0.214.0" deno_web = "0.245.0" deno_webidl = "0.214.0" deno_webgpu = { version = "0.181.0", path = "./deno_webgpu" } deno_unsync = "0.4.4" deno_error = "0.7.0" tokio = "1.47" termcolor = "1.4.1" # android dependencies ndk-sys = "0.6" # These overrides allow our examples to explicitly depend on release crates [patch.crates-io] wgpu = { path = "./wgpu" } env_logger = { version = "0.11", git = "https://github.com/rust-cli/env_logger.git", rev = "d550741" } libtest-mimic = { version = "0.8", git = "https://github.com/cwfitzgerald/libtest-mimic.git", rev = "9979b3c" } [profile.release] lto = "thin" debug = true # Speed up image comparison even in debug builds [profile.dev.package."nv-flip-sys"] opt-level = 3 ================================================ FILE: GOVERNANCE.md ================================================ The **wgpu project** is a set of open-source libraries that _enables application authors to write portable and performant graphics programs_. It was originally conceived to provide an implementation of WebGPU for Firefox as the standard evolved, and settled into something that could be shipped on all web browsers. wgpu has also enjoyed much contribution and use from other projects that require graphics programming. We expect that these sorts of users will continue for the lifetime of project, and we embrace these contributors' needs and effort as the lifeblood of wgpu. ## Mission The wgpu community seeks to realize the following directives through the project: it… 1. …provides libraries for the WebGPU API that… 1. …are correct and fully conformant. 1. …are portable across all major platforms, that is, … 1. …`wgpu-core` enables JavaScript platforms to implement their own proper WebGPU API. 1. …`wgpu` provides a WebGPU-style API library for native applications, which allows shipping to all major platforms, including WebGPU's JavaScript API. 1. …are performant enough to enable demanding applications. 1. …serves as a platform of experimentation for: 1. …WebGPU standards development. 1. …native application authors that wish to experiment with features that are not (yet?) standard. ## Decision-making The wgpu community's decision-making is influenced by the following groups: * Community leadership: * Connor Fitzgerald (@cwfitzgerald) * Joshua Groves (@grovesNL) * Andreas Reich (@wumpf) * Firefox's WebGPU team (@jimblandy, @nical, @teoxoy, @ErichDonGubler, and others) * Deno's WebGPU contributors (@crowlKats) * Other users that ship applications based on wgpu It is no coincidence that these groups correspond to the historically most active and consistent contributors. In general, wgpu's community structure is meritocratic: social influence is granted proportionate to groups' contribution to and stake in wgpu's mission. These decision-making groups meet together regularly to discuss issues of importance to the community, with a focus on wgpu's [mission](#Mission). --- NOTE: The above is a snapshot of a perpetually changing state of affairs in the wgpu community. It is not a binding contract between users and decision-makers of the wgpu project. ================================================ FILE: LICENSE.APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: LICENSE.MIT ================================================ MIT License Copyright (c) 2025 The gfx-rs developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # wgpu [![Build Status](https://img.shields.io/github/actions/workflow/status/gfx-rs/wgpu/ci.yml?branch=trunk&logo=github&label=CI)](https://github.com/gfx-rs/wgpu/actions) [![codecov.io](https://img.shields.io/codecov/c/github/gfx-rs/wgpu?logo=codecov&logoColor=fff&label=codecov&token=84qJTesmeS)](https://codecov.io/gh/gfx-rs/wgpu) `wgpu` is a cross-platform, safe, pure-Rust graphics API. It runs natively on Vulkan, Metal, D3D12, and OpenGL; and on top of WebGL2 and WebGPU on wasm. The API is based on the [WebGPU standard][webgpu], but is a fully native Rust library. It serves as the core of the WebGPU integration in Firefox, Servo, and Deno. ## Getting Started See our examples online at . You can see the Rust sources at [examples](examples) and run them directly with `cargo run --bin wgpu-examples `. ### Learning `wgpu` If you are new to `wgpu` and graphics programming, we recommend starting with [Learn Wgpu]. Additionally, [WebGPU Fundamentals] is a tutorial for WebGPU which is very similar to our API, minus differences between Rust and Javascript. [Learn Wgpu]: https://sotrh.github.io/learn-wgpu/ [WebGPU Fundamentals]: https://webgpufundamentals.org/ ### Wiki We have a [wiki](https://github.com/gfx-rs/wgpu/wiki) which has information on useful architecture patterns, debugging tips, and more getting started information. ### Need Help? Want to Contribute? The wgpu community uses Matrix and Discord to discuss. - [![`#wgpu:matrix.org`](https://img.shields.io/static/v1?label=wgpu-devs&message=%23wgpu&color=blueviolet&logo=matrix)](https://matrix.to/#/#wgpu:matrix.org) - discussion of wgpu's development. - [![`#wgpu-users:matrix.org`](https://img.shields.io/static/v1?label=wgpu-users&message=%23wgpu-users&color=blueviolet&logo=matrix)](https://matrix.to/#/#wgpu-users:matrix.org) - discussion of using the library and the surrounding ecosystem. - [![#wgpu on the Rust Gamedev Discord](https://img.shields.io/discord/676678179678715904?logo=discord&logoColor=E0E3FF&label=%23wgpu&color=5865F2) ](https://discord.gg/X3MYBNXUMJ) - Dedicated support channel on the Rust Gamedev Discord. ### Other Languages To use wgpu in C or dozens of other languages, look at [wgpu-native](https://github.com/gfx-rs/wgpu-native). These are C bindings to wgpu and has an up-to-date list of libraries bringing support to other languages. [Learn WebGPU (for C++)] is a good resource for learning how to use wgpu-native from C++. [Learn WebGPU (for C++)]: https://eliemichel.github.io/LearnWebGPU/ [webgpu]: https://gpuweb.github.io/gpuweb/ ## Quick Links | Docs | Examples | Changelog | |:---------------------:|:-------------------------:|:-----------------------:| | [v29][rel-docs] | [v29][rel-examples] | [v29][rel-change] | | [`trunk`][trunk-docs] | [`trunk`][trunk-examples] | [`trunk`][trunk-change] | Contributors are welcome! See [CONTRIBUTING.md][contrib] for more information. [rel-docs]: https://docs.rs/wgpu/ [rel-examples]: https://github.com/gfx-rs/wgpu/tree/v29/examples#readme [rel-change]: https://github.com/gfx-rs/wgpu/releases [trunk-docs]: https://wgpu.rs/doc/wgpu/ [trunk-examples]: https://github.com/gfx-rs/wgpu/tree/trunk/examples#readme [trunk-change]: https://github.com/gfx-rs/wgpu/blob/trunk/CHANGELOG.md#unreleased [contrib]: CONTRIBUTING.md ## Supported Platforms | API | Windows | Linux/Android | macOS/iOS | Web (wasm) | | ------ | ------------------ | ------------------ | ------------------ | ------------------ | | Vulkan | ✅ | ✅ | 🌋 | | | Metal | | | ✅ | | | DX12 | ✅ | | | | | OpenGL | 🆗 (GL 3.3+) | 🆗 (GL ES 3.0+) | 📐 | 🆗 (WebGL2) | | WebGPU | | | | ✅ | ✅ = First Class Support 🆗 = Downlevel/Best Effort Support 📐 = Requires the [ANGLE](https://github.com/gfx-rs/wgpu/wiki/Running-on-ANGLE) translation layer (GL ES 3.0 only) 🌋 = Requires the [MoltenVK](https://vulkan.lunarg.com/sdk/home#mac) translation layer 🛠️ = Unsupported, though open to contributions ## Environment Variables Testing, examples, and `::from_env()` methods use a standardized set of environment variables to control wgpu's behavior. - `WGPU_BACKEND` with a comma-separated list of the backends you want to use (`vulkan`, `metal`, `dx12`, or `gl`). - `WGPU_ADAPTER_NAME` with a case-insensitive substring of the name of the adapter you want to use (ex. `1080` will match `NVIDIA GeForce 1080ti`). - `WGPU_DX12_COMPILER` with the DX12 shader compiler you wish to use (`dxc`, `static-dxc`, or `fxc`). Note that `dxc` requires `dxcompiler.dll` (min v1.8.2502) to be in the working directory, and `static-dxc` requires the `static-dxc` crate feature to be enabled. Otherwise, it will fall back to `fxc`. See the [documentation](https://docs.rs/wgpu/latest/wgpu/index.html?search=env) for more environment variables. When running the CTS, use the variables `DENO_WEBGPU_ADAPTER_NAME`, `DENO_WEBGPU_BACKEND`, `DENO_WEBGPU_POWER_PREFERENCE`, and `DENO_WEBGPU_DX12_COMPILER`. ## Repo Overview For an overview of all the components in the gfx-rs ecosystem, see [the big picture](./docs/big-picture.png). ## MSRV policy TL;DR: If you're using `wgpu`, our MSRV is **1.87**. If you're running our tests or examples, our MSRV is **1.93**. We will avoid bumping the MSRV of `wgpu` without good reason, and such a change is considered breaking.
Specific Details Due to complex dependants, we have three MSRV policies: - `wgpu`'s MSRV is **1.87** - `wgpu-core` (and hence `wgpu-hal`, `naga`, and `wgpu-types`)'s MSRV is **1.87**. - The rest of the workspace has an MSRV of **1.93**. It is enforced on CI (in "/.github/workflows/ci.yml") with the `WGPU_MSRV`, `CORE_MSRV`, and `REPO_MSRV` variables, respectively. This version can only be upgraded in breaking releases, though we release a breaking version every three months. The following rules apply: - The `wgpu-core` crate should never require an MSRV ahead of Firefox's MSRV for nightly builds, as determined by the value of `MINIMUM_RUST_VERSION` in [`python/mozboot/mozboot/util.py`][moz-msrv]. - The `wgpu` crate should never require an MSRV ahead of Servo's MSRV, as determined by the value of their rust-version declaration in [`Cargo.toml`][servo-msrv] - The repository MSRV should never require an MSRV higher than `stable - 3`. For example, if stable is at 1.97, the repository MSRV should be no higher than 1.94. This is to allow people who are using a decently-updated OS-provided rust to be able to build our repository. Consider cross checking with [NixOS][nixos-msrv], though this is not required. [moz-msrv]: https://searchfox.org/mozilla-central/source/python/mozboot/mozboot/util.py [servo-msrv]: https://github.com/servo/servo/blob/main/Cargo.toml#L23 [nixos-msrv]: https://search.nixos.org/packages?show=rustc
## Testing and Environment Variables [Information about testing](./docs/testing.md), including where tests of various kinds live, and how to run the tests. ## Tracking the WebGPU and WGSL draft specifications The `wgpu` crate is meant to be an idiomatic Rust translation of the [WebGPU API][webgpu spec]. That specification, along with its shading language, [WGSL][wgsl spec], are both still in the "Working Draft" phase, and while the general outlines are stable, details change frequently. Until the specification is stabilized, the `wgpu` crate and the version of WGSL it implements will likely differ from what is specified, as the implementation catches up. Exactly which WGSL features `wgpu` supports depends on how you are using it: - When running as native code, `wgpu` uses [Naga][naga] to translate WGSL code into the shading language of your platform's native GPU API. Naga is working on catching up to the WGSL specification, with [bugs][naga bugs] tracking various issues, but there is no concise summary of differences from the specification. - When running in a web browser (by compilation to WebAssembly) without the `"webgl"` feature enabled, `wgpu` relies on the browser's own WebGPU implementation. WGSL shaders are simply passed through to the browser, so that determines which WGSL features you can use. - When running in a web browser with `wgpu`'s `"webgl"` feature enabled, `wgpu` uses Naga to translate WGSL programs into GLSL. This uses the same version of Naga as if you were running `wgpu` as native code. [webgpu spec]: https://www.w3.org/TR/webgpu/ [wgsl spec]: https://gpuweb.github.io/gpuweb/wgsl/ [naga]: https://github.com/gfx-rs/wgpu/tree/trunk/naga/ [naga bugs]: https://github.com/gfx-rs/wgpu/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22naga%22 ================================================ FILE: SECURITY.md ================================================ # wgpu Security Policy This document describes what is considered a security vulnerability in wgpu and how vulnerabilities should be reported. ## Vulnerability Definition WebGPU introduces a different threat model than is sometimes applied to GPU-related software. Unlike typical gaming or high-performance computing applications, where the software accessing GPU APIs is proprietary or obtained from a trusted developer, WebGPU makes GPU APIs available to arbitrary web applications. In the threat model of the web, malicious content should not be able to use the GPU APIs to access data or interfaces outside the intended scope for interaction with web content. Therefore, `wgpu` seeks to prevent undefined behavior and data leaks even when its API is misused, and failures to do so may be considered vulnerabilities. (This is also in accordance with the Rust principle of safe vs. unsafe code, since the `wgpu` library exposes a safe API.) The wgpu maintainers have discretion in assigning a severity to individual vulnerabilities. It is generally considered a high-severity vulnerability in wgpu if JavaScript or WebAssembly code, running with privileges of ordinary web content in a browser that is using wgpu to provide the WebGPU API to that content, is able to: - Access data associated with native applications other than the user agent, or associated with other web origins. - Escape the applicable sandbox and run arbitrary code or call arbitrary system APIs on the user agent host. - Consume system resources to the point that it is difficult to recover (e.g. by closing the web page). The wgpu Rust API offers some functionality, both supported and experimental, that is not part of the WebGPU standard and is not made available in JavaScript environments using wgpu. Associated vulnerabilities may be assigned lower severity than vulnerabilities that apply to a wgpu-based WebGPU implementation exposed to JavaScript. ## Supported Versions The wgpu project maintains security support for serious vulnerabilities in the [most recent major release](https://github.com/gfx-rs/wgpu/releases). Fixes for security vulnerabilities found shortly after the initial release of a major version may also be provided for the previous major release. Mozilla provides security support for versions of wgpu used in [current versions of Firefox](https://whattrainisitnow.com/). The version of wgpu that is active can be found in the Firefox repositories: - [release](https://github.com/mozilla-firefox/firefox/blob/release/gfx/wgpu_bindings/Cargo.toml), - [beta](https://github.com/mozilla-firefox/firefox/blob/beta/gfx/wgpu_bindings/Cargo.toml), and - [nightly](https://github.com/mozilla-firefox/firefox/blob/main/gfx/wgpu_bindings/Cargo.toml), We welcome reports of security vulnerabilities in any of these released versions or in the latest code on the `trunk` branch. ## Reporting a Vulnerability Although not all vulnerabilities in wgpu will affect Firefox, Mozilla accepts all vulnerability reports for wgpu and directs them appropriately. Additionally, Mozilla serves as the CVE numbering authority for the wgpu project. To report a security problem with wgpu, create a bug in Mozilla's Bugzilla instance in the [Core :: Graphics :: WebGPU](https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Graphics%3A+WebGPU&groups=core-security&groups=gfx-core-security) component. **IMPORTANT: For security issues, please make sure that you check the box labelled "Many users could be harmed by this security problem".** We advise that you check this option for anything that is potentially security-relevant, including memory safety, crashes, race conditions, and handling of confidential information. Review Mozilla's [guides on bug reporting](https://bugzilla.mozilla.org/page.cgi?id=bug-writing.html) before you open a bug. Mozilla operates a [bug bounty program](https://www.mozilla.org/en-US/security/bug-bounty/). Some vulnerabilities in this project may be eligible. ================================================ FILE: benches/Cargo.toml ================================================ [package] name = "wgpu-benchmark" version.workspace = true authors.workspace = true edition.workspace = true description = "wgpu benchmarking suite" homepage.workspace = true repository.workspace = true keywords.workspace = true license.workspace = true rust-version.workspace = true publish = false [[bench]] name = "wgpu-benchmark" harness = false [features] tracy = ["dep:tracy-client"] [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(feature, values("tracy"))', ] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] anyhow.workspace = true bincode = { workspace = true, features = ["serde"] } bytemuck.workspace = true naga = { workspace = true, features = [ "deserialize", "serialize", "wgsl-in", "spv-in", "glsl-in", "spv-out", "msl-out", "hlsl-out", "glsl-out", "wgsl-out", ] } naga-test = { workspace = true, features = [] } nanorand.workspace = true pico-args.workspace = true pollster.workspace = true profiling.workspace = true rayon.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true termcolor.workspace = true tracy-client = { workspace = true, optional = true } wgpu.workspace = true ================================================ FILE: benches/README.md ================================================ Collection of CPU benchmarks for `wgpu`. These benchmarks are designed as a first line of defence against performance regressions and generally approximate the performance for users. ## Usage ```sh # Run all benchmarks cargo bench -p wgpu-benchmark # Run a specific benchmarks that contains "filter" in its name cargo bench -p wgpu-benchmark -- "filter" ``` Use `WGPU_BACKEND` and `WGPU_ADAPTER_NAME` to adjust which device the benchmarks use. [More info on env vars](../README.md#environment-variables). ## Comparing Against a Baseline To compare the current benchmarks against a baseline, you can use the `--save-baseline` and `--baseline` flags. For example, to compare v28 against trunk, you could run the following: ```sh git checkout v28 # Run the baseline benchmarks cargo bench -p wgpu-benchmark -- --save-baseline "v28" git checkout trunk # Run the current benchmarks cargo bench -p wgpu-benchmark -- --baseline "v28" ``` The current benchmarking framework was added before v28, so comparisons only work after it was added. Before that the same commands will work, but comparison will be done using `criterion`. ## Integration with Profilers The benchmarks can be run with a profiler to get more detailed information about where time is being spent. Integrations are available for `tracy` and `superluminal`. #### Tracy Tracy is available prebuilt for Windows on [github](https://github.com/wolfpld/tracy/releases/latest/). ```sh # Once this is running, you can connect to it with the Tracy Profiler cargo bench -p wgpu-benchmark --features tracy,profiling/profile-with-tracy ``` #### Superluminal Superluminal is a paid product for windows available [here](https://superluminal.eu/). ```sh # This command will build the benchmarks, and display the path to the executable cargo bench -p wgpu-benchmark --features profiling/profile-with-superluminal -- -h # Have Superluminal run the following command (replacing with the path to the executable) --bench "filter" ``` #### `perf` and others You can follow the same pattern as above to run the benchmarks with other profilers. For example, the command line tool `perf` can be used to profile the benchmarks. ```sh # This command will build the benchmarks, and display the path to the executable cargo bench -p wgpu-benchmark -- -h # Run the benchmarks with perf perf record --bench "filter" ``` ## Benchmarks #### `Renderpass Encoding` This benchmark measures the performance of recording and submitting a render pass with a large number of draw calls and resources, emulating an intense, more traditional graphics application. By default it measures 10k draw calls, with 90k total resources. Within this benchmark, both single threaded and multi-threaded recording are tested, as well as splitting the render pass into multiple passes over multiple command buffers. If available, it also tests a bindless approach, binding all textures at once instead of switching the bind group for every draw call. #### `Computepass Encoding` This benchmark measures the performance of recording and submitting a compute pass with a large number of dispatches and resources. By default it measures 10k dispatch calls, with 60k total resources, emulating an unusually complex and sequential compute workload. Within this benchmark, both single threaded and multi-threaded recording are tested, as well as splitting the compute pass into multiple passes over multiple command buffers. If available, it also tests a bindless approach, binding all resources at once instead of switching the bind group for every draw call. TODO(https://github.com/gfx-rs/wgpu/issues/5766): The bindless version uses only 1k dispatches with 6k resources since it would be too slow for a reasonable benchmarking time otherwise. #### `Device::create_buffer` This benchmark measures the performance of creating large buffers. #### `Device::create_bind_group` This benchmark measures the performance of creating large bind groups of 5 to 50,000 resources. #### `naga::back`, `naga::compact`, `naga::front`, and `naga::valid` These benchmark measures the performance of naga parsing, validating, and generating shaders. ================================================ FILE: benches/benches/wgpu-benchmark/bind_groups.rs ================================================ use std::{num::NonZeroU32, time::Instant}; use nanorand::{Rng, WyRand}; use wgpu_benchmark::{iter, BenchmarkContext, SubBenchResult}; use crate::DeviceState; struct Params { max_texture_count: u32, texture_counts: &'static [u32], } // Creating 50_000 textures takes a considerable amount of time with syncval enabled. // // We greatly reduce the number of textures for the test case to keep the runtime // reasonable for testing. const BENCHMARK_PARAMS: Params = Params { max_texture_count: 50_000, texture_counts: &[5, 50, 500, 5_000, 50_000], }; const TEST_PARAMS: Params = Params { max_texture_count: 5, texture_counts: &[5], }; pub fn run_bench(ctx: BenchmarkContext) -> anyhow::Result> { let device_state = DeviceState::new(); if !device_state .device .features() .contains(wgpu::Features::TEXTURE_BINDING_ARRAY) { anyhow::bail!("Device does not support required feature TEXTURE_BINDING_ARRAY"); } let params = if ctx.is_test() { TEST_PARAMS } else { BENCHMARK_PARAMS }; // Performance gets considerably worse if the resources are shuffled. // // This more closely matches the real-world use case where resources have no // well defined usage order. let mut random = WyRand::new_seed(0x8BADF00D); let mut texture_views = Vec::with_capacity(params.max_texture_count as usize); for i in 0..params.max_texture_count { let texture = device_state .device .create_texture(&wgpu::TextureDescriptor { label: Some(&format!("Texture {i}")), size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); texture_views.push(texture.create_view(&wgpu::TextureViewDescriptor { label: Some(&format!("Texture View {i}")), ..Default::default() })); } random.shuffle(&mut texture_views); let mut results = Vec::new(); for &count in params.texture_counts { let bind_group_layout = device_state .device .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: Some(NonZeroU32::new(count).unwrap()), }], }); let texture_view_refs: Vec<_> = texture_views.iter().take(count as usize).collect(); let name = format!("{count} Textures"); let res = iter(&ctx, &name, "bindings", count, || { let start = Instant::now(); let bind_group = device_state .device .create_bind_group(&wgpu::BindGroupDescriptor { layout: &bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureViewArray(&texture_view_refs), }], label: None, }); let time = start.elapsed(); drop(bind_group); device_state .device .poll(wgpu::PollType::wait_indefinitely()) .unwrap(); time }); results.push(res); } Ok(results) } ================================================ FILE: benches/benches/wgpu-benchmark/computepass-bindless.wgsl ================================================ @group(0) @binding(0) var tex: binding_array>; @group(0) @binding(1) // TODO(https://github.com/gfx-rs/wgpu/issues/5765): The extra whitespace between the angle brackets is needed to workaround a parsing bug. var images: binding_array >; struct BufferElement { element: vec4f, } @group(0) @binding(2) var buffers: binding_array; @compute @workgroup_size(16) fn cs_main(@builtin(global_invocation_id) global_invocation_id: vec3) { let offset = global_invocation_id.x; // Would be nice to offset this dynamically (it's just 0 always in the current setup) let idx0 = offset * 2 + 0; let idx1 = offset * 2 + 1; let tex = textureLoad(tex[idx0], vec2u(0), 0) + textureLoad(tex[idx0], vec2u(0), 0); let image = textureLoad(images[idx0], vec2u(0)) + textureLoad(images[idx1], vec2u(0)); buffers[idx0].element = tex.rrrr; buffers[idx1].element = image.rrrr; } ================================================ FILE: benches/benches/wgpu-benchmark/computepass.rs ================================================ use std::{ num::{NonZeroU32, NonZeroU64}, time::{Duration, Instant}, }; use nanorand::{Rng, WyRand}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use wgpu_benchmark::{iter_auto, iter_many, BenchmarkContext, LoopControl}; use crate::DeviceState; fn dispatch_count(ctx: &BenchmarkContext) -> usize { // When testing we only want to run a very lightweight version of the benchmark // to ensure that it does not break. if ctx.is_test() { 8 } else { 10_000 } } // Currently bindless is _much_ slower than with regularly resources, // since wgpu needs to issues barriers for all resources between each dispatch for all read/write textures & buffers. // This is in fact so slow that it makes the benchmark unusable when we use the same amount of // resources as the regular benchmark. // For details see https://github.com/gfx-rs/wgpu/issues/5766 fn dispatch_count_bindless(ctx: &BenchmarkContext) -> usize { // On CI we only want to run a very lightweight version of the benchmark // to ensure that it does not break. if ctx.is_test() { 8 } else { 1_000 } } fn thread_count_list(ctx: &BenchmarkContext) -> &'static [usize] { if ctx.is_test() { &[2] } else { &[2, 4, 8] } } // Must match the number of textures in the computepass.wgsl shader const TEXTURES_PER_DISPATCH: usize = 2; const STORAGE_TEXTURES_PER_DISPATCH: usize = 2; const STORAGE_BUFFERS_PER_DISPATCH: usize = 2; const BUFFER_SIZE: u64 = 16; struct ComputepassState { device_state: DeviceState, pipeline: wgpu::ComputePipeline, bind_groups: Vec, // Bindless resources bindless_bind_group: Option, bindless_pipeline: Option, } impl ComputepassState { /// Create and prepare all the resources needed for the computepass benchmark. fn new(ctx: &BenchmarkContext) -> Self { let device_state = DeviceState::new(); let dispatch_count = dispatch_count(ctx); let dispatch_count_bindless = dispatch_count_bindless(ctx); let texture_count = dispatch_count * TEXTURES_PER_DISPATCH; let storage_buffer_count = dispatch_count * STORAGE_BUFFERS_PER_DISPATCH; let storage_texture_count = dispatch_count * STORAGE_TEXTURES_PER_DISPATCH; let supports_bindless = device_state.device.features().contains( wgpu::Features::BUFFER_BINDING_ARRAY | wgpu::Features::TEXTURE_BINDING_ARRAY | wgpu::Features::STORAGE_RESOURCE_BINDING_ARRAY | wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, ) // TODO: as of writing llvmpipe segfaults the bindless benchmark on ci && device_state.adapter_info.driver != "llvmpipe"; // Performance gets considerably worse if the resources are shuffled. // // This more closely matches the real-world use case where resources have no // well defined usage order. let mut random = WyRand::new_seed(0x8BADF00D); let mut bind_group_layout_entries = Vec::with_capacity(TEXTURES_PER_DISPATCH); for i in 0..TEXTURES_PER_DISPATCH { bind_group_layout_entries.push(wgpu::BindGroupLayoutEntry { binding: i as u32, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }); } for i in 0..STORAGE_TEXTURES_PER_DISPATCH { bind_group_layout_entries.push(wgpu::BindGroupLayoutEntry { binding: (TEXTURES_PER_DISPATCH + i) as u32, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::StorageTexture { access: wgpu::StorageTextureAccess::ReadWrite, format: wgpu::TextureFormat::R32Float, view_dimension: wgpu::TextureViewDimension::D2, }, count: None, }); } for i in 0..STORAGE_BUFFERS_PER_DISPATCH { bind_group_layout_entries.push(wgpu::BindGroupLayoutEntry { binding: (TEXTURES_PER_DISPATCH + STORAGE_BUFFERS_PER_DISPATCH + i) as u32, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: NonZeroU64::new(BUFFER_SIZE), }, count: None, }); } let bind_group_layout = device_state .device .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &bind_group_layout_entries, }); let mut texture_views = Vec::with_capacity(texture_count); for i in 0..texture_count { let texture = device_state .device .create_texture(&wgpu::TextureDescriptor { label: Some(&format!("Texture {i}")), size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); texture_views.push(texture.create_view(&wgpu::TextureViewDescriptor { label: Some(&format!("Texture View {i}")), ..Default::default() })); } random.shuffle(&mut texture_views); let texture_view_refs: Vec<_> = texture_views.iter().collect(); let mut storage_texture_views = Vec::with_capacity(storage_texture_count); for i in 0..storage_texture_count { let texture = device_state .device .create_texture(&wgpu::TextureDescriptor { label: Some(&format!("StorageTexture {i}")), size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R32Float, usage: wgpu::TextureUsages::STORAGE_BINDING, view_formats: &[], }); storage_texture_views.push(texture.create_view(&wgpu::TextureViewDescriptor { label: Some(&format!("StorageTexture View {i}")), ..Default::default() })); } random.shuffle(&mut storage_texture_views); let storage_texture_view_refs: Vec<_> = storage_texture_views.iter().collect(); let mut storage_buffers = Vec::with_capacity(storage_buffer_count); for i in 0..storage_buffer_count { storage_buffers.push(device_state.device.create_buffer(&wgpu::BufferDescriptor { label: Some(&format!("Buffer {i}")), size: BUFFER_SIZE, usage: wgpu::BufferUsages::STORAGE, mapped_at_creation: false, })); } random.shuffle(&mut storage_buffers); let storage_buffer_bindings: Vec<_> = storage_buffers .iter() .map(|b| b.as_entire_buffer_binding()) .collect(); let mut bind_groups = Vec::with_capacity(dispatch_count); for dispatch_idx in 0..dispatch_count { let mut entries = Vec::with_capacity(TEXTURES_PER_DISPATCH); for tex_idx in 0..TEXTURES_PER_DISPATCH { entries.push(wgpu::BindGroupEntry { binding: tex_idx as u32, resource: wgpu::BindingResource::TextureView( &texture_views[dispatch_idx * TEXTURES_PER_DISPATCH + tex_idx], ), }); } for tex_idx in 0..STORAGE_TEXTURES_PER_DISPATCH { entries.push(wgpu::BindGroupEntry { binding: (TEXTURES_PER_DISPATCH + tex_idx) as u32, resource: wgpu::BindingResource::TextureView( &storage_texture_views [dispatch_idx * STORAGE_TEXTURES_PER_DISPATCH + tex_idx], ), }); } for buffer_idx in 0..STORAGE_BUFFERS_PER_DISPATCH { entries.push(wgpu::BindGroupEntry { binding: (TEXTURES_PER_DISPATCH + STORAGE_BUFFERS_PER_DISPATCH + buffer_idx) as u32, resource: wgpu::BindingResource::Buffer( storage_buffers[dispatch_idx * STORAGE_BUFFERS_PER_DISPATCH + buffer_idx] .as_entire_buffer_binding(), ), }); } bind_groups.push( device_state .device .create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &entries, }), ); } random.shuffle(&mut bind_groups); let sm = device_state .device .create_shader_module(wgpu::include_wgsl!("computepass.wgsl")); let pipeline_layout = device_state .device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); let pipeline = device_state .device .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some("Compute Pipeline"), layout: Some(&pipeline_layout), module: &sm, entry_point: Some("cs_main"), compilation_options: wgpu::PipelineCompilationOptions::default(), cache: None, }); let (bindless_bind_group, bindless_pipeline) = if supports_bindless { let bindless_bind_group_layout = device_state .device .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true, }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: Some(NonZeroU32::new(texture_count as u32).unwrap()), }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::StorageTexture { access: wgpu::StorageTextureAccess::ReadWrite, format: wgpu::TextureFormat::R32Float, view_dimension: wgpu::TextureViewDimension::D2, }, count: Some(NonZeroU32::new(storage_texture_count as u32).unwrap()), }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: std::num::NonZeroU64::new(BUFFER_SIZE), }, count: Some(NonZeroU32::new(storage_buffer_count as u32).unwrap()), }, ], }); let bindless_bind_group = device_state .device .create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bindless_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureViewArray( &texture_view_refs[..dispatch_count_bindless], ), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureViewArray( &storage_texture_view_refs[..dispatch_count_bindless], ), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::BufferArray( &storage_buffer_bindings[..dispatch_count_bindless], ), }, ], }); let bindless_sm = device_state .device .create_shader_module(wgpu::include_wgsl!("computepass-bindless.wgsl")); let bindless_pipeline_layout = device_state .device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bindless_bind_group_layout)], immediate_size: 0, }); let bindless_pipeline = device_state .device .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some("Compute Pipeline bindless"), layout: Some(&bindless_pipeline_layout), module: &bindless_sm, entry_point: Some("cs_main"), compilation_options: wgpu::PipelineCompilationOptions::default(), cache: None, }); (Some(bindless_bind_group), Some(bindless_pipeline)) } else { (None, None) }; Self { device_state, pipeline, bind_groups, bindless_bind_group, bindless_pipeline, } } fn run_subpass( &self, ctx: &BenchmarkContext, pass_number: usize, total_passes: usize, ) -> wgpu::CommandBuffer { profiling::scope!("Computepass", &format!("Pass {pass_number}/{total_passes}")); let dispatch_count = dispatch_count(ctx); let dispatch_per_pass = dispatch_count / total_passes; let mut encoder = self .device_state .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); let start_idx = pass_number * dispatch_per_pass; let end_idx = start_idx + dispatch_per_pass; for dispatch_idx in start_idx..end_idx { compute_pass.set_pipeline(&self.pipeline); compute_pass.set_bind_group(0, &self.bind_groups[dispatch_idx], &[]); compute_pass.dispatch_workgroups(1, 1, 1); } drop(compute_pass); encoder.finish() } fn run_bindless_pass(&self, dispatch_count_bindless: usize) -> wgpu::CommandBuffer { profiling::scope!("Bindless Computepass"); let mut encoder = self .device_state .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); compute_pass.set_pipeline(self.bindless_pipeline.as_ref().unwrap()); compute_pass.set_bind_group(0, Some(self.bindless_bind_group.as_ref().unwrap()), &[]); for _ in 0..dispatch_count_bindless { compute_pass.dispatch_workgroups(1, 1, 1); } drop(compute_pass); encoder.finish() } } pub fn run_bench(mut ctx: BenchmarkContext) -> anyhow::Result> { let state = ComputepassState::new(&ctx); ctx.default_iterations = LoopControl::Time(Duration::from_secs(3)); // This benchmark hangs on Apple Paravirtualized GPUs. No idea why. if state.device_state.adapter_info.name.contains("Paravirtual") { anyhow::bail!("Benchmark unsupported on Paravirtualized GPUs"); } let dispatch_count = dispatch_count(&ctx); let dispatch_count_bindless = dispatch_count_bindless(&ctx); let mut results = Vec::new(); // Test 10k dispatch calls split up into 1, 2, 4, and 8 computepasses for &cpasses in thread_count_list(&ctx) { let labels = vec![ format!("Encoding ({cpasses} passes)"), format!("Submit ({cpasses} passes)"), ]; results.extend(iter_many( &ctx, labels, "dispatches", dispatch_count as _, || { let mut buffers: Vec = Vec::with_capacity(cpasses); let encoding_start = Instant::now(); for i in 0..cpasses { buffers.push(state.run_subpass(&ctx, i, cpasses)); } let encoding_duration = encoding_start.elapsed(); let submit_start = Instant::now(); state.device_state.queue.submit(buffers); let submit_duration = submit_start.elapsed(); state .device_state .device .poll(wgpu::PollType::wait_indefinitely()) .unwrap(); vec![encoding_duration, submit_duration] }, )); } // Test 10k dispatch calls split up over 2, 4, and 8 threads. for &threads in thread_count_list(&ctx) { let labels = vec![ format!("Encoding ({threads} threads)"), format!("Submit ({threads} threads)"), ]; results.extend(iter_many( &ctx, labels, "dispatches", dispatch_count as _, || { let encoding_start = Instant::now(); let buffers = (0..threads) .into_par_iter() .map(|i| state.run_subpass(&ctx, i, threads)) .collect::>(); let encoding_duration = encoding_start.elapsed(); let submit_start = Instant::now(); state.device_state.queue.submit(buffers); let submit_duration = submit_start.elapsed(); state .device_state .device .poll(wgpu::PollType::wait_indefinitely()) .unwrap(); vec![encoding_duration, submit_duration] }, )); } // Test 10k dispatch calls with bindless rendering. if state.bindless_bind_group.is_some() { let labels = vec![ "Encoding (bindless)".to_string(), "Submit (bindless)".to_string(), ]; results.extend(iter_many( &ctx, labels, "dispatches", dispatch_count_bindless as _, || { let encoding_start = Instant::now(); let buffer = state.run_bindless_pass(dispatch_count_bindless); let encoding_duration = encoding_start.elapsed(); let submit_start = Instant::now(); state.device_state.queue.submit([buffer]); let submit_duration = submit_start.elapsed(); state .device_state .device .poll(wgpu::PollType::wait_indefinitely()) .unwrap(); vec![encoding_duration, submit_duration] }, )); } // Test empty submit overhead with all resources let texture_count = dispatch_count * TEXTURES_PER_DISPATCH; let storage_buffer_count = dispatch_count * STORAGE_BUFFERS_PER_DISPATCH; let storage_texture_count = dispatch_count * STORAGE_TEXTURES_PER_DISPATCH; results.push(iter_auto( &ctx, &format!( "Empty Submit with {} Resources", texture_count + storage_texture_count + storage_buffer_count ), "submits", 1, || { state.device_state.queue.submit([]); }, )); Ok(results) } ================================================ FILE: benches/benches/wgpu-benchmark/computepass.wgsl ================================================ @group(0) @binding(0) var tex_0: texture_2d; @group(0) @binding(1) var tex_1: texture_2d; @group(0) @binding(2) var image_0: texture_storage_2d; @group(0) @binding(3) var image_1: texture_storage_2d; @group(0) @binding(4) var buffer0 : array; @group(0) @binding(5) var buffer1 : array; @compute @workgroup_size(16) fn cs_main(@builtin(global_invocation_id) global_invocation_id: vec3) { let tex = textureLoad(tex_0, vec2u(0), 0) + textureLoad(tex_1, vec2u(0), 0); let image = textureLoad(image_0, vec2u(0)) + textureLoad(image_1, vec2u(0)); buffer0[0] = tex.rrrr; buffer1[0] = image.rrrr; } ================================================ FILE: benches/benches/wgpu-benchmark/main.rs ================================================ #![cfg_attr(target_arch = "wasm32", no_main)] #![cfg(not(target_arch = "wasm32"))] use pollster::block_on; use wgpu_benchmark::Benchmark; mod bind_groups; mod computepass; mod renderpass; mod resource_creation; mod shader; struct DeviceState { adapter_info: wgpu::AdapterInfo, device: wgpu::Device, queue: wgpu::Queue, } impl DeviceState { fn new() -> Self { #[cfg(feature = "tracy")] tracy_client::Client::start(); let base_backend = if cfg!(target_os = "macos") { // We don't want to use Molten-VK on Mac. wgpu::Backends::METAL } else { wgpu::Backends::all() }; let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: wgpu::Backends::from_env().unwrap_or(base_backend), ..wgpu::InstanceDescriptor::new_without_display_handle_from_env() }); let adapter = block_on(wgpu::util::initialize_adapter_from_env_or_default( &instance, None, )) .unwrap(); let adapter_info = adapter.get_info(); println!( " Using adapter: {} ({:?})", adapter_info.name, adapter_info.backend ); let (device, queue) = block_on(adapter.request_device(&wgpu::DeviceDescriptor { required_features: adapter.features(), required_limits: adapter.limits(), memory_hints: wgpu::MemoryHints::Performance, experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() }, label: None, trace: wgpu::Trace::Off, })) .unwrap(); Self { adapter_info, device, queue, } } } fn main() { let benchmarks = vec![ Benchmark { name: "Device::create_bind_group", func: bind_groups::run_bench, }, Benchmark { name: "Device::create_buffer", func: resource_creation::run_bench, }, Benchmark { name: "naga::front", func: shader::frontends, }, Benchmark { name: "naga::valid", func: shader::validation, }, Benchmark { name: "naga::compact", func: shader::compact, }, Benchmark { name: "naga::back", func: shader::backends, }, Benchmark { name: "Renderpass Encoding", func: renderpass::run_bench, }, Benchmark { name: "Computepass Encoding", func: computepass::run_bench, }, ]; wgpu_benchmark::main(benchmarks); } ================================================ FILE: benches/benches/wgpu-benchmark/renderpass-bindless.wgsl ================================================ @group(0) @binding(0) var tex: binding_array>; struct VertexOutput { @builtin(position) position: vec4f, @location(0) @interpolate(flat) instance_index: u32, } @vertex fn vs_main(@builtin(instance_index) instance_index: u32) -> VertexOutput { return VertexOutput( vec4f(0.0, 0.0, 0.0, 1.0), instance_index ); } @fragment fn fs_main(vs_in: VertexOutput) -> @location(0) vec4f { return textureLoad(tex[7 * vs_in.instance_index + 0], vec2u(0), 0) + textureLoad(tex[7 * vs_in.instance_index + 1], vec2u(0), 0) + textureLoad(tex[7 * vs_in.instance_index + 2], vec2u(0), 0) + textureLoad(tex[7 * vs_in.instance_index + 3], vec2u(0), 0) + textureLoad(tex[7 * vs_in.instance_index + 4], vec2u(0), 0) + textureLoad(tex[7 * vs_in.instance_index + 5], vec2u(0), 0) + textureLoad(tex[7 * vs_in.instance_index + 6], vec2u(0), 0); } ================================================ FILE: benches/benches/wgpu-benchmark/renderpass.rs ================================================ use std::{ num::NonZeroU32, time::{Duration, Instant}, }; use nanorand::{Rng, WyRand}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use wgpu_benchmark::{iter_many, BenchmarkContext, LoopControl}; use crate::DeviceState; fn draw_count(ctx: &BenchmarkContext) -> u32 { // When testing we only want to run a very lightweight version of the benchmark // to ensure that it does not break. if ctx.is_test() { 8 } else { 10_000 } } fn thread_count_list(ctx: &BenchmarkContext) -> &'static [u32] { if ctx.is_test() { &[2] } else { &[1, 2, 4] } } // Must match the number of textures in the renderpass.wgsl shader const TEXTURES_PER_DRAW: u32 = 7; const VERTEX_BUFFERS_PER_DRAW: u32 = 2; struct RenderpassState { device_state: DeviceState, pipeline: wgpu::RenderPipeline, bind_groups: Vec, vertex_buffers: Vec, index_buffers: Vec, render_target: wgpu::TextureView, // Bindless resources bindless_bind_group: Option, bindless_pipeline: Option, } impl RenderpassState { /// Create and prepare all the resources needed for the renderpass benchmark. fn new(ctx: &BenchmarkContext) -> Self { let device_state = DeviceState::new(); let draw_count = draw_count(ctx); let vertex_buffer_count = draw_count * VERTEX_BUFFERS_PER_DRAW; let texture_count = draw_count * TEXTURES_PER_DRAW; let supports_bindless = device_state.device.features().contains( wgpu::Features::TEXTURE_BINDING_ARRAY | wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, ) && device_state .device .limits() .max_sampled_textures_per_shader_stage >= texture_count as _; // Performance gets considerably worse if the resources are shuffled. // // This more closely matches the real-world use case where resources have no // well defined usage order. let mut random = WyRand::new_seed(0x8BADF00D); let mut bind_group_layout_entries = Vec::with_capacity(TEXTURES_PER_DRAW as usize); for i in 0..TEXTURES_PER_DRAW { bind_group_layout_entries.push(wgpu::BindGroupLayoutEntry { binding: i, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }); } let bind_group_layout = device_state .device .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &bind_group_layout_entries, }); let mut texture_views = Vec::with_capacity(texture_count as usize); for i in 0..texture_count { let texture = device_state .device .create_texture(&wgpu::TextureDescriptor { label: Some(&format!("Texture {i}")), size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); texture_views.push(texture.create_view(&wgpu::TextureViewDescriptor { label: Some(&format!("Texture View {i}")), ..Default::default() })); } random.shuffle(&mut texture_views); let texture_view_refs: Vec<_> = texture_views.iter().collect(); let mut bind_groups = Vec::with_capacity(draw_count as usize); for draw_idx in 0..draw_count { let mut entries = Vec::with_capacity(TEXTURES_PER_DRAW as usize); for tex_idx in 0..TEXTURES_PER_DRAW { entries.push(wgpu::BindGroupEntry { binding: tex_idx, resource: wgpu::BindingResource::TextureView( &texture_views[(draw_idx * TEXTURES_PER_DRAW + tex_idx) as usize], ), }); } bind_groups.push( device_state .device .create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &entries, }), ); } random.shuffle(&mut bind_groups); let sm = device_state .device .create_shader_module(wgpu::include_wgsl!("renderpass.wgsl")); let pipeline_layout = device_state .device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); let mut vertex_buffers = Vec::with_capacity(vertex_buffer_count as usize); for _ in 0..vertex_buffer_count { vertex_buffers.push(device_state.device.create_buffer(&wgpu::BufferDescriptor { label: None, size: 3 * 16, usage: wgpu::BufferUsages::VERTEX, mapped_at_creation: false, })); } random.shuffle(&mut vertex_buffers); let mut index_buffers = Vec::with_capacity(draw_count as usize); for _ in 0..draw_count { index_buffers.push(device_state.device.create_buffer(&wgpu::BufferDescriptor { label: None, size: 3 * 4, usage: wgpu::BufferUsages::INDEX, mapped_at_creation: false, })); } random.shuffle(&mut index_buffers); let mut vertex_buffer_attributes = Vec::with_capacity(VERTEX_BUFFERS_PER_DRAW as usize); for i in 0..VERTEX_BUFFERS_PER_DRAW { vertex_buffer_attributes.push(wgpu::vertex_attr_array![i => Float32x4]); } let mut vertex_buffer_layouts = Vec::with_capacity(VERTEX_BUFFERS_PER_DRAW as usize); for attributes in &vertex_buffer_attributes { vertex_buffer_layouts.push(wgpu::VertexBufferLayout { array_stride: 16, step_mode: wgpu::VertexStepMode::Vertex, attributes, }); } let pipeline = device_state .device .create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &sm, entry_point: Some("vs_main"), buffers: &vertex_buffer_layouts, compilation_options: wgpu::PipelineCompilationOptions::default(), }, primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, strip_index_format: None, front_face: wgpu::FrontFace::Cw, cull_mode: Some(wgpu::Face::Back), polygon_mode: wgpu::PolygonMode::Fill, unclipped_depth: false, conservative: false, }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), fragment: Some(wgpu::FragmentState { module: &sm, entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format: wgpu::TextureFormat::Rgba8UnormSrgb, blend: None, write_mask: wgpu::ColorWrites::ALL, })], compilation_options: wgpu::PipelineCompilationOptions::default(), }), multiview_mask: None, cache: None, }); let render_target = device_state .device .create_texture(&wgpu::TextureDescriptor { label: Some("Render Target"), size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }) .create_view(&wgpu::TextureViewDescriptor::default()); let mut bindless_bind_group = None; let mut bindless_pipeline = None; if supports_bindless { let bindless_bind_group_layout = device_state .device .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: Some(NonZeroU32::new(texture_count).unwrap()), }], }); bindless_bind_group = Some(device_state.device.create_bind_group( &wgpu::BindGroupDescriptor { label: None, layout: &bindless_bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureViewArray(&texture_view_refs), }], }, )); let bindless_shader_module = device_state .device .create_shader_module(wgpu::include_wgsl!("renderpass-bindless.wgsl")); let bindless_pipeline_layout = device_state .device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bindless_bind_group_layout)], immediate_size: 0, }); bindless_pipeline = Some(device_state.device.create_render_pipeline( &wgpu::RenderPipelineDescriptor { label: None, layout: Some(&bindless_pipeline_layout), vertex: wgpu::VertexState { module: &bindless_shader_module, entry_point: Some("vs_main"), buffers: &vertex_buffer_layouts, compilation_options: wgpu::PipelineCompilationOptions::default(), }, primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, strip_index_format: None, front_face: wgpu::FrontFace::Cw, cull_mode: Some(wgpu::Face::Back), polygon_mode: wgpu::PolygonMode::Fill, unclipped_depth: false, conservative: false, }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), fragment: Some(wgpu::FragmentState { module: &bindless_shader_module, entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format: wgpu::TextureFormat::Rgba8UnormSrgb, blend: None, write_mask: wgpu::ColorWrites::ALL, })], compilation_options: wgpu::PipelineCompilationOptions::default(), }), multiview_mask: None, cache: None, }, )); } Self { device_state, pipeline, bind_groups, vertex_buffers, index_buffers, render_target, bindless_bind_group, bindless_pipeline, } } fn run_subpass( &self, pass_number: u32, total_passes: u32, draw_count: u32, ) -> wgpu::CommandBuffer { profiling::scope!("Renderpass", &format!("Pass {pass_number}/{total_passes}")); let draws_per_pass = draw_count / total_passes; let mut encoder = self .device_state .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &self.render_target, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store, }, })], occlusion_query_set: None, timestamp_writes: None, depth_stencil_attachment: None, multiview_mask: None, }); let start_idx = pass_number * draws_per_pass; let end_idx = start_idx + draws_per_pass; for draw_idx in start_idx..end_idx { render_pass.set_pipeline(&self.pipeline); render_pass.set_bind_group(0, &self.bind_groups[draw_idx as usize], &[]); for i in 0..VERTEX_BUFFERS_PER_DRAW { render_pass.set_vertex_buffer( i, self.vertex_buffers[(draw_idx * VERTEX_BUFFERS_PER_DRAW + i) as usize] .slice(..), ); } render_pass.set_index_buffer( self.index_buffers[draw_idx as usize].slice(..), wgpu::IndexFormat::Uint32, ); render_pass.draw_indexed(0..3, 0, 0..1); } drop(render_pass); encoder.finish() } fn run_bindless_pass(&self, draw_count: u32) -> wgpu::CommandBuffer { profiling::scope!("Bindless Renderpass"); let mut encoder = self .device_state .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &self.render_target, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store, }, })], occlusion_query_set: None, timestamp_writes: None, depth_stencil_attachment: None, multiview_mask: None, }); render_pass.set_pipeline(self.bindless_pipeline.as_ref().unwrap()); render_pass.set_bind_group(0, Some(self.bindless_bind_group.as_ref().unwrap()), &[]); for i in 0..VERTEX_BUFFERS_PER_DRAW { render_pass.set_vertex_buffer(i, self.vertex_buffers[0].slice(..)); } render_pass.set_index_buffer(self.index_buffers[0].slice(..), wgpu::IndexFormat::Uint32); for draw_idx in 0..draw_count { render_pass.draw_indexed(0..3, 0, draw_idx..draw_idx + 1); } drop(render_pass); encoder.finish() } } pub fn run_bench(mut ctx: BenchmarkContext) -> anyhow::Result> { let state = RenderpassState::new(&ctx); ctx.default_iterations = LoopControl::Time(Duration::from_secs(3)); // This benchmark hangs on Apple Paravirtualized GPUs. No idea why. if state.device_state.adapter_info.name.contains("Paravirtual") { anyhow::bail!("Benchmark unsupported on Paravirtualized GPUs"); } let draw_count = draw_count(&ctx); let mut results = Vec::new(); // Test 10k draw calls split up into 1, 2, 4, and 8 renderpasses for &rpasses in thread_count_list(&ctx) { let labels = vec![ format!("Encoding ({rpasses} passes)"), format!("Submit ({rpasses} passes)"), ]; results.extend(iter_many(&ctx, labels, "draw calls", draw_count, || { let mut buffers: Vec = Vec::with_capacity(rpasses as usize); let encoding_start = Instant::now(); for i in 0..rpasses { buffers.push(state.run_subpass(i, rpasses, draw_count)); } let encoding_duration = encoding_start.elapsed(); let submit_start = Instant::now(); state.device_state.queue.submit(buffers); let submit_duration = submit_start.elapsed(); state .device_state .device .poll(wgpu::PollType::wait_indefinitely()) .unwrap(); vec![encoding_duration, submit_duration] })); } // Test 10k draw calls split up over 2, 4, and 8 threads. for &threads in thread_count_list(&ctx) { let labels = vec![ format!("Encoding ({threads} threads)"), format!("Submit ({threads} threads)"), ]; results.extend(iter_many(&ctx, labels, "draw calls", draw_count, || { let encoding_start = Instant::now(); let buffers = (0..threads) .into_par_iter() .map(|i| state.run_subpass(i, threads, draw_count)) .collect::>(); let encoding_duration = encoding_start.elapsed(); let submit_start = Instant::now(); state.device_state.queue.submit(buffers); let submit_duration = submit_start.elapsed(); state .device_state .device .poll(wgpu::PollType::wait_indefinitely()) .unwrap(); vec![encoding_duration, submit_duration] })); } // Test 10k draw calls with bindless rendering. if state.bindless_bind_group.is_some() { let labels = vec![ "Encoding (bindless)".to_string(), "Submit (bindless)".to_string(), ]; results.extend(iter_many(&ctx, labels, "draw calls", draw_count, || { let encoding_start = Instant::now(); let buffer = state.run_bindless_pass(draw_count); let encoding_duration = encoding_start.elapsed(); let submit_start = Instant::now(); state.device_state.queue.submit([buffer]); let submit_duration = submit_start.elapsed(); state .device_state .device .poll(wgpu::PollType::wait_indefinitely()) .unwrap(); vec![encoding_duration, submit_duration] })); } Ok(results) } ================================================ FILE: benches/benches/wgpu-benchmark/renderpass.wgsl ================================================ @group(0) @binding(0) var tex_1: texture_2d; @group(0) @binding(1) var tex_2: texture_2d; @group(0) @binding(2) var tex_3: texture_2d; @group(0) @binding(3) var tex_4: texture_2d; @group(0) @binding(4) var tex_5: texture_2d; @group(0) @binding(5) var tex_6: texture_2d; @group(0) @binding(6) var tex_7: texture_2d; @vertex fn vs_main() -> @builtin(position) vec4f { return vec4f(0.0, 0.0, 0.0, 1.0); } @fragment fn fs_main() -> @location(0) vec4f { return textureLoad(tex_1, vec2u(0), 0) + textureLoad(tex_2, vec2u(0), 0) + textureLoad(tex_3, vec2u(0), 0) + textureLoad(tex_4, vec2u(0), 0) + textureLoad(tex_5, vec2u(0), 0) + textureLoad(tex_6, vec2u(0), 0) + textureLoad(tex_7, vec2u(0), 0); } ================================================ FILE: benches/benches/wgpu-benchmark/resource_creation.rs ================================================ use std::time::Instant; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use wgpu_benchmark::{iter, BenchmarkContext, SubBenchResult}; use crate::DeviceState; fn thread_count_list(ctx: &BenchmarkContext) -> &'static [usize] { if ctx.is_test() { &[2] } else { &[1, 2, 4, 8] } } pub fn run_bench(ctx: BenchmarkContext) -> anyhow::Result> { let state = DeviceState::new(); const RESOURCES_TO_CREATE: usize = 8; let mut results = Vec::new(); for &threads in thread_count_list(&ctx) { let resources_per_thread = RESOURCES_TO_CREATE / threads; results.push(iter( &ctx, &format!("{threads} threads"), "buffers", RESOURCES_TO_CREATE as u32, || { let start = Instant::now(); let buffers = (0..threads) .into_par_iter() .map(|_| { (0..resources_per_thread) .map(|_| { state.device.create_buffer(&wgpu::BufferDescriptor { label: None, size: 256 * 1024 * 1024, usage: wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }) }) .collect::>() }) .collect::>(); let duration = start.elapsed(); drop(buffers); state.queue.submit([]); state .device .poll(wgpu::PollType::wait_indefinitely()) .unwrap(); duration }, )); } Ok(results) } ================================================ FILE: benches/benches/wgpu-benchmark/shader.rs ================================================ use std::{fs, process::Command}; use wgpu_benchmark::{iter_auto, BenchmarkContext, SubBenchResult}; const DIR_IN: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../naga/tests/in"); use naga_test::*; struct InputWithInfo { inner: Input, data: Vec, string: Option, options: Parameters, module: Option, module_info: Option, } impl From for InputWithInfo { fn from(value: Input) -> Self { let mut options = value.read_parameters(DIR_IN); options.targets = Some(options.targets.unwrap_or(Targets::all())); Self { options, inner: value, data: Vec::new(), string: None, module: None, module_info: None, } } } impl InputWithInfo { fn filename(&self) -> &str { self.inner.file_name.file_name().unwrap().to_str().unwrap() } } struct Inputs { inner: Vec, } impl Inputs { #[track_caller] fn from_dir(folder: &str, extension: &str) -> Self { let inputs: Vec = Input::files_in_dir(folder, &[extension], DIR_IN) .map(|a| a.into()) .collect(); Self { inner: inputs } } fn bytes(&self) -> u64 { self.inner .iter() .map(|input| input.inner.bytes(DIR_IN)) .sum() } fn load(&mut self) { for input in &mut self.inner { if !input.data.is_empty() { continue; } input.data = fs::read(input.inner.input_path(DIR_IN)).unwrap_or_default(); } } fn load_utf8(&mut self) { self.load(); for input in &mut self.inner { if input.string.is_some() { continue; } input.string = Some(std::str::from_utf8(&input.data).unwrap().to_string()); } } fn parse(&mut self) { self.load_utf8(); let mut parser = naga::front::wgsl::Frontend::new(); for input in &mut self.inner { if input.module.is_some() { continue; } parser.set_options((&input.options.wgsl_in).into()); input.module = Some(parser.parse(input.string.as_ref().unwrap()).unwrap()); } } fn validate(&mut self) { self.parse(); let mut validator = naga::valid::Validator::new( naga::valid::ValidationFlags::all(), // Note, this is empty, to let all backends work. naga::valid::Capabilities::empty(), ); for input in &mut self.inner { if input.module_info.is_some() { continue; } input.module_info = validator.validate(input.module.as_ref().unwrap()).ok(); } self.inner.retain(|input| input.module_info.is_some()); } fn is_empty(&self) -> bool { self.inner.is_empty() } } fn parse_glsl(stage: naga::ShaderStage, inputs: &Inputs) { let mut parser = naga::front::glsl::Frontend::default(); let options = naga::front::glsl::Options { stage, defines: Default::default(), }; for input in &inputs.inner { parser .parse(&options, &input.inner.read_source(DIR_IN, false)) .unwrap(); } } fn get_wgsl_inputs() -> Inputs { let mut inputs: Vec = Input::files_in_dir("wgsl", &["wgsl"], DIR_IN) .map(|a| a.into()) .collect(); // remove "large-source" tests, they skew the results inputs.retain(|input| !input.filename().contains("large-source")); assert!(!inputs.is_empty()); Inputs { inner: inputs } } pub fn frontends(ctx: BenchmarkContext) -> anyhow::Result> { let mut results = Vec::new(); let mut inputs_wgsl = get_wgsl_inputs(); inputs_wgsl.parse(); inputs_wgsl.load_utf8(); let inputs_bin = inputs_wgsl .inner .iter() .map(|input| { bincode::serde::encode_to_vec( input.module.as_ref().unwrap(), bincode::config::standard(), ) .unwrap() }) .collect::>(); results.push(iter_auto( &ctx, "bincode decode", "bytes", inputs_wgsl.bytes() as u32, move || { for input in inputs_bin.iter() { bincode::serde::decode_from_slice::( input, bincode::config::standard(), ) .unwrap(); } }, )); let mut frontend = naga::front::wgsl::Frontend::new(); results.push(iter_auto( &ctx, "wgsl", "bytes", inputs_wgsl.bytes() as u32, || { for input in &inputs_wgsl.inner { frontend.set_options((&input.options.wgsl_in).into()); frontend.parse(input.string.as_ref().unwrap()).unwrap(); } }, )); let inputs_spirv = Inputs::from_dir("spv", "spvasm"); assert!(!inputs_spirv.is_empty()); // Assemble all the SPIR-V assembly. let mut assembled_spirv = Vec::>::new(); 'spirv: for input in &inputs_spirv.inner { let output = match Command::new("spirv-as") .arg(input.inner.input_path(DIR_IN)) .arg("-o") .arg("-") .output() { Ok(output) => output, Err(e) => { eprintln!( "Failed to execute spirv-as: {e}\n\ spvasm benchmarks will be skipped.\n\ spirv-as can be installed by installing the Vulkan SDK and adding \ it to your PATH.", ); break 'spirv; } }; if !output.status.success() { panic!( "spirv-as failed: {}\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); } assembled_spirv.push(bytemuck::pod_collect_to_vec(&output.stdout)); } let total_bytes: u64 = assembled_spirv.iter().map(|spv| spv.len() as u64).sum(); assert!(assembled_spirv.len() == inputs_spirv.inner.len() || assembled_spirv.is_empty()); results.push(iter_auto( &ctx, "spv parse", "bytes", total_bytes as u32, || { for (i, input) in assembled_spirv.iter().enumerate() { let params = &inputs_spirv.inner[i].options; let SpirvInParameters { adjust_coordinate_space, } = params.spv_in; let parser = naga::front::spv::Frontend::new( input.iter().cloned(), &naga::front::spv::Options { adjust_coordinate_space, strict_capabilities: true, ..Default::default() }, ); parser.parse().unwrap(); } }, )); let mut inputs_vertex = Inputs::from_dir("glsl", "vert"); let mut inputs_fragment = Inputs::from_dir("glsl", "frag"); let mut inputs_compute = Inputs::from_dir("glsl", "comp"); assert!(!inputs_vertex.is_empty()); assert!(!inputs_fragment.is_empty()); assert!(!inputs_compute.is_empty()); inputs_vertex.load_utf8(); inputs_fragment.load_utf8(); inputs_compute.load_utf8(); results.push(iter_auto( &ctx, "glsl parse", "bytes", (inputs_vertex.bytes() + inputs_fragment.bytes() + inputs_compute.bytes()) as u32, || { parse_glsl(naga::ShaderStage::Vertex, &inputs_vertex); parse_glsl(naga::ShaderStage::Fragment, &inputs_fragment); parse_glsl(naga::ShaderStage::Compute, &inputs_compute); }, )); Ok(results) } pub fn validation(ctx: BenchmarkContext) -> anyhow::Result> { let mut results = Vec::new(); let mut inputs = get_wgsl_inputs(); inputs.parse(); let mut validator = naga::valid::Validator::new( naga::valid::ValidationFlags::all(), naga::valid::Capabilities::all(), ); validator .subgroup_stages(naga::valid::ShaderStages::all()) .subgroup_operations(naga::valid::SubgroupOperationSet::all()); results.push(iter_auto( &ctx, "validation", "bytes", inputs.bytes() as u32, || { for input in &inputs.inner { validator.validate(input.module.as_ref().unwrap()).unwrap(); } }, )); Ok(results) } pub fn compact(ctx: BenchmarkContext) -> anyhow::Result> { use naga::compact::{compact, KeepUnused}; let mut results = Vec::new(); let mut inputs = get_wgsl_inputs(); inputs.validate(); assert!(!inputs.is_empty()); results.push(iter_auto( &ctx, "compact", "bytes", inputs.bytes() as u32, || { for input in &mut inputs.inner { compact(input.module.as_mut().unwrap(), KeepUnused::No); } }, )); Ok(results) } pub fn backends(ctx: BenchmarkContext) -> anyhow::Result> { let mut results = Vec::new(); let mut inputs = get_wgsl_inputs(); inputs.validate(); assert!(!inputs.is_empty()); let total_bytes = inputs.bytes() as u32; results.push(iter_auto(&ctx, "wgsl", "bytes", total_bytes, || { let mut string = String::new(); for input in &inputs.inner { if input.options.targets.unwrap().contains(Targets::WGSL) { let mut writer = naga::back::wgsl::Writer::new(&mut string, (&input.options.wgsl).into()); let _ = writer.write( input.module.as_ref().unwrap(), input.module_info.as_ref().unwrap(), ); string.clear(); } } })); results.push(iter_auto(&ctx, "spv", "bytes", total_bytes, || { let mut data = Vec::new(); let mut writer = naga::back::spv::Writer::new(&Default::default()).unwrap(); for input in &inputs.inner { let shared_info = WriterSharedOptions { mesh_output_validation: input.options.mesh_output_validation, task_limits: input.options.task_limits, bounds_checks_policies: input.options.bounds_check_policies, }; if input.options.targets.unwrap().contains(Targets::SPIRV) { if input.filename().contains("pointer-function-arg") { continue; } let opt = input.options.spv.to_options(&shared_info, None); if writer.set_options(&opt).is_ok() { let _ = writer.write( input.module.as_ref().unwrap(), input.module_info.as_ref().unwrap(), None, &None, &mut data, ); data.clear(); } } } })); results.push(iter_auto( &ctx, "spv multiple entrypoints", "bytes", total_bytes, || { let mut data = Vec::new(); let options = naga::back::spv::Options::default(); for input in &inputs.inner { if input.options.targets.unwrap().contains(Targets::SPIRV) { if input.filename().contains("pointer-function-arg") { continue; } let mut writer = naga::back::spv::Writer::new(&options).unwrap(); let module = input.module.as_ref().unwrap(); for ep in module.entry_points.iter() { let pipeline_options = naga::back::spv::PipelineOptions { shader_stage: ep.stage, entry_point: ep.name.clone(), }; let _ = writer.write( input.module.as_ref().unwrap(), input.module_info.as_ref().unwrap(), Some(&pipeline_options), &None, &mut data, ); data.clear(); } } } }, )); results.push(iter_auto(&ctx, "msl", "bytes", total_bytes, || { let mut string = String::new(); let options = naga::back::msl::Options::default(); for input in &inputs.inner { if input.options.targets.unwrap().contains(Targets::METAL) { let pipeline_options = naga::back::msl::PipelineOptions::default(); let mut writer = naga::back::msl::Writer::new(&mut string); let _ = writer.write( input.module.as_ref().unwrap(), input.module_info.as_ref().unwrap(), &options, &pipeline_options, ); string.clear(); } } })); results.push(iter_auto(&ctx, "hlsl", "bytes", total_bytes, || { let options = naga::back::hlsl::Options::default(); let mut string = String::new(); for input in &inputs.inner { if input.options.targets.unwrap().contains(Targets::HLSL) { let pipeline_options = Default::default(); let mut writer = naga::back::hlsl::Writer::new(&mut string, &options, &pipeline_options); let _ = writer.write( input.module.as_ref().unwrap(), input.module_info.as_ref().unwrap(), None, ); string.clear(); } } })); results.push(iter_auto( &ctx, "glsl multiple entrypoints", "bytes", total_bytes, || { let mut string = String::new(); let options = naga::back::glsl::Options { version: naga::back::glsl::Version::new_gles(320), writer_flags: naga::back::glsl::WriterFlags::empty(), binding_map: Default::default(), zero_initialize_workgroup_memory: true, }; for input in &inputs.inner { if !input.options.targets.unwrap().contains(Targets::GLSL) { continue; } let module = input.module.as_ref().unwrap(); let info = input.module_info.as_ref().unwrap(); for ep in module.entry_points.iter() { let pipeline_options = naga::back::glsl::PipelineOptions { shader_stage: ep.stage, entry_point: ep.name.clone(), multiview: None, }; if let Ok(mut writer) = naga::back::glsl::Writer::new( &mut string, module, info, &options, &pipeline_options, naga::proc::BoundsCheckPolicies::default(), ) { let _ = writer.write(); } string.clear(); } } }, )); Ok(results) } ================================================ FILE: benches/src/context.rs ================================================ use std::time::Duration; #[derive(Clone, Copy)] pub enum LoopControl { Iterations(u32), Time(Duration), } impl Default for LoopControl { fn default() -> Self { LoopControl::Time(Duration::from_secs(2)) } } impl LoopControl { pub(crate) fn finished(&self, iterations: u32, elapsed: Duration) -> bool { match self { LoopControl::Iterations(target) => iterations >= *target, LoopControl::Time(target) => elapsed >= *target, } } } pub struct BenchmarkContext { pub(crate) override_iters: Option, pub default_iterations: LoopControl, pub(crate) is_test: bool, } impl BenchmarkContext { pub fn is_test(&self) -> bool { self.is_test } } ================================================ FILE: benches/src/file.rs ================================================ use anyhow::Context as _; use crate::BenchmarkFile; const FILE_PREFIX: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../target/bench/"); pub const PREVIOUS: &str = "previous"; pub(crate) fn get_comparison_file(baseline: Option<&str>) -> Option { let file_name = baseline.unwrap_or(PREVIOUS); let path = format!("{FILE_PREFIX}{file_name}.json"); let file = std::fs::read_to_string(path).ok()?; let benchmark_file: BenchmarkFile = serde_json::from_str(&file).ok()?; Some(benchmark_file) } pub(crate) fn write_results_file( file_name: &str, output_file: &BenchmarkFile, ) -> anyhow::Result<()> { let path = format!("{FILE_PREFIX}{file_name}.json"); let json = serde_json::to_string_pretty(output_file)?; std::fs::create_dir_all(FILE_PREFIX) .with_context(|| format!("Trying to create directory {FILE_PREFIX}"))?; std::fs::write(&path, json).with_context(|| format!("Trying to write file {path}"))?; Ok(()) } ================================================ FILE: benches/src/iter.rs ================================================ use std::time::Duration; use crate::{BenchmarkContext, LoopControl, SubBenchResult}; pub fn iter( ctx: &BenchmarkContext, name: &str, throughput_unit: &str, throughput_count_per_iteration: u32, mut f: impl FnMut() -> Duration, ) -> SubBenchResult { profiling::scope!("iter", name); let mut iterations = 0_u32; let mut duration = Duration::ZERO; let control = if let Some(override_control) = ctx.override_iters { override_control } else { ctx.default_iterations }; while !control.finished(iterations, duration) { duration += f(); iterations += 1; } SubBenchResult { name: name.to_string(), avg_duration_per_iteration: duration / iterations, iterations, throughput_unit: throughput_unit.to_string(), throughput_count_per_iteration, } } pub fn iter_auto( ctx: &BenchmarkContext, name: &str, throughput_unit: &str, throughput_count_per_iteration: u32, mut f: impl FnMut(), ) -> SubBenchResult { iter( ctx, name, throughput_unit, throughput_count_per_iteration, || { let start = std::time::Instant::now(); f(); start.elapsed() }, ) } pub fn iter_many( ctx: &BenchmarkContext, names: Vec, throughput_unit: &str, throughput_count_per_iteration: u32, mut f: impl FnMut() -> Vec, ) -> Vec { profiling::scope!("iter", &*names[0]); let mut iterations = 0_u32; let mut durations = vec![Duration::ZERO; names.len()]; let control = if let Some(override_control) = ctx.override_iters { override_control } else { LoopControl::Time(Duration::from_secs(1)) }; // We use the first duration to determine whether to stop. This means the other sub-benchmarks // could have run for longer or shorter than intended, but that's acceptable. while !control.finished(iterations, *durations.first().unwrap_or(&Duration::ZERO)) { let iteration_durations = f(); assert_eq!(iteration_durations.len(), names.len()); for (i, dur) in iteration_durations.into_iter().enumerate() { durations[i] += dur; } iterations += 1; } durations .into_iter() .enumerate() .map(|(i, d)| SubBenchResult { name: names[i].to_string(), avg_duration_per_iteration: d / iterations, iterations, throughput_unit: throughput_unit.to_string(), throughput_count_per_iteration, }) .collect() } ================================================ FILE: benches/src/lib.rs ================================================ #![cfg(not(target_arch = "wasm32"))] #![expect(clippy::disallowed_types)] // We're outside of the main wgpu codebase //! Benchmarking framework for `wgpu`. //! //! This crate is a basic framework for benchmarking. Its design is guided //! by a few goals: //! //! - Enumerating tests should be extremely cheap. `criterion` needs //! to run all of your benchmark functions to enumerate them during //! testing. This requires your code to contort itself to avoid doing //! any work until you enter a benchmark callback. This framework //! avoids that by having an explicit list of benchmark function. //! - It must be compatible with `cargo-nextest` and have a compatible //! "test" mode that runs each benchmark exactly once. //! - It should be able to have intuitive test grouping, allowing for //! allowing for quick execution of a reasonable baseline set of benchmarks //! during development, while still allowing for a more exhaustive //! benchmark suite to be run if desired. //! //! By default all tests run for 2 seconds, but this can be overridden //! by individual tests. use std::{collections::HashMap, io::IsTerminal, time::Duration}; use anyhow::Result; use pico_args::Arguments; use serde::{Deserialize, Serialize}; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; mod context; mod file; mod iter; mod print; pub use context::*; pub use iter::*; use crate::file::PREVIOUS; #[derive(Serialize, Deserialize, Default)] pub struct BenchmarkFile { pub results: HashMap>, } impl BenchmarkFile { pub fn get_result( &self, benchmark_name: &str, sub_benchmark_name: &str, ) -> Option<&SubBenchResult> { self.results .get(benchmark_name)? .iter() .find(|r| r.name == sub_benchmark_name) } } #[derive(Serialize, Deserialize)] pub struct SubBenchResult { /// Name of the subbenchmark. pub name: String, /// Average duration per iteration of the subbenchmark. pub avg_duration_per_iteration: Duration, /// Total number of iterations executed. pub iterations: u32, /// Throughput unit description. e.g., "bytes", "elements", etc. pub throughput_unit: String, /// Number of throughput units processed per iteration. pub throughput_count_per_iteration: u32, } impl SubBenchResult { pub fn throughput_per_second(&self) -> f64 { let secs_f64 = self.avg_duration_per_iteration.as_secs_f64(); if secs_f64 == 0.0 { return 0.0; } self.throughput_count_per_iteration as f64 / secs_f64 } } pub struct Benchmark { pub name: &'static str, pub func: fn(BenchmarkContext) -> Result>, } const HELP: &str = "\ Usage: wgpu-benchmark [OPTIONS] [BENCHMARK_NAME] Modes: --bench Run in benchmark mode, comparing against previous results. --list List available benchmarks. Run in test mode, executing each benchmark exactly once. Test Matching: --exact When specifying BENCHMARK_NAME, only run exact matches. BENCHMARK_NAME Only run benchmarks whose names contain this substring. Comparison: -b, --baseline NAME Specify a baseline file for comparison. -s, --save-baseline NAME Save the results as a baseline file. Timings: --iters N Override number of iterations per benchmark. --time SECONDS Override time per benchmark in seconds. Other: --color Set colored output (always,always-ansi,auto,never). --format terse Specify --list output format (only 'terse' is supported). --no-capture (Ignored) "; pub fn main(benchmarks: Vec) { let mut args = Arguments::from_env(); let help = args.contains(["-h", "--help"]); if help { println!("{HELP}"); return; } let mut color: ColorChoice = args .opt_value_from_str("--color") .unwrap_or(None) .unwrap_or(ColorChoice::Auto); if color == ColorChoice::Auto && !std::io::stdin().is_terminal() { color = ColorChoice::Never; } let exact = args.contains("--exact"); // We don't actually need this flag, but cargo-nextest passes it in // test mode, so we need to accept it. let _no_capture = args.contains("--no-capture"); #[expect(clippy::manual_map)] // So much clearer this way let mut override_iterations = if let Some(iters) = args.opt_value_from_str("--iters").unwrap() { Some(LoopControl::Iterations(iters)) } else if let Some(seconds) = args.opt_value_from_str("--time").unwrap() { Some(LoopControl::Time(Duration::from_secs_f64(seconds))) } else { None }; let baseline_name: Option = args.opt_value_from_str(["-b", "--baseline"]).unwrap(); let write_baseline: Option = args.opt_value_from_str(["-s", "--save-baseline"]).unwrap(); let is_bench = args.contains("--bench"); let is_list = args.contains("--list"); let is_test = !is_bench && !is_list; let format: Option = args.opt_value_from_str("--format").unwrap(); if let Some(fmt) = format { assert_eq!(fmt, "terse", "Only 'terse' format is supported."); } if let Some(ref baseline) = baseline_name { if baseline == PREVIOUS { eprintln!("Cannot use '{PREVIOUS}' as a baseline name."); return; } } if let Some(ref write_baseline) = write_baseline { if write_baseline == PREVIOUS { eprintln!("Cannot use '{PREVIOUS}' as a baseline name."); return; } } if override_iterations.is_none() && is_test { override_iterations = Some(LoopControl::Iterations(1)); } let name = args.free_from_str::().ok(); let baseline = if is_bench { let res = file::get_comparison_file(baseline_name.as_deref()); match (&res, baseline_name.as_deref()) { (Some(_), Some(baseline)) => { println!("Using baseline \"{baseline}\" for comparison.\n") } (None, Some(baseline)) => { eprintln!("Could not find baseline named {baseline:?}.\n"); return; } (Some(_), None) => { println!("Using previous benchmark results for comparison.\n"); } (None, None) => { println!("No previous benchmark results found for comparison.\n"); } } res } else { None }; let mut output_file = BenchmarkFile::default(); let mut stdout = StandardStream::stdout(color); for bench in benchmarks { if let Some(ref bench_name) = name { if exact { if bench.name != bench_name { continue; } } else if !bench.name.contains(bench_name) { continue; } } if is_list { println!("{}: benchmark", bench.name); continue; } let ctx = BenchmarkContext { override_iters: override_iterations, default_iterations: LoopControl::default(), is_test, }; stdout .set_color(ColorSpec::new().set_fg(Some(Color::Blue))) .unwrap(); println!("Running benchmark: {}", bench.name); stdout.reset().unwrap(); let results = { profiling::scope!("bench", bench.name); let r = (bench.func)(ctx); match r { Ok(r) => r, Err(e) => { eprintln!(" Error running benchmark '{}': {:?}", bench.name, e); continue; } } }; let previous_results = if let Some(ref baseline) = baseline { baseline.results.get(bench.name).map(|r| r.as_slice()) } else { None }; print::print_results(&mut stdout, &results, previous_results); output_file.results.insert(bench.name.to_string(), results); } file::write_results_file(PREVIOUS, &output_file).unwrap(); if let Some(output_baseline) = write_baseline { file::write_results_file(&output_baseline, &output_file).unwrap(); } } ================================================ FILE: benches/src/print.rs ================================================ use std::collections::HashMap; use std::io::Write; use termcolor::{Color, ColorSpec, StandardStream, WriteColor}; use crate::SubBenchResult; #[derive(Default, Clone)] struct Delta { throughput_change_str: String, throughput_change: f64, time_change_str: String, time_change: f64, } impl Delta { fn new(previous: &SubBenchResult, current: &SubBenchResult) -> Self { let prev_throughput = previous.throughput_per_second(); let curr_throughput = current.throughput_per_second(); let delta_throughput = if prev_throughput != 0.0 { (curr_throughput - prev_throughput) / prev_throughput * 100.0 } else { 0.0 }; let throughput_change = format!(" ({delta_throughput:+.2}%)"); let prev_time = previous.avg_duration_per_iteration; let curr_time = current.avg_duration_per_iteration; let delta_time = if prev_time.as_nanos() != 0 { (curr_time.as_secs_f64() - prev_time.as_secs_f64()) / prev_time.as_secs_f64() * 100.0 } else { 0.0 }; let time_change = format!("{delta_time:+.2}%; "); Delta { throughput_change_str: throughput_change, throughput_change: delta_throughput, time_change_str: time_change, time_change: delta_time, } } } /// Get a color spec for the given change percentage. /// /// Positive changes are red (regression), negative changes are green (improvement). /// This represents changes for time durations. For throughput changes, the sign should be inverted /// before passing to this method. fn get_change_color(percent_change: f64) -> ColorSpec { let mut color_spec = ColorSpec::new(); if percent_change > 3.0 { color_spec.set_fg(Some(Color::Red)); } else if percent_change < -3.0 { color_spec.set_fg(Some(Color::Green)); } else { color_spec.set_fg(Some(Color::Yellow)); } if percent_change.abs() > 15.0 { color_spec.set_intense(true); } color_spec } pub fn print_results( stdout: &mut StandardStream, results: &[SubBenchResult], previous_results: Option<&[SubBenchResult]>, ) { let mut deltas = HashMap::new(); if let Some(previous_results) = previous_results { for result in results { if let Some(previous_result) = previous_results.iter().find(|r| r.name == result.name) { deltas.insert(result.name.clone(), Delta::new(previous_result, result)); } } } let longest_throughput_change_len = deltas .values() .map(|d| d.throughput_change_str.len()) .max() .unwrap_or(0); let longest_time_change_len = deltas .values() .map(|d| d.time_change_str.len()) .max() .unwrap_or(0); let longest_name_len = results.iter().map(|r| r.name.len()).max().unwrap_or(0); let duration_strings: Vec = results .iter() .map(|r| format!("{:.3?}", r.avg_duration_per_iteration)) .collect(); let longest_duration_len = duration_strings.iter().map(|s| s.len()).max().unwrap_or(0); let iterations_strings: Vec = results .iter() .map(|r| format!("{}", r.iterations)) .collect(); let longest_iterations_len = iterations_strings .iter() .map(|s| s.len()) .max() .unwrap_or(0); let throughput_strings: Vec = results .iter() .map(|r| { let throughput_per_second = r.throughput_count_per_iteration as f64 / r.avg_duration_per_iteration.as_secs_f64(); human_scale(throughput_per_second) }) .collect(); let longest_throughput_len = throughput_strings .iter() .map(|s| s.len()) .max() .unwrap_or(0); let longest_throughput_unit_len = results .iter() .map(|r| r.throughput_unit.len()) .max() .unwrap_or(0); for (i, result) in results.iter().enumerate() { let delta = deltas.get(&result.name).cloned().unwrap_or_default(); let time_color = get_change_color(delta.time_change); let throughput_color = get_change_color(-delta.throughput_change); stdout .set_color(ColorSpec::new().set_fg(Some(Color::Cyan))) .unwrap(); write!(stdout, " {:>longest_name_len$}: ", result.name).unwrap(); stdout.set_color(&time_color).unwrap(); write!(stdout, "{:>longest_duration_len$} ", duration_strings[i],).unwrap(); stdout.reset().unwrap(); write!(stdout, "(").unwrap(); stdout.set_color(&time_color).unwrap(); write!( stdout, "{:>longest_time_change_len$}", delta.time_change_str ) .unwrap(); stdout.reset().unwrap(); write!( stdout, "over {:>longest_iterations_len$} iter) ", result.iterations, ) .unwrap(); stdout.set_color(&throughput_color).unwrap(); write!(stdout, "{:>longest_throughput_len$}", throughput_strings[i]).unwrap(); stdout.reset().unwrap(); write!( stdout, " {:>longest_throughput_unit_len$}/s", result.throughput_unit, ) .unwrap(); stdout.set_color(&throughput_color).unwrap(); writeln!( stdout, "{:>longest_throughput_change_len$}", delta.throughput_change_str ) .unwrap(); } println!(); } fn human_scale(value: f64) -> String { const PREFIXES: &[&str] = &["", "K", "M", "G", "T", "P"]; if value == 0.0 { return "0".to_string(); } let abs_value = value.abs(); let exponent = (abs_value.log10() / 3.0).floor() as usize; let prefix_index = exponent.min(PREFIXES.len() - 1); let scaled = value / 10_f64.powi((prefix_index * 3) as i32); // Determine decimal places for 3 significant figures let decimal_places = if scaled.abs() >= 100.0 { 0 } else if scaled.abs() >= 10.0 { 1 } else { 2 }; format!( "{:.prec$}{}", scaled, PREFIXES[prefix_index], prec = decimal_places ) } ================================================ FILE: clippy.toml ================================================ # NOTE: Other global Clippy config (severity overrides) is in top-level Cargo.toml. disallowed-types = [ { path = "std::collections::HashMap", reason = "use hashbrown::HashMap instead" }, { path = "std::collections::HashSet", reason = "use hashbrown::HashSet instead" }, ] # The default large-error-threshold is 128. We have a bunch of complex error # types that are slightly larger than that. large-error-threshold = 192 ================================================ FILE: codecov.yml ================================================ coverage: status: project: default: informational: true if_ci_failed: success patch: default: informational: true if_ci_failed: success comment: false github_checks: annotations: false ================================================ FILE: cts_runner/Cargo.toml ================================================ [package] name = "cts_runner" version.workspace = true authors = ["Luca Casonato "] edition.workspace = true description = "CTS runner for wgpu" license.workspace = true publish = false [dependencies] env_logger.workspace = true # We make all dependencies conditional on not being wasm, # so the whole workspace can built as wasm. [target.'cfg(not(target_arch = "wasm32"))'.dependencies] deno_console.workspace = true deno_core.workspace = true deno_features.workspace = true deno_url.workspace = true deno_web.workspace = true deno_webidl.workspace = true deno_webgpu.workspace = true log.workspace = true pico-args.workspace = true tokio = { workspace = true, features = ["full"] } termcolor.workspace = true [dev-dependencies] tempfile.workspace = true ================================================ FILE: cts_runner/README.md ================================================ # cts_runner This crate contains infrastructure for running the WebGPU conformance tests on Deno's `wgpu`-based implementation of WebGPU. Instructions for running the tests via the CTS `xtask` are in [docs/testing.md](https://github.com/gfx-rs/wgpu/blob/trunk/docs/testing.md#webgpu-cts). The file [revision.txt](./revision.txt) specifies the version of the CTS that will be used by default. `cts_runner` is somewhat misnamed at this point, in that it is useful for things other than just running the CTS: - The [tests](./tests) directory contains a few directed tests for Deno's bindings to `wgpu`. - Standalone JavaScript snippets that use WebGPU can be run with a command like: `cargo run -p cts_runner -- test.js`. ================================================ FILE: cts_runner/examples/hello-compute.js ================================================ const adapter = await navigator.gpu.requestAdapter(); const numbers = [1, 4, 3, 295]; const device = await adapter.requestDevice(); const shaderCode = ` @group(0) @binding(0) var v_indices: array; // this is used as both input and output for convenience // The Collatz Conjecture states that for any integer n: // If n is even, n = n/2 // If n is odd, n = 3n+1 // And repeat this process for each new n, you will always eventually reach 1. // Though the conjecture has not been proven, no counterexample has ever been found. // This function returns how many times this recurrence needs to be applied to reach 1. fn collatz_iterations(n_base: u32) -> u32{ var n: u32 = n_base; var i: u32 = 0u; loop { if (n <= 1u) { break; } if (n % 2u == 0u) { n = n / 2u; } else { // Overflow? (i.e. 3*n + 1 > 0xffffffffu?) if (n >= 1431655765u) { // 0x55555555u return 4294967295u; // 0xffffffffu } n = 3u * n + 1u; } i = i + 1u; } return i; } @compute @workgroup_size(1) fn main(@builtin(global_invocation_id) global_id: vec3) { v_indices[global_id.x] = collatz_iterations(v_indices[global_id.x]); }`; const shaderModule = device.createShaderModule({ code: shaderCode, }); const size = new Uint32Array(numbers).byteLength; const stagingBuffer = device.createBuffer({ size: size, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, }); const storageBuffer = device.createBuffer({ label: "Storage Buffer", size: size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, mappedAtCreation: true, }); const buf = new Uint32Array(storageBuffer.getMappedRange()); buf.set(numbers); storageBuffer.unmap(); const computePipeline = device.createComputePipeline({ layout: "auto", compute: { module: shaderModule, entryPoint: "main", }, }); const bindGroupLayout = computePipeline.getBindGroupLayout(0); const bindGroup = device.createBindGroup({ layout: bindGroupLayout, entries: [ { binding: 0, resource: { buffer: storageBuffer, }, }, ], }); const encoder = device.createCommandEncoder(); const computePass = encoder.beginComputePass(); computePass.setPipeline(computePipeline); computePass.setBindGroup(0, bindGroup); computePass.insertDebugMarker("compute collatz iterations"); computePass.dispatchWorkgroups(numbers.length); computePass.end(); encoder.copyBufferToBuffer(storageBuffer, 0, stagingBuffer, 0, size); device.queue.submit([encoder.finish()]); await stagingBuffer.mapAsync(1); const data = stagingBuffer.getMappedRange(); function isTypedArrayEqual(a, b) { if (a.byteLength !== b.byteLength) return false; return a.every((val, i) => val === b[i]); } const actual = new Uint32Array(data); const expected = new Uint32Array([0, 2, 7, 55]); console.log("actual", actual); console.log("expected", expected); if (!isTypedArrayEqual(actual, expected)) { throw new TypeError("Actual does not equal expected!"); } stagingBuffer.unmap(); device.destroy(); ================================================ FILE: cts_runner/fail.lst ================================================ // CTS test selectors that are not expected to pass. // // At present this only includes tests in the `api,validation` and // `shader,validation` hierarchies. // // Percentages in comments are the portion of tests that are passing. Some // failures that are intermittent or platform-dependent are marked with `***`. // Many populated by Claude and may be wrong. // // Linked issues may not cover all failures in the suite. // // The `cts_runner` integration test `lst_files_are_sorted` verifies that test selectors // appear in this file in order -- including ones in comments. webgpu:api,validation,buffer,mapping:* // crash webgpu:api,validation,capability_checks,features,* // 21%, TypeError not thrown for missing features (needs deno_webgpu fix) webgpu:api,validation,capability_checks,limits,* // 60%, workgroup storage size validation unimplemented; interstage vars counting; bind group timing on dx12 webgpu:api,validation,compute_pipeline:limits,workgroup_storage_size:* // 0% webgpu:api,validation,compute_pipeline:overrides,workgroup_size,limits,workgroup_storage_size:* // 0%, missing workgroup storage size validation webgpu:api,validation,compute_pipeline:overrides,workgroup_size,limits:* // 0% webgpu:api,validation,createBindGroup:buffer,resource_state:* // 0%, https://github.com/gfx-rs/wgpu/issues/7881 webgpu:api,validation,createBindGroup:external_texture,* // 0%, no external external texture in deno webgpu:api,validation,createBindGroup:texture,resource_state:* // crash, https://github.com/gfx-rs/wgpu/issues/7881 webgpu:api,validation,createBindGroupLayout:visibility,VERTEX_shader_stage_buffer_type:* // 25%, writable storage buffers not allowed in VERTEX webgpu:api,validation,createBindGroupLayout:visibility,VERTEX_shader_stage_storage_texture_access:* // 25%, write-access storage textures not allowed in VERTEX webgpu:api,validation,createBindGroupLayout:visibility:* // 25%, missing per-stage storage limits webgpu:api,validation,createView:texture_state:* // 0%, https://github.com/gfx-rs/wgpu/issues/7881 webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_ranges:* // ***, https://github.com/gfx-rs/wgpu/issues/8118 webgpu:api,validation,encoding,cmds,debug:* // 92%, https://github.com/gfx-rs/wgpu/issues/8039 webgpu:api,validation,encoding,cmds,render,* // https://github.com/gfx-rs/wgpu/issues/7912 webgpu:api,validation,encoding,cmds,setBindGroup:dynamic_offsets_match_expectations_in_pass_encoder:* // deno unwrap webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="compute%20pass";state="destroyed";* // https://github.com/gfx-rs/wgpu/issues/7881 webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="render%20bundle";state="destroyed";* // https://github.com/gfx-rs/wgpu/issues/7881 webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="render%20pass";state="destroyed";* // https://github.com/gfx-rs/wgpu/issues/7881 webgpu:api,validation,encoding,cmds,setBindGroup:u32array_start_and_length:* // deno unwrap webgpu:api,validation,encoding,cmds,setImmediates:* // 0%, feature not implemented webgpu:api,validation,encoding,createRenderBundleEncoder:* // 26%, empty attachments, format compatibility webgpu:api,validation,encoding,encoder_open_state:* // https://github.com/gfx-rs/wgpu/issues/7857 webgpu:api,validation,encoding,queries,resolveQuerySet:* // 93%, https://github.com/gfx-rs/wgpu/issues/7881 webgpu:api,validation,encoding,render_bundle:* // 81%, readonly flag normalization mismatch webgpu:api,validation,image_copy,buffer_texture_copies:* // https://github.com/gfx-rs/wgpu/issues/7946 webgpu:api,validation,layout_shader_compat:pipeline_layout_shader_exact_match:* // dx12, https://bugzilla.mozilla.org/show_bug.cgi?id=2017725 webgpu:api,validation,non_filterable_texture:non_filterable_texture_with_filtering_sampler:* // 80%, depth textures with filtering samplers webgpu:api,validation,query_set,create:count:* // 0%, wgpu incorrectly rejects zero-count query sets webgpu:api,validation,queue,buffer_mapped:* // ***, vulkan webgpu:api,validation,queue,destroyed,* // 71%, writeBuffer/writeTexture return value, destroyed query set webgpu:api,validation,queue,writeBuffer:ranges:* // 0%, missing OperationError for invalid ranges webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,depth_stencil_read_only_write_state:* // fails on dx12 webgpu:api,validation,render_pass,render_pass_descriptor:depth_stencil_attachment,loadOp_storeOp_match_depthReadOnly_stencilReadOnly:* // 33%, missing validation: depth/stencil load/store ops provided for missing texture format aspects webgpu:api,validation,render_pipeline,fragment_state:dual_source_blending,color_target_count:* webgpu:api,validation,render_pipeline,fragment_state:pipeline_output_targets,blend:* // 95%, blend factors reading source alpha require vec4 webgpu:api,validation,render_pipeline,fragment_state:pipeline_output_targets:* // 3%, color target without shader output requires writeMask=0 webgpu:api,validation,render_pipeline,inter_stage:* // 15%, inter-stage type validation gaps and interpolation default handling webgpu:api,validation,render_pipeline,misc:external_texture:* // 0%, no external texture in deno webgpu:api,validation,render_pipeline,misc:storage_texture,format:* // 8%, similar to compute pipeline issue webgpu:api,validation,render_pipeline,multisample_state:* // 60%, https://github.com/gfx-rs/wgpu/issues/8779 webgpu:api,validation,render_pipeline,overrides:* // 17%, missing validation: invalid pipeline constant identifiers silently ignored webgpu:api,validation,render_pipeline,vertex_state:max_vertex_attribute_limit:* // 0%, empty vertex buffers not counted, GPUInternalError no message webgpu:api,validation,render_pipeline,vertex_state:max_vertex_buffer_limit:* // 0%, empty vertex buffers not counted toward limit webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_contained_in_stride:* // fails on metal in CI https://github.com/gfx-rs/wgpu/issues/8312 webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_offset_alignment:* // fails on metal in CI webgpu:api,validation,resource_usages,texture,in_pass_encoder:subresources_and_binding_types_combination_for_color:* // 92%, https://github.com/gfx-rs/wgpu/issues/3126 webgpu:api,validation,resource_usages,texture,in_render_common:subresources,depth_stencil_attachment_and_bind_group:* // 69%, https://github.com/gfx-rs/wgpu/issues/8705 webgpu:api,validation,state,device_lost,destroy:* // crash webgpu:api,validation,texture,destroy:submit_a_destroyed_texture_as_attachment:* // 44%, https://github.com/gfx-rs/wgpu/issues/8714 webgpu:shader,execution,expression,call,builtin,atan2:f16:* // dx12, fails with dxc, passes with fxc, https://github.com/gfx-rs/wgpu/issues/9179 webgpu:shader,validation,decl,context_dependent_resolution:* // 92%, f16 as reserved keyword when enabled webgpu:shader,validation,decl,let:* // texture/sampler let webgpu:shader,validation,decl,override:* // 93%, unrestricted_pointer_parameters not implemented, https://github.com/gfx-rs/wgpu/issues/5158 webgpu:shader,validation,decl,var:* // 99%, https://github.com/gfx-rs/wgpu/issues/8925 (trailing comma), atomics in read-only storage, shader stage restrictions webgpu:shader,validation,expression,access,array:* // 97%, runtime indexing with compile-time-known values, https://github.com/gfx-rs/wgpu/issues/4390 webgpu:shader,validation,expression,access,matrix:* // 93%, runtime OOB matrix access with literal indices incorrectly rejected webgpu:shader,validation,expression,access,vector:* // 52%, https://github.com/gfx-rs/wgpu/issues/4390, and missing swizzle validation webgpu:shader,validation,expression,binary,add_sub_mul:* // 95%, u32 const-eval overflow incorrectly rejected, f16 const-eval overflow not rejected, atomics #5474 webgpu:shader,validation,expression,binary,and_or_xor:* // 96%, https://github.com/gfx-rs/wgpu/issues/5474 webgpu:shader,validation,expression,binary,bitwise_shift:invalid_types:* // 93%, atomics #5474 webgpu:shader,validation,expression,binary,comparison:* // 74%, https://github.com/gfx-rs/wgpu/issues/5474 webgpu:shader,validation,expression,binary,div_rem:* // 86%, https://github.com/gfx-rs/wgpu/issues/5474 webgpu:shader,validation,expression,binary,short_circuiting_and_or:* // 92%, https://github.com/gfx-rs/wgpu/issues/8440 webgpu:shader,validation,expression,call,builtin,abs:* // 98%, atomic type validation gap, https://github.com/gfx-rs/wgpu/issues/5474 webgpu:shader,validation,expression,call,builtin,acosh:* // 56%, missing domain validation [1,∞) in const eval webgpu:shader,validation,expression,call,builtin,atomics:* // 86%, atomics in vertex shaders, invalid address spaces/access modes webgpu:shader,validation,expression,call,builtin,bitcast:* // 57%, bitcast const-eval unimplemented; size validation missing; f16 vector validation incorrect webgpu:shader,validation,expression,call,builtin,clamp:* // 71%, clamp low<=high constraint not checked for const/override parameters webgpu:shader,validation,expression,call,builtin,countLeadingZeros:* // 98%, atomic type validation gap, https://github.com/gfx-rs/wgpu/issues/5474 webgpu:shader,validation,expression,call,builtin,countOneBits:* // 98%, atomic type validation gap, https://github.com/gfx-rs/wgpu/issues/5474 webgpu:shader,validation,expression,call,builtin,countTrailingZeros:* // 98%, atomic type validation gap, https://github.com/gfx-rs/wgpu/issues/5474 webgpu:shader,validation,expression,call,builtin,cross:* // 86%, abstract int/float overflow issues in const eval webgpu:shader,validation,expression,call,builtin,derivatives:* // 83%, f16 support not properly validated webgpu:shader,validation,expression,call,builtin,determinant:* // 71%, abstract int/float const eval issues webgpu:shader,validation,expression,call,builtin,distance:* // 66%, scalar distance uses wrong formula (sqrt instead of abs) webgpu:shader,validation,expression,call,builtin,dot4I8Packed:* // 91%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,dot4U8Packed:* // 91%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,extractBits:* // 55%, const eval issues webgpu:shader,validation,expression,call,builtin,faceForward:* // 68%, const eval overflow for abstract/f16 webgpu:shader,validation,expression,call,builtin,firstLeadingBit:* // 98%, atomic type validation gap, https://github.com/gfx-rs/wgpu/issues/5474 webgpu:shader,validation,expression,call,builtin,firstTrailingBit:* // 98%, atomic type validation gap, https://github.com/gfx-rs/wgpu/issues/5474 webgpu:shader,validation,expression,call,builtin,fma:* // 85%, const eval issues webgpu:shader,validation,expression,call,builtin,frexp:* // 39%, const/override eval not supported webgpu:shader,validation,expression,call,builtin,insertBits:* // 73%, missing const eval support webgpu:shader,validation,expression,call,builtin,inverseSqrt:* // 61%, const eval overflow for abstract/f16 webgpu:shader,validation,expression,call,builtin,ldexp:* // 43%, const eval not implemented webgpu:shader,validation,expression,call,builtin,length:* // 74%, const eval overflow for abstract/f16 webgpu:shader,validation,expression,call,builtin,mix:* // 66%, const eval issues webgpu:shader,validation,expression,call,builtin,modf:* // 52%, const eval not fully implemented webgpu:shader,validation,expression,call,builtin,normalize:* // 63%, missing const/override eval overflow validation (intermediate values overflow to infinity) webgpu:shader,validation,expression,call,builtin,pack2x16float:* // 80%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,pack2x16snorm:* // 88%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,pack2x16unorm:* // 88%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,pack4x8snorm:* // 88%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,pack4x8unorm:* // 88%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,pack4xI8:* // 74%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,pack4xI8Clamp:* // 74%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,pack4xU8:* // 74%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,pack4xU8Clamp:* // 74%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,pow:* // 63%, missing const/override eval validation (negative base, zero^non-positive, overflow), http://github.com/gpuweb/issues/4527 webgpu:shader,validation,expression,call,builtin,quantizeToF16:* // 77%, overflow validation issues webgpu:shader,validation,expression,call,builtin,reflect:* // 39%, const eval not implemented webgpu:shader,validation,expression,call,builtin,refract:* // 44%, const eval not implemented webgpu:shader,validation,expression,call,builtin,reverseBits:* // 98%, atomic type validation gap, https://github.com/gfx-rs/wgpu/issues/5474 webgpu:shader,validation,expression,call,builtin,select:* // >99%, https://github.com/gfx-rs/wgpu/issues/5474 webgpu:shader,validation,expression,call,builtin,smoothstep:* // 69%, const eval issues webgpu:shader,validation,expression,call,builtin,textureDimensions:* // >99%, no external texture in deno webgpu:shader,validation,expression,call,builtin,textureGather:* // 99%, https://github.com/gfx-rs/wgpu/issues/8876 webgpu:shader,validation,expression,call,builtin,textureGatherCompare:* // 99%, edge case in offset or external texture validation webgpu:shader,validation,expression,call,builtin,textureLoad:* // 99%, texture_external not implemented webgpu:shader,validation,expression,call,builtin,textureSample:* // 99%, texture_external webgpu:shader,validation,expression,call,builtin,textureSampleBaseClampToEdge:* // 96%, texture_external webgpu:shader,validation,expression,call,builtin,textureSampleBias:* // 99%, missing offset validation (range & cube texture) webgpu:shader,validation,expression,call,builtin,textureSampleCompare:* // 99%, missing offset range validation (-8 to +7) webgpu:shader,validation,expression,call,builtin,textureSampleCompareLevel:* // 99%, missing offset range validation (-8 to +7) webgpu:shader,validation,expression,call,builtin,textureSampleGrad:* // 99%, missing offset range validation + depth textures incorrectly accepted webgpu:shader,validation,expression,call,builtin,textureSampleLevel:* // 99%, missing offset validation (cube texture & value range) webgpu:shader,validation,expression,call,builtin,transpose:* // 86%, missing const eval webgpu:shader,validation,expression,call,builtin,unpack2x16float:* // 81%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,unpack2x16snorm:* // 81%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,unpack2x16unorm:* // 81%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,unpack4x8snorm:* // 81%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,unpack4x8unorm:* // 81%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,unpack4xI8:* // 75%, missing const eval (#4507) webgpu:shader,validation,expression,call,builtin,unpack4xU8:* // 75%, missing const eval (#4507) webgpu:shader,validation,expression,early_evaluation:* // 67%, mixed override/runtime composite evaluation webgpu:shader,validation,expression,matrix,* // 99%, #5474, #8868, atomic webgpu:shader,validation,expression,precedence:* // 76%, https://github.com/gfx-rs/wgpu/issues/4536 webgpu:shader,validation,expression,unary,* // 99%, atomics #5474 webgpu:shader,validation,extension,dual_source_blending:blend_src_usage:* // 61%, @blend_src validation gaps webgpu:shader,validation,extension,readonly_and_readwrite_storage_textures:* // 0%, CTS bug https://github.com/gpuweb/cts/pull/4567 webgpu:shader,validation,functions,alias_analysis:* // 2%, https://github.com/gfx-rs/wgpu/issues/7650 webgpu:shader,validation,functions,restrictions:* // 81%, texture_external webgpu:shader,validation,parse,attribute:* // 93%, group index validation at shader module creation webgpu:shader,validation,parse,blankspace:* // 95%, null in comments, https://github.com/gfx-rs/wgpu/issues/8877 webgpu:shader,validation,parse,comments:* // 93%, unterminated block comments, https://github.com/gfx-rs/wgpu/issues/8877 webgpu:shader,validation,parse,diagnostic:* // 50%, https://github.com/gfx-rs/wgpu/issues/6458 webgpu:shader,validation,parse,literal:* // 96%, https://github.com/gfx-rs/wgpu/issues/7046 webgpu:shader,validation,parse,must_use:* // 97%, https://github.com/gfx-rs/wgpu/issues/8876 webgpu:shader,validation,parse,shadow_builtins:* // 83%, function param shadowing parser issue; determinant const-eval; texture_external capability missing webgpu:shader,validation,shader_io,align:* // 98%, https://github.com/gfx-rs/wgpu/issues/8892 webgpu:shader,validation,shader_io,binding:* // 95%, https://github.com/gfx-rs/wgpu/issues/8892 webgpu:shader,validation,shader_io,builtins:* // 50%, trailing comma not accepted webgpu:shader,validation,shader_io,group:* // 95%, https://github.com/gfx-rs/wgpu/issues/8892 webgpu:shader,validation,shader_io,id:* // 94%, https://github.com/gfx-rs/wgpu/issues/8892, @id on const webgpu:shader,validation,shader_io,interpolate:* // 91%, https://github.com/gfx-rs/wgpu/issues/8892 webgpu:shader,validation,shader_io,layout_constraints:* // 99%, struct alignment not inferred from @align on members webgpu:shader,validation,shader_io,locations:stage_inout:* // 66%, invalid usage @location in compute shaders webgpu:shader,validation,shader_io,pipeline_stage:* // 92%, stage attributes incorrectly accepted on var and var webgpu:shader,validation,shader_io,size:* // 92%, https://github.com/gfx-rs/wgpu/issues/8892, large size validation, runtime-sized array webgpu:shader,validation,shader_io,workgroup_size:* // 83%, https://github.com/gfx-rs/wgpu/issues/8892, type mixing, attribute placement webgpu:shader,validation,statement,continue:* // 90%, continue bypassing declaration used in continuing block, https://github.com/gfx-rs/wgpu/issues/7650 webgpu:shader,validation,statement,for:* // 93%, phony/increment in for-loop init/cont, empty loop behavior webgpu:shader,validation,statement,increment_decrement:* // 98%, atomic type validation gap, https://github.com/gfx-rs/wgpu/issues/5474 webgpu:shader,validation,statement,loop:* // 92%, https://github.com/gfx-rs/wgpu/issues/7650 webgpu:shader,validation,statement,phony:* // 90%, phony assignment in for-loops with semicolons webgpu:shader,validation,statement,statement_behavior:* // https://github.com/gfx-rs/wgpu/issues/7650 webgpu:shader,validation,statement,switch:* // https://github.com/gfx-rs/wgpu/issues/7650 webgpu:shader,validation,types,* // 95%, texture_external not supported (2 tests), atomic validation gaps (8 tests), pointer validation gaps (5 tests), 16-bit normalized storage texture formats (36 tests) webgpu:shader,validation,uniformity,* // 21%, https://github.com/gfx-rs/wgpu/issues/4369 ================================================ FILE: cts_runner/revision.txt ================================================ e9adcf85f5d3698da4dbd7d0e2de0ef0350c3d90 ================================================ FILE: cts_runner/skip.lst ================================================ // CTS test selectors that are entirely or nearly entirely skipped. // // The `cts_runner` integration test `lst_files_are_sorted` verifies that test selectors // appear in this file in order -- including ones in comments. webgpu:api,validation,createPipelineLayout:immediate_data_size:* // immediates, https://github.com/gfx-rs/wgpu/issues/8556 webgpu:api,validation,encoding,programmable,pipeline_immediate:* // immediates, https://github.com/gfx-rs/wgpu/issues/8556 webgpu:api,validation,gpu_external_texture_expiration:* // external texture webgpu:api,validation,pipeline,immediates:pipeline_creation_immediate_size_mismatch:* // immediates, https://github.com/gfx-rs/wgpu/issues/8556 webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:* // external texture webgpu:shader,validation,expression,call,builtin,quadBroadcast:* // subgroups, https://github.com/gfx-rs/wgpu/issues/8722 webgpu:shader,validation,expression,call,builtin,quadSwap:* // ibid. webgpu:shader,validation,expression,call,builtin,subgroupAdd:* // ibid. webgpu:shader,validation,expression,call,builtin,subgroupAnyAll:* // ibid. webgpu:shader,validation,expression,call,builtin,subgroupBallot:* // ibid. webgpu:shader,validation,expression,call,builtin,subgroupBitwise:* // ibid. webgpu:shader,validation,expression,call,builtin,subgroupBroadcast:* // ibid. webgpu:shader,validation,expression,call,builtin,subgroupBroadcastFirst:* // ibid. webgpu:shader,validation,expression,call,builtin,subgroupElect:* // ibid. webgpu:shader,validation,expression,call,builtin,subgroupMinMax:* // ibid. webgpu:shader,validation,expression,call,builtin,subgroupMul:* // ibid. webgpu:shader,validation,expression,call,builtin,subgroupShuffle:* // ibid. webgpu:shader,validation,statement,swizzle_assignment:* // swizzle assignment, https://github.com/gfx-rs/wgpu/issues/9159 ================================================ FILE: cts_runner/src/bootstrap.js ================================================ // Adapted from https://github.com/denoland/deno/blob/6abf126c2a7a451cded8c6b5e6ddf1b69c84055d/runtime/js/99_main.js // Removes the `__proto__` for security reasons. This intentionally makes // Deno non compliant with ECMA-262 Annex B.2.2.1 // delete Object.prototype.__proto__; import { core, primordials } from "ext:core/mod.js"; const { Error, ObjectDefineProperty, ObjectDefineProperties, ObjectSetPrototypeOf, Symbol, DateNow, } = primordials; import { pathFromURL } from "ext:deno_web/00_infra.js"; import * as webidl from "ext:deno_webidl/00_webidl.js"; import * as globalInterfaces from "ext:deno_web/04_global_interfaces.js"; import * as event from "ext:deno_web/02_event.js"; import * as timers from "ext:deno_web/02_timers.js"; import * as base64 from "ext:deno_web/05_base64.js"; import * as encoding from "ext:deno_web/08_text_encoding.js"; import { Console } from "ext:deno_console/01_console.js"; import * as url from "ext:deno_url/00_url.js"; import { DOMException } from "ext:deno_web/01_dom_exception.js"; import * as performance from "ext:deno_web/15_performance.js"; import { loadWebGPU } from "ext:deno_webgpu/00_init.js"; import * as imageData from "ext:deno_web/16_image_data.js"; const webgpu = loadWebGPU(); webgpu.initGPU(); // imports needed to pass module evaluation import "ext:deno_url/01_urlpattern.js"; import "ext:deno_web/01_mimesniff.js"; import "ext:deno_web/03_abort_signal.js"; import "ext:deno_web/06_streams.js"; import "ext:deno_web/09_file.js"; import "ext:deno_web/10_filereader.js"; import "ext:deno_web/12_location.js"; import "ext:deno_web/13_message_port.js"; import "ext:deno_web/14_compression.js"; import "ext:deno_webgpu/02_surface.js"; let globalThis_; class NotFound extends Error { constructor(msg) { super(msg); this.name = "NotFound"; } } class BrokenPipe extends Error { constructor(msg) { super(msg); this.name = "BrokenPipe"; } } class AlreadyExists extends Error { constructor(msg) { super(msg); this.name = "AlreadyExists"; } } class InvalidData extends Error { constructor(msg) { super(msg); this.name = "InvalidData"; } } class TimedOut extends Error { constructor(msg) { super(msg); this.name = "TimedOut"; } } class WriteZero extends Error { constructor(msg) { super(msg); this.name = "WriteZero"; } } class UnexpectedEof extends Error { constructor(msg) { super(msg); this.name = "UnexpectedEof"; } } class NotSupported extends Error { constructor(msg) { super(msg); this.name = "NotSupported"; } } const util = { writable(value) { return { value, writable: true, enumerable: true, configurable: true, }; }, nonEnumerable(value) { return { value, writable: true, configurable: true, }; }, readOnly(value) { return { value, enumerable: true, }; }, getterOnly(getter) { return { get: getter, set() { }, enumerable: true, configurable: true, }; }, }; class Navigator { constructor() { webidl.illegalConstructor(); } } const NavigatorPrototype = Navigator.prototype; const navigator = webidl.createBranded(Navigator); ObjectDefineProperties(NavigatorPrototype, { gpu: { configurable: true, enumerable: true, get() { webidl.assertBranded(this, NavigatorPrototype); return webgpu.gpu; }, }, }); const windowOrWorkerGlobalScope = { CloseEvent: util.nonEnumerable(event.CloseEvent), CustomEvent: util.nonEnumerable(event.CustomEvent), DOMException: util.nonEnumerable(DOMException), ErrorEvent: util.nonEnumerable(event.ErrorEvent), Event: util.nonEnumerable(event.Event), EventTarget: util.nonEnumerable(event.EventTarget), Navigator: util.nonEnumerable(Navigator), navigator: util.getterOnly(() => navigator), MessageEvent: util.nonEnumerable(event.MessageEvent), Performance: util.nonEnumerable(performance.Performance), PerformanceEntry: util.nonEnumerable(performance.PerformanceEntry), PerformanceMark: util.nonEnumerable(performance.PerformanceMark), PerformanceMeasure: util.nonEnumerable(performance.PerformanceMeasure), TextDecoder: util.nonEnumerable(encoding.TextDecoder), TextEncoder: util.nonEnumerable(encoding.TextEncoder), URL: util.nonEnumerable(url.URL), URLSearchParams: util.nonEnumerable(url.URLSearchParams), atob: util.writable(base64.atob), btoa: util.writable(base64.btoa), console: util.writable(new Console(core.print)), setInterval: util.writable(timers.setInterval), setTimeout: util.writable(timers.setTimeout), clearInterval: util.writable(timers.clearInterval), clearTimeout: util.writable(timers.clearTimeout), performance: util.writable(performance.performance), ImageData: core.propNonEnumerable(imageData.ImageData), GPU: util.nonEnumerable(webgpu.GPU), GPUAdapter: util.nonEnumerable(webgpu.GPUAdapter), GPUAdapterInfo: util.nonEnumerable(webgpu.GPUAdapterInfo), GPUSupportedLimits: util.nonEnumerable(webgpu.GPUSupportedLimits), GPUSupportedFeatures: util.nonEnumerable(webgpu.GPUSupportedFeatures), GPUDeviceLostInfo: util.nonEnumerable(webgpu.GPUDeviceLostInfo), GPUDevice: util.nonEnumerable(webgpu.GPUDevice), GPUQueue: util.nonEnumerable(webgpu.GPUQueue), GPUBuffer: util.nonEnumerable(webgpu.GPUBuffer), GPUBufferUsage: util.nonEnumerable(webgpu.GPUBufferUsage), GPUMapMode: util.nonEnumerable(webgpu.GPUMapMode), GPUTextureUsage: util.nonEnumerable(webgpu.GPUTextureUsage), GPUTexture: util.nonEnumerable(webgpu.GPUTexture), GPUTextureView: util.nonEnumerable(webgpu.GPUTextureView), GPUExternalTexture: util.nonEnumerable(webgpu.GPUExternalTexture), GPUSampler: util.nonEnumerable(webgpu.GPUSampler), GPUBindGroupLayout: util.nonEnumerable(webgpu.GPUBindGroupLayout), GPUPipelineError: util.nonEnumerable(webgpu.GPUPipelineError), GPUPipelineLayout: util.nonEnumerable(webgpu.GPUPipelineLayout), GPUBindGroup: util.nonEnumerable(webgpu.GPUBindGroup), GPUCompilationInfo: util.nonEnumerable(webgpu.GPUCompilationInfo), GPUCompilationMessage: util.nonEnumerable(webgpu.GPUCompilationMessage), GPUShaderModule: util.nonEnumerable(webgpu.GPUShaderModule), GPUShaderStage: util.nonEnumerable(webgpu.GPUShaderStage), GPUComputePipeline: util.nonEnumerable(webgpu.GPUComputePipeline), GPURenderPipeline: util.nonEnumerable(webgpu.GPURenderPipeline), GPUColorWrite: util.nonEnumerable(webgpu.GPUColorWrite), GPUCommandEncoder: util.nonEnumerable(webgpu.GPUCommandEncoder), GPURenderPassEncoder: util.nonEnumerable(webgpu.GPURenderPassEncoder), GPUComputePassEncoder: util.nonEnumerable(webgpu.GPUComputePassEncoder), GPUCommandBuffer: util.nonEnumerable(webgpu.GPUCommandBuffer), GPURenderBundleEncoder: util.nonEnumerable(webgpu.GPURenderBundleEncoder), GPURenderBundle: util.nonEnumerable(webgpu.GPURenderBundle), GPUQuerySet: util.nonEnumerable(webgpu.GPUQuerySet), GPUError: util.nonEnumerable(webgpu.GPUError), GPUInternalError: util.nonEnumerable(webgpu.GPUInternalError), GPUValidationError: util.nonEnumerable(webgpu.GPUValidationError), GPUOutOfMemoryError: util.nonEnumerable(webgpu.GPUOutOfMemoryError), GPUUncapturedErrorEvent: util.nonEnumerable(webgpu.GPUUncapturedErrorEvent), }; windowOrWorkerGlobalScope.console.enumerable = false; // Print uncaptured WebGPU errors to stderr. This is useful when running // standalone JavaScript test snippets. It isn't needed for the CTS, because the // CTS uses error scopes. (The CTS also installs its own error handler with // `addEventListener`, so having this here may result in printing duplicate // errors from the CTS in some cases.) Printing uncaptured errors to stderr // isn't desired as built-in behavior in Deno, because the console is reserved // for the application. // // Note that catching an error here _does not_ result in a non-zero exit status. let enableExternalTexture_ = false; const requestDevice = webgpu.GPUAdapter.prototype.requestDevice; webgpu.GPUAdapter.prototype.requestDevice = function(desc) { if (enableExternalTexture_) { // Deno doesn't meaningfully support external textures, but we provide // an option to enable it anyways to allow running some CTS tests that // do pass. if (!desc) { desc = { requiredFeatures: ['wgpu-external-texture'] }; } else if (!desc.requiredFeatures) { desc.requiredFeatures = ['wgpu-external-texture']; } else { desc.requiredFeatures.push('wgpu-external-texture'); } } return requestDevice.call(this, desc).then((device) => { device.onuncapturederror = (event) => { core.print("cts_runner caught WebGPU error: " + event.error.message + "\n", true); }; return device; }) }; const mainRuntimeGlobalProperties = { Window: globalInterfaces.windowConstructorDescriptor, window: util.readOnly(globalThis), self: util.readOnly(globalThis), }; const denoNs = { exit(code) { core.ops.op_exit(code); }, readFileSync(path) { return core.ops.op_read_file_sync(pathFromURL(path)); }, readTextFileSync(path) { const buf = core.ops.op_read_file_sync(pathFromURL(path)); const decoder = new TextDecoder(); return decoder.decode(buf); }, writeFileSync(path, buf) { return core.ops.op_write_file_sync(pathFromURL(path), buf); }, }; core.registerErrorClass("NotFound", NotFound); core.registerErrorClass("AlreadyExists", AlreadyExists); core.registerErrorClass("InvalidData", InvalidData); core.registerErrorClass("TimedOut", TimedOut); core.registerErrorClass("WriteZero", WriteZero); core.registerErrorClass("UnexpectedEof", UnexpectedEof); core.registerErrorClass("NotSupported", NotSupported); core.registerErrorBuilder( "DOMExceptionOperationError", function DOMExceptionOperationError(msg) { return new DOMException(msg, "OperationError"); }, ); core.registerErrorBuilder( "DOMExceptionAbortError", function DOMExceptionAbortError(msg) { return new domException.DOMException(msg, "AbortError"); }, ); core.registerErrorBuilder( "DOMExceptionInvalidCharacterError", function DOMExceptionInvalidCharacterError(msg) { return new domException.DOMException(msg, "InvalidCharacterError"); }, ); core.registerErrorBuilder( "DOMExceptionDataError", function DOMExceptionDataError(msg) { return new domException.DOMException(msg, "DataError"); }, ); let hasBootstrapped = false; function bootstrapRuntime({ args, cwd, enableExternalTexture = false }) { if (hasBootstrapped) { throw new Error("Runtime has already been bootstrapped."); } performance.setTimeOrigin(DateNow()); globalThis_ = globalThis; enableExternalTexture_ = enableExternalTexture; // Remove bootstrapping data from the global scope delete globalThis.__bootstrap; delete globalThis.bootstrap; hasBootstrapped = true; event.setEventTargetData(globalThis); event.saveGlobalThisReference(globalThis); Error.prepareStackTrace = core.prepareStackTrace; ObjectDefineProperties(globalThis, windowOrWorkerGlobalScope); ObjectDefineProperties(globalThis, mainRuntimeGlobalProperties); ObjectSetPrototypeOf(globalThis, Window.prototype); event.setEventTargetData(globalThis); denoNs.args = args; denoNs.cwd = () => cwd; ObjectDefineProperty(globalThis, "Deno", util.readOnly(denoNs)); Error.prepareStackTrace = core.prepareStackTrace; } globalThis.bootstrap = bootstrapRuntime; ================================================ FILE: cts_runner/src/main.rs ================================================ #![cfg_attr(target_arch = "wasm32", no_main)] #![cfg(not(target_arch = "wasm32"))] use std::sync::Arc; use std::{ env, fmt, io::{Read, Write}, rc::Rc, }; use deno_core::anyhow::anyhow; use deno_core::error::AnyError; use deno_core::op2; use deno_core::resolve_url_or_path; use deno_core::serde_json::json; use deno_core::v8; use deno_core::JsRuntime; use deno_core::RuntimeOptions; use deno_web::BlobStore; use termcolor::Ansi; use termcolor::Color::Red; use termcolor::ColorSpec; use termcolor::WriteColor; pub async fn run() -> Result<(), AnyError> { let mut args = pico_args::Arguments::from_env(); let enable_external_texture = args.contains("--enable-external-texture"); let url = args .subcommand() .ok() .flatten() .ok_or_else(|| anyhow!("missing specifier in first command line argument"))?; let specifier = resolve_url_or_path(&url, &env::current_dir()?)?; #[cfg(target_os = "windows")] match env::var(deno_webgpu::DX12_COMPILER_ENV_VAR) { Ok(val) => { log::info!( "Environment variable `{}` is set to `{val}`.", deno_webgpu::DX12_COMPILER_ENV_VAR, ); } Err(_) => { log::info!( "cts_runner uses DXC by default. Configure with `{}` environment variable.", deno_webgpu::DX12_COMPILER_ENV_VAR ); unsafe { // SAFETY: Both of the following conditions apply; either is sufficient. // 1. Calling `env::set_var` is always safe on Windows. // 2. We are single-threaded at this point. env::set_var(deno_webgpu::DX12_COMPILER_ENV_VAR, "dynamicdxc"); } } } let options = RuntimeOptions { module_loader: Some(Rc::new(deno_core::FsModuleLoader)), extensions: vec![ deno_webidl::deno_webidl::init(), deno_console::deno_console::init(), deno_url::deno_url::init(), deno_web::deno_web::init::(Arc::new(BlobStore::default()), None), deno_webgpu::deno_webgpu::init(), cts_runner::init(), ], ..Default::default() }; let mut js_runtime = JsRuntime::new(options); let args = args .finish() .into_iter() .map(|os| os.into_string().ok()) .collect::>>() .ok_or_else(|| anyhow!("Invalid UTF-8 in arguments"))?; let cfg = json!({ "args": args, "cwd": env::current_dir().unwrap().to_string_lossy(), "enableExternalTexture": enable_external_texture, }); { let context = js_runtime.main_context(); let scope = &mut js_runtime.handle_scope(); let context_local = v8::Local::new(scope, context); let global_obj = context_local.global(scope); let bootstrap_str = v8::String::new(scope, "bootstrap").unwrap(); let bootstrap_fn = global_obj.get(scope, bootstrap_str.into()).unwrap(); let bootstrap_fn = v8::Local::::try_from(bootstrap_fn).unwrap(); let options_v8 = deno_core::serde_v8::to_v8(scope, cfg).unwrap(); let undefined = v8::undefined(scope); bootstrap_fn .call(scope, undefined.into(), &[options_v8]) .unwrap(); } let mod_id = js_runtime.load_main_es_module(&specifier).await?; let result = js_runtime.mod_evaluate(mod_id); js_runtime.run_event_loop(Default::default()).await?; result.await?; Ok(()) } deno_core::extension!( cts_runner, deps = [deno_webidl, deno_web], ops = [op_exit, op_read_file_sync, op_write_file_sync], esm_entry_point = "ext:cts_runner/src/bootstrap.js", esm = ["src/bootstrap.js"], state = |state| { let mut feature_checker = deno_features::FeatureChecker::default(); feature_checker.enable_feature(deno_webgpu::UNSTABLE_FEATURE_NAME); state.put(feature_checker); state.put(Permissions {}); } ); #[op2(fast)] fn op_exit(code: i32) { std::process::exit(code) } #[op2] #[buffer] fn op_read_file_sync(#[string] path: &str) -> Result, std::io::Error> { let path = std::path::Path::new(path); let mut file = std::fs::File::open(path)?; let mut buf = Vec::new(); file.read_to_end(&mut buf)?; Ok(buf) } #[op2(fast)] fn op_write_file_sync(#[string] path: &str, #[buffer] buf: &[u8]) -> Result<(), std::io::Error> { let path = std::path::Path::new(path); let mut file = std::fs::File::create(path)?; file.write_all(buf)?; Ok(()) } pub fn unwrap_or_exit(result: Result) -> T { match result { Ok(value) => value, Err(error) => { eprintln!("{}: {:?}", red_bold("error"), error); std::process::exit(1); } } } fn style>(s: S, colorspec: ColorSpec) -> impl fmt::Display { let mut v = Vec::new(); let mut ansi_writer = Ansi::new(&mut v); ansi_writer.set_color(&colorspec).unwrap(); ansi_writer.write_all(s.as_ref().as_bytes()).unwrap(); ansi_writer.reset().unwrap(); String::from_utf8_lossy(&v).into_owned() } fn red_bold>(s: S) -> impl fmt::Display { let mut style_spec = ColorSpec::new(); style_spec.set_fg(Some(Red)).set_bold(true); style(s, style_spec) } // NOP permissions struct Permissions; impl deno_web::TimersPermission for Permissions { fn allow_hrtime(&mut self) -> bool { false } } #[tokio::main(flavor = "current_thread")] async fn main() { env_logger::init(); unwrap_or_exit(run().await) } ================================================ FILE: cts_runner/test.lst ================================================ // CTS test selectors that are expected to pass. These are run in CI. // // The following may be useful to generate lists of tests for this file: // ``` // cargo xtask cts -- --list // ``` // // The `cts_runner` integration test `lst_files_are_sorted` verifies that test selectors // appear in this file in order -- including ones in comments. unittests:* webgpu:api,operation,adapter,requestAdapter:* webgpu:api,operation,buffers,createBindGroup:buffer_binding_resource:* webgpu:api,operation,command_buffer,basic:* webgpu:api,operation,command_buffer,copyBufferToBuffer:* fails-if(vulkan) webgpu:api,operation,command_buffer,copyTextureToTexture:copy_depth_stencil:format="depth16unorm" fails-if(vulkan) webgpu:api,operation,command_buffer,copyTextureToTexture:copy_depth_stencil:format="depth24plus" fails-if(vulkan) webgpu:api,operation,command_buffer,copyTextureToTexture:copy_depth_stencil:format="depth24plus-stencil8" fails-if(vulkan) webgpu:api,operation,command_buffer,copyTextureToTexture:copy_depth_stencil:format="depth32float" fails-if(vulkan) webgpu:api,operation,command_buffer,copyTextureToTexture:copy_depth_stencil:format="depth32float-stencil8" webgpu:api,operation,command_buffer,copyTextureToTexture:copy_depth_stencil:format="stencil8" fails-if(dx12) webgpu:api,operation,command_buffer,image_copy:offsets_and_sizes:* webgpu:api,operation,command_buffer,image_copy:undefined_params:initMethod="CopyB2T";checkMethod="FullCopyT2B";dimension="1d" webgpu:api,operation,command_buffer,image_copy:undefined_params:initMethod="CopyB2T";checkMethod="FullCopyT2B";dimension="2d" fails-if(dx12,vulkan,metal) webgpu:api,operation,command_buffer,image_copy:undefined_params:initMethod="CopyB2T";checkMethod="FullCopyT2B";dimension="3d" webgpu:api,operation,command_buffer,image_copy:undefined_params:initMethod="WriteTexture";checkMethod="FullCopyT2B";dimension="1d" webgpu:api,operation,command_buffer,image_copy:undefined_params:initMethod="WriteTexture";checkMethod="FullCopyT2B";dimension="2d" webgpu:api,operation,command_buffer,image_copy:undefined_params:initMethod="WriteTexture";checkMethod="FullCopyT2B";dimension="3d" webgpu:api,operation,command_buffer,image_copy:undefined_params:initMethod="WriteTexture";checkMethod="PartialCopyT2B";dimension="1d" fails-if(dx12) webgpu:api,operation,command_buffer,image_copy:undefined_params:initMethod="WriteTexture";checkMethod="PartialCopyT2B";dimension="2d" webgpu:api,operation,command_buffer,image_copy:undefined_params:initMethod="WriteTexture";checkMethod="PartialCopyT2B";dimension="3d" webgpu:api,operation,compute_pipeline,overrides:* //FAIL: webgpu:api,operation,compute,basic:large_dispatch:* webgpu:api,operation,compute,basic:memcpy:* webgpu:api,operation,device,lost:* webgpu:api,operation,render_pass,storeOp:* webgpu:api,operation,render_pipeline,overrides:* webgpu:api,operation,render_pipeline,pipeline_output_targets:color,component_count,blend:* webgpu:api,operation,rendering,3d_texture_slices:* webgpu:api,operation,rendering,basic:clear:* webgpu:api,operation,rendering,basic:fullscreen_quad:* //FAIL: webgpu:api,operation,rendering,basic:large_draw:* webgpu:api,operation,rendering,color_target_state:blend_constant,setting:* webgpu:api,operation,rendering,color_target_state:blending,formats:* webgpu:api,operation,rendering,color_target_state:blending,GPUBlendComponent:* webgpu:api,operation,rendering,depth:* webgpu:api,operation,rendering,draw:* webgpu:api,operation,shader_module,compilation_info:* webgpu:api,operation,uncapturederror:iff_uncaptured:* //FAIL: webgpu:api,operation,uncapturederror:onuncapturederror_order_wrt_addEventListener // There are also two unimplemented SKIPs in uncapturederror not enumerated here. fails-if(vulkan) webgpu:api,operation,vertex_state,correctness:array_stride_zero:* webgpu:api,operation,vertex_state,correctness:non_zero_array_stride_and_attribute_offset:* webgpu:api,operation,vertex_state,correctness:setVertexBuffer_offset_and_attribute_offset:* webgpu:api,validation,buffer,create:* webgpu:api,validation,buffer,destroy:* webgpu:api,validation,capability_checks,features,clip_distances:* webgpu:api,validation,capability_checks,limits,maxBindGroups:setBindGroup,* webgpu:api,validation,capability_checks,limits,maxBindingsPerBindGroup:validate,* webgpu:api,validation,capability_checks,limits,maxBufferSize:* webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupSizeX:validate,* webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupSizeY:validate,* webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupSizeZ:validate,* webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupsPerDimension:validate,* //FAIL: other `maxInterStageShaderVariables` cases w/ limitTest ∈ [underDefault, overMaximum] // https://github.com/gfx-rs/wgpu/issues/8945 webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";* webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atMaximum";* webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="betweenDefaultAndMaximum";* webgpu:api,validation,capability_checks,limits,maxStorageBufferBindingSize:validate,* webgpu:api,validation,capability_checks,limits,maxUniformBufferBindingSize:validate,* webgpu:api,validation,capability_checks,limits,maxVertexBufferArrayStride:validate,* webgpu:api,validation,capability_checks,limits,minStorageBufferOffsetAlignment:validate,* webgpu:api,validation,capability_checks,limits,minUniformBufferOffsetAlignment:validate,* webgpu:api,validation,compute_pipeline:basic:* webgpu:api,validation,compute_pipeline:limits,invocations_per_workgroup,each_component:* webgpu:api,validation,compute_pipeline:limits,invocations_per_workgroup:* webgpu:api,validation,compute_pipeline:overrides,entry_point,validation_error:* webgpu:api,validation,compute_pipeline:overrides,identifier:* webgpu:api,validation,compute_pipeline:overrides,uninitialized:* webgpu:api,validation,compute_pipeline:overrides,value,type_error:* webgpu:api,validation,compute_pipeline:overrides,value,validation_error,f16:* webgpu:api,validation,compute_pipeline:overrides,value,validation_error:* webgpu:api,validation,compute_pipeline:overrides,workgroup_size:* webgpu:api,validation,compute_pipeline:pipeline_layout,device_mismatch:* webgpu:api,validation,compute_pipeline:resource_compatibility:* webgpu:api,validation,compute_pipeline:shader_module,* webgpu:api,validation,compute_pipeline:storage_texture,format:* webgpu:api,validation,createBindGroup:bind_group_layout,device_mismatch:* webgpu:api,validation,createBindGroup:binding_count_mismatch:* webgpu:api,validation,createBindGroup:binding_must_be_present_in_layout:* webgpu:api,validation,createBindGroup:binding_must_contain_resource_defined_in_layout:* webgpu:api,validation,createBindGroup:binding_resources,device_mismatch:* webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:* webgpu:api,validation,createBindGroup:buffer,effective_buffer_binding_size:* webgpu:api,validation,createBindGroup:buffer,resource_binding_size:* webgpu:api,validation,createBindGroup:buffer,resource_offset:* webgpu:api,validation,createBindGroup:buffer,usage:* webgpu:api,validation,createBindGroup:minBindingSize:* webgpu:api,validation,createBindGroup:multisampled_validation:* webgpu:api,validation,createBindGroup:sampler,* webgpu:api,validation,createBindGroup:storage_texture,* webgpu:api,validation,createBindGroup:texture_binding_must_have_correct_usage:* webgpu:api,validation,createBindGroup:texture_must_have_correct_component_type:* webgpu:api,validation,createBindGroup:texture_must_have_correct_dimension:* webgpu:api,validation,createBindGroupLayout:duplicate_bindings:* webgpu:api,validation,createBindGroupLayout:max_dynamic_buffers:* // Doesn't actually fail, but is very slow. https://github.com/gfx-rs/wgpu/issues/9229 fails-if(dx12) webgpu:api,validation,createBindGroupLayout:max_resources_per_stage,* webgpu:api,validation,createBindGroupLayout:maximum_binding_limit:* webgpu:api,validation,createBindGroupLayout:multisampled_validation:* webgpu:api,validation,createBindGroupLayout:storage_texture,formats:* webgpu:api,validation,createBindGroupLayout:storage_texture,layout_dimension:* webgpu:api,validation,createPipelineLayout:* webgpu:api,validation,createSampler:* webgpu:api,validation,createTexture:* webgpu:api,validation,createView:array_layers:* webgpu:api,validation,createView:aspect:* webgpu:api,validation,createView:cube_faces_square:* webgpu:api,validation,createView:dimension:* webgpu:api,validation,createView:format:* webgpu:api,validation,createView:mip_levels:* webgpu:api,validation,createView:texture_view_usage_with_view_format:* webgpu:api,validation,createView:texture_view_usage:* webgpu:api,validation,debugMarker:* webgpu:api,validation,encoding,beginComputePass:* webgpu:api,validation,encoding,beginRenderPass:* webgpu:api,validation,encoding,cmds,clearBuffer:* webgpu:api,validation,encoding,cmds,compute_pass:* webgpu:api,validation,encoding,cmds,copyBufferToBuffer:* webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_aspects:* webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_ranges_with_compressed_texture_formats:* // Intermittently fails on dx12, https://github.com/gfx-rs/wgpu/issues/8118 fails-if(dx12) webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_ranges:* webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_with_invalid_or_destroyed_texture:* webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_within_same_texture:* webgpu:api,validation,encoding,cmds,copyTextureToTexture:depth_stencil_copy_restrictions:* webgpu:api,validation,encoding,cmds,copyTextureToTexture:mipmap_level:* webgpu:api,validation,encoding,cmds,copyTextureToTexture:multisampled_copy_restrictions:* webgpu:api,validation,encoding,cmds,copyTextureToTexture:sample_count:* webgpu:api,validation,encoding,cmds,copyTextureToTexture:texture_format_compatibility:* webgpu:api,validation,encoding,cmds,copyTextureToTexture:texture_usage:* webgpu:api,validation,encoding,cmds,copyTextureToTexture:texture,device_mismatch:* webgpu:api,validation,encoding,cmds,debug:debug_group_balanced:encoderType="compute%20pass" webgpu:api,validation,encoding,cmds,debug:debug_group_balanced:encoderType="non-pass" //FAIL: webgpu:api,validation,encoding,cmds,debug:debug_group_balanced:encoderType="render%20bundle" // https://github.com/gfx-rs/wgpu/issues/8039 webgpu:api,validation,encoding,cmds,debug:debug_group_balanced:encoderType="render%20pass" webgpu:api,validation,encoding,cmds,debug:debug_group:* webgpu:api,validation,encoding,cmds,debug:debug_marker:* webgpu:api,validation,encoding,cmds,index_access:* webgpu:api,validation,encoding,cmds,render,draw:index_buffer_OOB:* webgpu:api,validation,encoding,cmds,render,draw:unused_buffer_bound:* webgpu:api,validation,encoding,cmds,render,dynamic_state:* webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_buffer_usage:* webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_buffer,device_mismatch:* webgpu:api,validation,encoding,cmds,render,setIndexBuffer:* webgpu:api,validation,encoding,cmds,render,setPipeline:* webgpu:api,validation,encoding,cmds,render,setVertexBuffer:* webgpu:api,validation,encoding,cmds,render,state_tracking:vertex_buffers_do_not_inherit_between_render_passes:* webgpu:api,validation,encoding,cmds,render,state_tracking:vertex_buffers_inherit_from_previous_pipeline:* webgpu:api,validation,encoding,cmds,setBindGroup:bind_group,device_mismatch:* webgpu:api,validation,encoding,cmds,setBindGroup:buffer_dynamic_offsets:* webgpu:api,validation,encoding,cmds,setBindGroup:dynamic_offsets_passed_but_not_expected:* webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="compute%20pass";state="invalid";* webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="compute%20pass";state="valid";* webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="render%20bundle";state="invalid";* webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="render%20bundle";state="valid";* webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="render%20pass";state="invalid";* webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="render%20pass";state="valid";* webgpu:api,validation,encoding,encoder_open_state:compute_pass_commands:* webgpu:api,validation,encoding,encoder_open_state:non_pass_commands:* //FAIL: webgpu:api,validation,encoding,encoder_open_state:render_bundle_commands:* // https://github.com/gfx-rs/wgpu/issues/7857 webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:* webgpu:api,validation,encoding,encoder_state:* webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:* webgpu:api,validation,encoding,queries,begin_end:nesting:* webgpu:api,validation,encoding,queries,begin_end:occlusion_query,* webgpu:api,validation,encoding,queries,general:occlusion_query,* webgpu:api,validation,error_scope:* webgpu:api,validation,getBindGroupLayout:* webgpu:api,validation,image_copy,buffer_related:* // image_copy depth/stencil failures on dx12: https://github.com/gfx-rs/wgpu/issues/8133 fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:depth_stencil_format,copy_buffer_offset:format="depth24plus-stencil8";aspect="stencil-only";copyType="CopyB2T" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:depth_stencil_format,copy_buffer_offset:format="depth24plus-stencil8";aspect="stencil-only";copyType="CopyT2B" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:depth_stencil_format,copy_buffer_offset:format="depth32float";aspect="depth-only";copyType="CopyT2B" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:depth_stencil_format,copy_buffer_offset:format="stencil8";aspect="stencil-only";copyType="CopyB2T" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:depth_stencil_format,copy_buffer_offset:format="stencil8";aspect="stencil-only";copyType="CopyT2B" webgpu:api,validation,image_copy,buffer_texture_copies:depth_stencil_format,copy_buffer_size:* webgpu:api,validation,image_copy,buffer_texture_copies:depth_stencil_format,copy_usage_and_aspect:* webgpu:api,validation,image_copy,buffer_texture_copies:device_mismatch:* fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="astc-4x4-unorm";copyType="CopyB2T";dimension="2d" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="astc-4x4-unorm";copyType="CopyB2T";dimension="3d" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="astc-4x4-unorm";copyType="CopyT2B";dimension="2d" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="astc-4x4-unorm";copyType="CopyT2B";dimension="3d" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="bgra8unorm";copyType="CopyB2T";dimension="2d" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="bgra8unorm";copyType="CopyT2B";dimension="2d" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="r8uint";copyType="CopyB2T";dimension="1d" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="r8uint";copyType="CopyT2B";dimension="1d" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="rgba32float";copyType="CopyB2T";dimension="1d" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="rgba32float";copyType="CopyT2B";dimension="1d" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="rgba8unorm";copyType="CopyB2T";dimension="2d" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="rgba8unorm";copyType="CopyB2T";dimension="3d" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="rgba8unorm";copyType="CopyT2B";dimension="2d" fails-if(dx12) webgpu:api,validation,image_copy,buffer_texture_copies:offset_and_bytesPerRow:format="rgba8unorm";copyType="CopyT2B";dimension="3d" webgpu:api,validation,image_copy,buffer_texture_copies:sample_count:* webgpu:api,validation,image_copy,buffer_texture_copies:texture_buffer_usages:* webgpu:api,validation,image_copy,layout_related:bound_on_bytes_per_row:* webgpu:api,validation,image_copy,layout_related:bound_on_offset:* webgpu:api,validation,image_copy,layout_related:bound_on_rows_per_image:* webgpu:api,validation,image_copy,layout_related:copy_end_overflows_u64:* webgpu:api,validation,image_copy,layout_related:offset_alignment:* webgpu:api,validation,image_copy,layout_related:required_bytes_in_copy:* webgpu:api,validation,image_copy,layout_related:rows_per_image_alignment:* webgpu:api,validation,image_copy,texture_related:* fails-if(dx12) webgpu:api,validation,layout_shader_compat:pipeline_layout_shader_exact_match:* webgpu:api,validation,query_set,destroy:* webgpu:api,validation,queue,buffer_mapped:copyBufferToBuffer:* webgpu:api,validation,queue,buffer_mapped:copyBufferToTexture:* webgpu:api,validation,queue,buffer_mapped:copyTextureToBuffer:* webgpu:api,validation,queue,buffer_mapped:map_command_recording_order:* // `vulkan` failure: https://github.com/gfx-rs/wgpu/issues/???? fails-if(vulkan) webgpu:api,validation,queue,buffer_mapped:writeBuffer:* webgpu:api,validation,queue,submit:command_buffer,* webgpu:api,validation,queue,writeBuffer:buffer_state:* webgpu:api,validation,queue,writeBuffer:buffer,device_mismatch:* webgpu:api,validation,queue,writeBuffer:usages:* webgpu:api,validation,queue,writeTexture:* webgpu:api,validation,render_pass,attachment_compatibility:render_pass_and_bundle,* webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,color_count:* webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,color_format:* webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,color_sparse:* webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,depth_format:* fails-if(dx12) webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,depth_stencil_read_only_write_state:* webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,sample_count:* webgpu:api,validation,render_pass,render_pass_descriptor:attachments,* webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,* webgpu:api,validation,render_pass,render_pass_descriptor:depth_stencil_attachment,depth_clear_value:* webgpu:api,validation,render_pass,render_pass_descriptor:depth_stencil_attachment,sample_counts_mismatch:* webgpu:api,validation,render_pass,render_pass_descriptor:occlusionQuerySet,query_set_type:* webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,* webgpu:api,validation,render_pass,render_pass_descriptor:timestampWrite,query_index:* webgpu:api,validation,render_pass,render_pass_descriptor:timestampWrites,query_set_type:* webgpu:api,validation,render_pass,resolve:resolve_attachment:* webgpu:api,validation,render_pipeline,depth_stencil_state:* webgpu:api,validation,render_pipeline,float32_blendable:* webgpu:api,validation,render_pipeline,fragment_state:color_target_exists:* webgpu:api,validation,render_pipeline,fragment_state:dual_source_blending,* webgpu:api,validation,render_pipeline,fragment_state:limits,* webgpu:api,validation,render_pipeline,fragment_state:targets_blend:* webgpu:api,validation,render_pipeline,fragment_state:targets_format_filterable:* webgpu:api,validation,render_pipeline,fragment_state:targets_format_is_color_format:* webgpu:api,validation,render_pipeline,fragment_state:targets_format_renderable:* webgpu:api,validation,render_pipeline,fragment_state:targets_write_mask:* webgpu:api,validation,render_pipeline,inter_stage:location,* webgpu:api,validation,render_pipeline,inter_stage:max_shader_variable_location:* fails-if(dx12) webgpu:api,validation,render_pipeline,inter_stage:max_variables_count,* webgpu:api,validation,render_pipeline,misc:basic:* fails-if(vulkan) webgpu:api,validation,render_pipeline,misc:external_texture:* webgpu:api,validation,render_pipeline,misc:no_attachment:* webgpu:api,validation,render_pipeline,misc:pipeline_layout,device_mismatch:* webgpu:api,validation,render_pipeline,misc:vertex_state_only:* webgpu:api,validation,render_pipeline,primitive_state:* webgpu:api,validation,render_pipeline,resource_compatibility:* webgpu:api,validation,render_pipeline,shader_module:* webgpu:api,validation,render_pipeline,vertex_state:many_attributes_overlapping:* webgpu:api,validation,render_pipeline,vertex_state:max_vertex_buffer_array_stride_limit:* fails-if(metal) webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_contained_in_stride:* fails-if(metal) webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_offset_alignment:* webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_shaderLocation_limit:* webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_shaderLocation_unique:* webgpu:api,validation,render_pipeline,vertex_state:vertex_buffer_array_stride_limit_alignment:* webgpu:api,validation,render_pipeline,vertex_state:vertex_shader_input_location_in_vertex_state:* webgpu:api,validation,render_pipeline,vertex_state:vertex_shader_input_location_limit:* webgpu:api,validation,render_pipeline,vertex_state:vertex_shader_type_matches_attribute_format:* webgpu:api,validation,resource_usages,buffer,in_pass_encoder:* webgpu:api,validation,resource_usages,buffer,in_pass_misc:* webgpu:api,validation,resource_usages,texture,in_pass_encoder:bindings_in_bundle:* webgpu:api,validation,resource_usages,texture,in_pass_encoder:replaced_binding:* webgpu:api,validation,resource_usages,texture,in_pass_encoder:scope,* webgpu:api,validation,resource_usages,texture,in_pass_encoder:shader_stages_and_visibility,* webgpu:api,validation,resource_usages,texture,in_pass_encoder:subresources_and_binding_types_combination_for_aspect:* webgpu:api,validation,resource_usages,texture,in_pass_encoder:subresources_and_binding_types_combination_for_color:compute=false;type0="render-target";type1="render-target" webgpu:api,validation,resource_usages,texture,in_pass_encoder:unused_bindings_in_pipeline:* webgpu:api,validation,resource_usages,texture,in_render_common:subresources,color_attachment_and_bind_group:* webgpu:api,validation,resource_usages,texture,in_render_common:subresources,color_attachments:* // FAIL: webgpu:api,validation,resource_usages,texture,in_render_common:subresources,depth_stencil_attachment_and_bind_group:* // https://github.com/gfx-rs/wgpu/issues/8705 webgpu:api,validation,resource_usages,texture,in_render_common:subresources,depth_stencil_texture_in_bind_groups:* webgpu:api,validation,resource_usages,texture,in_render_common:subresources,multiple_bind_groups:* webgpu:api,validation,resource_usages,texture,in_render_misc:* webgpu:api,validation,shader_module,* webgpu:api,validation,texture,bgra8unorm_storage:* webgpu:api,validation,texture,destroy:base:* webgpu:api,validation,texture,destroy:invalid_texture:* webgpu:api,validation,texture,destroy:twice:* webgpu:api,validation,texture,float32_filterable:create_bind_group:* webgpu:api,validation,texture,rg11b10ufloat_renderable:* fails-if(dx12) webgpu:shader,execution,expression,call,builtin,atan2:f16:* webgpu:shader,execution,expression,call,builtin,atan2:f32:* webgpu:shader,execution,expression,call,builtin,dot:abstract_int_vec2:* webgpu:shader,execution,expression,call,builtin,dot:abstract_int_vec3:* webgpu:shader,execution,expression,call,builtin,dot:abstract_int_vec4:* webgpu:shader,execution,expression,call,builtin,dot:i32_vec2:* webgpu:shader,execution,expression,call,builtin,dot:i32_vec3:* webgpu:shader,execution,expression,call,builtin,dot:i32_vec4:* webgpu:shader,execution,expression,call,builtin,dot:u32_vec2:* webgpu:shader,execution,expression,call,builtin,dot:u32_vec3:* webgpu:shader,execution,expression,call,builtin,dot:u32_vec4:* //FAIL: webgpu:shader,execution,expression,call,builtin,select:* // - Fails with `const`/abstract int cases on all platforms because of . // - Fails with `vec3` & `f16` cases on macOS because of . webgpu:shader,execution,expression,call,builtin,textureSample:sampled_1d_coords:* webgpu:shader,execution,expression,call,builtin,textureSampleBaseClampToEdge:2d_coords:stage="c";textureType="texture_2d";* // NOTE: This is supposed to be an exhaustive listing underneath // `webgpu:shader,execution,expression,call,builtin,workgroupUniformLoad:*`, so exceptions can be // worked around. webgpu:shader,execution,expression,call,builtin,workgroupUniformLoad:types:type="array%3Cu32,%204%3E";* webgpu:shader,execution,expression,call,builtin,workgroupUniformLoad:types:type="atomic%3Ci32%3E";* webgpu:shader,execution,expression,call,builtin,workgroupUniformLoad:types:type="atomic%3Cu32%3E";* webgpu:shader,execution,expression,call,builtin,workgroupUniformLoad:types:type="AtomicInStruct";* webgpu:shader,execution,expression,call,builtin,workgroupUniformLoad:types:type="bool";* //FAIL: https://github.com/gfx-rs/wgpu/issues/8812 // webgpu:shader,execution,expression,call,builtin,workgroupUniformLoad:types:type="ComplexStruct";* webgpu:shader,execution,expression,call,builtin,workgroupUniformLoad:types:type="mat3x2f";* webgpu:shader,execution,expression,call,builtin,workgroupUniformLoad:types:type="SimpleStruct";* webgpu:shader,execution,expression,call,builtin,workgroupUniformLoad:types:type="u32";* webgpu:shader,execution,expression,call,builtin,workgroupUniformLoad:types:type="vec4u";* webgpu:shader,execution,expression,unary,bool_conversion:* webgpu:shader,execution,flow_control,return:* // Many other vertex_buffer_access subtests also passing, but there are too many to enumerate. // Fails on Metal in CI only, not when running locally. fails-if(metal) webgpu:shader,execution,robust_access_vertex:vertex_buffer_access:indexed=true;indirect=false;drawCallTestParameter="baseVertex";type="float32x4";additionalBuffers=4;partialLastNumber=false;offsetVertexBuffer=true webgpu:shader,execution,shader_io,fragment_builtins:inputs,front_facing:* webgpu:shader,execution,shader_io,fragment_builtins:inputs,interStage,centroid:* fails-if(vulkan) webgpu:shader,execution,shader_io,fragment_builtins:inputs,interStage:* fails-if(dx12,vulkan) webgpu:shader,execution,shader_io,fragment_builtins:inputs,position:* webgpu:shader,execution,shader_io,fragment_builtins:inputs,sample_index:* fails-if(dx12,vulkan) webgpu:shader,execution,shader_io,fragment_builtins:inputs,sample_mask:* webgpu:shader,execution,shader_io,fragment_builtins:primitive_index,* webgpu:shader,execution,shader_io,fragment_builtins:subgroup_invocation_id:* webgpu:shader,execution,shader_io,fragment_builtins:subgroup_size:* webgpu:shader,execution,shader_io,vertex_builtins:outputs,clip_distances:* webgpu:shader,execution,statement,compound:* webgpu:shader,validation,const_assert,const_assert:* webgpu:shader,validation,decl,assignment_statement:* webgpu:shader,validation,decl,compound_statement:* webgpu:shader,validation,decl,const:* webgpu:shader,validation,expression,access,array:early_eval_errors:case="override_array_cnt_size_neg" webgpu:shader,validation,expression,access,array:early_eval_errors:case="override_array_cnt_size_one" webgpu:shader,validation,expression,access,array:early_eval_errors:case="override_array_cnt_size_zero_signed" webgpu:shader,validation,expression,access,array:early_eval_errors:case="override_array_cnt_size_zero_unsigned" webgpu:shader,validation,expression,access,array:early_eval_errors:case="override_in_bounds" webgpu:shader,validation,expression,access,structure:* webgpu:shader,validation,expression,binary,add_sub_mul:scalar_vector_out_of_range:lhs="i32";* webgpu:shader,validation,expression,binary,add_sub_mul:scalar_vector_out_of_range:lhs="u32";* webgpu:shader,validation,expression,binary,bitwise_shift:partial_eval_errors:* webgpu:shader,validation,expression,binary,bitwise_shift:scalar_vector:* webgpu:shader,validation,expression,binary,bitwise_shift:shift_left_abstract:* webgpu:shader,validation,expression,binary,bitwise_shift:shift_left_concrete:* webgpu:shader,validation,expression,binary,bitwise_shift:shift_right_abstract:* webgpu:shader,validation,expression,binary,bitwise_shift:shift_right_concrete:* webgpu:shader,validation,expression,binary,parse:* webgpu:shader,validation,expression,binary,short_circuiting_and_or:array_override:op="%26%26";a_val=1;b_val=1 webgpu:shader,validation,expression,binary,short_circuiting_and_or:invalid_types:* webgpu:shader,validation,expression,binary,short_circuiting_and_or:scalar_vector:op="%26%26";lhs="bool";rhs="bool" webgpu:shader,validation,expression,call,builtin,acos:* webgpu:shader,validation,expression,call,builtin,all:* webgpu:shader,validation,expression,call,builtin,any:* webgpu:shader,validation,expression,call,builtin,arrayLength:* webgpu:shader,validation,expression,call,builtin,asin:* webgpu:shader,validation,expression,call,builtin,asinh:* webgpu:shader,validation,expression,call,builtin,atan:* webgpu:shader,validation,expression,call,builtin,atan2:* webgpu:shader,validation,expression,call,builtin,atanh:* webgpu:shader,validation,expression,call,builtin,barriers:* webgpu:shader,validation,expression,call,builtin,ceil:* webgpu:shader,validation,expression,call,builtin,cos:* webgpu:shader,validation,expression,call,builtin,cosh:* webgpu:shader,validation,expression,call,builtin,degrees:* webgpu:shader,validation,expression,call,builtin,distance:values:stage="constant";type="f32" webgpu:shader,validation,expression,call,builtin,dot:* webgpu:shader,validation,expression,call,builtin,exp:* webgpu:shader,validation,expression,call,builtin,exp2:* webgpu:shader,validation,expression,call,builtin,floor:* webgpu:shader,validation,expression,call,builtin,fract:* webgpu:shader,validation,expression,call,builtin,length:scalar:stage="constant";type="f32" webgpu:shader,validation,expression,call,builtin,log:* webgpu:shader,validation,expression,call,builtin,log2:* webgpu:shader,validation,expression,call,builtin,max:* webgpu:shader,validation,expression,call,builtin,min:* webgpu:shader,validation,expression,call,builtin,radians:* webgpu:shader,validation,expression,call,builtin,round:* webgpu:shader,validation,expression,call,builtin,saturate:* webgpu:shader,validation,expression,call,builtin,sign:* webgpu:shader,validation,expression,call,builtin,sin:* webgpu:shader,validation,expression,call,builtin,sinh:* webgpu:shader,validation,expression,call,builtin,sqrt:* webgpu:shader,validation,expression,call,builtin,step:* webgpu:shader,validation,expression,call,builtin,tan:* webgpu:shader,validation,expression,call,builtin,tanh:* // Uses external textures fails-if(vulkan) webgpu:shader,validation,expression,call,builtin,textureLoad:* webgpu:shader,validation,expression,call,builtin,textureNumLayers:* webgpu:shader,validation,expression,call,builtin,textureNumLevels:* webgpu:shader,validation,expression,call,builtin,textureNumSamples:* // Uses external textures fails-if(vulkan) webgpu:shader,validation,expression,call,builtin,textureSampleBaseClampToEdge:* webgpu:shader,validation,expression,call,builtin,textureStore:* webgpu:shader,validation,expression,call,builtin,trunc:* webgpu:shader,validation,expression,call,builtin,value_constructor:* webgpu:shader,validation,expression,call,builtin,workgroupUniformLoad:* webgpu:shader,validation,expression,overload_resolution:* webgpu:shader,validation,extension,clip_distances:* webgpu:shader,validation,extension,dual_source_blending:* webgpu:shader,validation,extension,pointer_composite_access:* webgpu:shader,validation,functions,restrictions:param_type_can_be_alias:* webgpu:shader,validation,parse,blankspace:blankspace:* webgpu:shader,validation,parse,blankspace:bom:* webgpu:shader,validation,parse,blankspace:null_characters:contains_null=false;* webgpu:shader,validation,parse,blankspace:null_characters:contains_null=true;placement="delimiter" webgpu:shader,validation,parse,blankspace:null_characters:contains_null=true;placement="eol" webgpu:shader,validation,parse,enable:* webgpu:shader,validation,parse,identifiers:* webgpu:shader,validation,parse,requires:* webgpu:shader,validation,parse,semicolon:* webgpu:shader,validation,parse,shadow_builtins:function_param:* webgpu:shader,validation,parse,source:* webgpu:shader,validation,shader_io,entry_point:* // Uses external texture bindings fails-if(vulkan) webgpu:shader,validation,shader_io,group_and_binding:* webgpu:shader,validation,shader_io,invariant:* webgpu:shader,validation,shader_io,locations:duplicates:* webgpu:shader,validation,shader_io,locations:location_fp16:* webgpu:shader,validation,shader_io,locations:nesting:* webgpu:shader,validation,shader_io,locations:out_of_order:* webgpu:shader,validation,shader_io,locations:type:* webgpu:shader,validation,shader_io,locations:validation:* webgpu:shader,validation,statement,break_if:* webgpu:shader,validation,statement,break:* webgpu:shader,validation,statement,compound:* webgpu:shader,validation,statement,const_assert:* webgpu:shader,validation,statement,continuing:* webgpu:shader,validation,statement,discard:* webgpu:shader,validation,statement,if:* webgpu:shader,validation,statement,return:* webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="break_if" webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="break" webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="continue" webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="for3" webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="for4" webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="for5" webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="loop4" webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="loop5" webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="loop6" webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="loop8" webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="switch1" webgpu:shader,validation,statement,while:* webgpu:util,texture,texture_ok:* ================================================ FILE: cts_runner/tests/integration.rs ================================================ use std::{ ffi::OsStr, fs, io::Write, path::PathBuf, process::{Command, Output}, str, }; use tempfile::NamedTempFile; pub fn target_dir() -> PathBuf { let current_exe = std::env::current_exe().unwrap(); let target_dir = current_exe.parent().unwrap().parent().unwrap(); target_dir.into() } pub fn cts_runner_exe_path() -> PathBuf { // Something like /Users/lucacasonato/src/wgpu/target/debug/cts_runner let mut p = target_dir().join("cts_runner"); if cfg!(windows) { p.set_extension("exe"); } p } fn exec_cts_runner(script_file: impl AsRef) -> Output { Command::new(cts_runner_exe_path()) .arg(script_file) .output() .unwrap() } // The idea here is that if the test outputs something on stderr, we want to // print it verbatim, not as a quoted string with escape sequences. struct Error(String); impl std::fmt::Debug for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) } } fn exec_js_file(script_file: impl AsRef) -> Result<(), Error> { let output = exec_cts_runner(script_file); println!("{}", str::from_utf8(&output.stdout).unwrap()); eprintln!("{}", str::from_utf8(&output.stderr).unwrap()); if !output.status.success() { return Err(Error(format!( "process exited unsuccessfully: {}", output.status ))); } Ok(()) } fn check_js_stderr(script: &str, expected: &str) -> Result<(), Error> { let mut tempfile = NamedTempFile::new().unwrap(); tempfile.write_all(script.as_bytes()).unwrap(); tempfile.flush().unwrap(); let output = exec_cts_runner(tempfile.path()); if !output.stdout.is_empty() { return Err(Error(format!( "unexpected output on stdout: {}", str::from_utf8(&output.stdout).unwrap(), ))); } let stderr_str = str::from_utf8(&output.stderr).unwrap(); if expected.is_empty() && !stderr_str.is_empty() { return Err(Error(format!( "unexpected output on stderr: {}", stderr_str, ))); } else if stderr_str != expected { return Err(Error(format!( "expected the following output on stderr:\n{}\n\nbut observed:\n{}", expected, stderr_str, ))); } if !output.status.success() { return Err(Error(format!( "process exited unsuccessfully: {}", output.status ))); } Ok(()) } fn exec_js(script: &str) -> Result<(), Error> { check_js_stderr(script, "") } #[test] fn hello_compute_example() -> Result<(), Error> { exec_js_file("examples/hello-compute.js") } #[test] fn features() -> Result<(), Error> { // Check that we don't expose native-only features. exec_js( r#" const adapter = await navigator.gpu.requestAdapter(); if (adapter.features.has("mappable-primary-buffers")) { throw new TypeError("Adapter should not report support for wgpu native-only features"); } "#, )?; // Check for features tested by the CTS. Because these are optional // features, the applicable CTS tests will pass (silently, without // exercising the functionality) when support is not reported. This test // serves to bridge the gap between the coverage provided by the CTS // ("feature must work if available") and our desired coverage ("feature // must be implemented and work"), in case we inadvertently stop reporting // support for a feature. (There ought to also be relevant wgpu tests of the // feature that would catch this, but better to be safe.) exec_js( r#" const adapter = await navigator.gpu.requestAdapter(); if (!adapter.features.has("primitive-index")) { throw new TypeError("Adapter should report support for primitive-index feature"); } "#, )?; Ok(()) } #[test] fn uncaptured_error() -> Result<(), Error> { check_js_stderr( r#" const code = `const val: u32 = 1.1;`; const adapter = await navigator.gpu.requestAdapter(); const device = await adapter.requestDevice(); device.createShaderModule({ code }) "#, "cts_runner caught WebGPU error:\x20 Shader '' parsing error: the type of `val` is expected to be `u32`, but got `{AbstractFloat}` ┌─ wgsl:1:7 │ 1 │ const val: u32 = 1.1; │ ^^^ definition of `val`\n\n\n", ) } #[test] fn lst_files_are_sorted() { let workspace_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .parent() .unwrap() .to_path_buf(); let files = ["test.lst", "fail.lst", "skip.lst"]; for file in &files { let file_path = workspace_dir.join("cts_runner").join(file); let contents = fs::read_to_string(&file_path).unwrap(); let selectors = contents .lines() .enumerate() .filter_map(|(idx, line)| { // Extract selectors (including in comments, removing fails-if annotations) let trimmed = line.trim(); trimmed .find("webgpu:") .or_else(|| trimmed.find("unittests:")) .map(|pos| (idx, &trimmed[pos..])) }) .map(|(idx, line)| { // Crude en_US sort. '_' < ',' < ':' < digits < letters let sort_key = line .chars() .map(|c| { if c.is_ascii_uppercase() { c.to_ascii_lowercase() } else if c == '_' { ' ' } else if c == ':' { '-' } else { c } }) .collect::(); (idx, line, sort_key) }) .collect::>(); let mut sorted = selectors.clone(); sorted.sort_by_key(|(_, _, sort_key)| sort_key.clone()); if selectors != sorted { let (found, expected) = selectors .iter() .zip(sorted.iter()) .find(|(a, b)| a != b) .unwrap(); panic!( "{} is not sorted. First mismatch on line {}:\nFound: {}\nShould be: {}", file, found.0 + 1, found.1, expected.1, ); } } } ================================================ FILE: deno_webgpu/00_init.js ================================================ // Copyright 2018-2025 the Deno authors. MIT license. import { core } from "ext:core/mod.js"; const loadWebGPU = core.createLazyLoader("ext:deno_webgpu/01_webgpu.js"); export { loadWebGPU }; ================================================ FILE: deno_webgpu/01_webgpu.js ================================================ // Copyright 2018-2025 the Deno authors. MIT license. // @ts-check /// /// /// /// import { core, primordials } from "ext:core/mod.js"; import { GPU, GPUAdapter, GPUAdapterInfo, GPUBindGroup, GPUBindGroupLayout, GPUBuffer, GPUCommandBuffer, GPUCommandEncoder, GPUCompilationInfo, GPUCompilationMessage, GPUComputePassEncoder, GPUComputePipeline, GPUDevice, GPUDeviceLostInfo, GPUPipelineLayout, GPUQuerySet, GPUQueue, GPURenderBundle, GPURenderBundleEncoder, GPURenderPassEncoder, GPURenderPipeline, GPUSampler, GPUShaderModule, GPUSupportedFeatures, GPUSupportedLimits, GPUTexture, GPUTextureView, GPUExternalTexture, WGSLLanguageFeatures, op_create_gpu, op_webgpu_device_start_capture, op_webgpu_device_stop_capture, } from "ext:core/ops"; const { ObjectDefineProperty, ObjectPrototypeIsPrototypeOf, ObjectSetPrototypeOf, ReflectGet, Symbol, SymbolFor, } = primordials; import * as webidl from "ext:deno_webidl/00_webidl.js"; import { defineEventHandler, Event, EventTargetPrototype, setEventTargetData, } from "ext:deno_web/02_event.js"; import { DOMException } from "ext:deno_web/01_dom_exception.js"; import { createFilteredInspectProxy } from "ext:deno_console/01_console.js"; const privateCustomInspect = SymbolFor("Deno.privateCustomInspect"); const _message = Symbol("[[message]]"); const illegalConstructorKey = Symbol("illegalConstructorKey"); class GPUError { constructor(key = null) { if (key !== illegalConstructorKey) { webidl.illegalConstructor(); } } [_message]; get message() { webidl.assertBranded(this, GPUErrorPrototype); return this[_message]; } [privateCustomInspect](inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPUErrorPrototype, this), keys: [ "message", ], }), inspectOptions, ); } } const GPUErrorPrototype = GPUError.prototype; class GPUValidationError extends GPUError { /** @param {string} message */ constructor(message) { const prefix = "Failed to construct 'GPUValidationError'"; webidl.requiredArguments(arguments.length, 1, prefix); message = webidl.converters.DOMString(message, prefix, "Argument 1"); super(illegalConstructorKey); this[webidl.brand] = webidl.brand; this[_message] = message; } } core.registerErrorClass("GPUValidationError", GPUValidationError); class GPUOutOfMemoryError extends GPUError { constructor(message) { const prefix = "Failed to construct 'GPUOutOfMemoryError'"; webidl.requiredArguments(arguments.length, 1, prefix); message = webidl.converters.DOMString(message, prefix, "Argument 1"); super(illegalConstructorKey); this[webidl.brand] = webidl.brand; this[_message] = message; } } core.registerErrorClass("GPUOutOfMemoryError", GPUOutOfMemoryError); class GPUInternalError extends GPUError { constructor() { super(illegalConstructorKey); this[webidl.brand] = webidl.brand; } } core.registerErrorClass("GPUInternalError", GPUInternalError); class GPUPipelineError extends DOMException { #reason; constructor(message = "", options = { __proto__: null }) { const prefix = "Failed to construct 'GPUPipelineError'"; message = webidl.converters.DOMString(message, prefix, "Argument 1"); options = webidl.converters.GPUPipelineErrorInit( options, prefix, "Argument 2", ); super(message, "GPUPipelineError"); this.#reason = options.reason; } get reason() { webidl.assertBranded(this, GPUPipelineErrorPrototype); return this.#reason; } } const GPUPipelineErrorPrototype = GPUPipelineError.prototype; class GPUUncapturedErrorEvent extends Event { #error; constructor(type, gpuUncapturedErrorEventInitDict) { super(type, gpuUncapturedErrorEventInitDict); this[webidl.brand] = webidl.brand; const prefix = "Failed to construct 'GPUUncapturedErrorEvent'"; webidl.requiredArguments(arguments.length, 2, prefix); gpuUncapturedErrorEventInitDict = webidl.converters .GPUUncapturedErrorEventInit( gpuUncapturedErrorEventInitDict, prefix, "Argument 2", ); this.#error = gpuUncapturedErrorEventInitDict.error; } get error() { webidl.assertBranded(this, GPUUncapturedErrorEventPrototype); return this.#error; } } const GPUUncapturedErrorEventPrototype = GPUUncapturedErrorEvent.prototype; const GPUPrototype = GPU.prototype; ObjectDefineProperty(GPUPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return `${this.constructor.name} ${inspect({}, inspectOptions)}`; }, }); const GPUAdapterPrototype = GPUAdapter.prototype; ObjectDefineProperty(GPUAdapterPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPUAdapterPrototype, this), keys: [ "features", "limits", "info", ], }), inspectOptions, ); }, }); const GPUAdapterInfoPrototype = GPUAdapterInfo.prototype; ObjectDefineProperty(GPUAdapterInfoPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPUAdapterInfoPrototype, this), keys: [ "vendor", "architecture", "device", "description", "subgroupMinSize", "subgroupMaxSize", "isFallbackAdapter", ], }), inspectOptions, ); }, }); const GPUSupportedFeaturesPrototype = GPUSupportedFeatures.prototype; webidl.setlikeObjectWrap(GPUSupportedFeaturesPrototype, true); ObjectDefineProperty(GPUSupportedFeaturesPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { if (ObjectPrototypeIsPrototypeOf(GPUSupportedFeaturesPrototype, this)) { return `${this.constructor.name} ${ // deno-lint-ignore prefer-primordials inspect([...this], inspectOptions)}`; } else { return `${this.constructor.name} ${inspect({}, inspectOptions)}`; } }, }); const WGSLLanguageFeaturesPrototype = WGSLLanguageFeatures.prototype; webidl.setlikeObjectWrap(WGSLLanguageFeaturesPrototype, true); ObjectDefineProperty(WGSLLanguageFeaturesPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { if (ObjectPrototypeIsPrototypeOf(WGSLLanguageFeaturesPrototype, this)) { return `${this.constructor.name} ${ // deno-lint-ignore prefer-primordials inspect([...this], inspectOptions)}`; } else { return `${this.constructor.name} ${inspect({}, inspectOptions)}`; } }, }); const GPUSupportedLimitsPrototype = GPUSupportedLimits.prototype; ObjectDefineProperty(GPUSupportedLimitsPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( GPUSupportedLimitsPrototype, this, ), keys: [ "maxTextureDimension1D", "maxTextureDimension2D", "maxTextureDimension3D", "maxTextureArrayLayers", "maxBindGroups", // TODO(@crowlKats): support max_bind_groups_plus_vertex_buffers // "maxBindGroupsPlusVertexBuffers", "maxBindingsPerBindGroup", "maxDynamicUniformBuffersPerPipelineLayout", "maxDynamicStorageBuffersPerPipelineLayout", "maxSampledTexturesPerShaderStage", "maxSamplersPerShaderStage", "maxStorageBuffersPerShaderStage", "maxStorageTexturesPerShaderStage", "maxUniformBuffersPerShaderStage", "maxUniformBufferBindingSize", "maxStorageBufferBindingSize", "minUniformBufferOffsetAlignment", "minStorageBufferOffsetAlignment", "maxVertexBuffers", "maxBufferSize", "maxVertexAttributes", "maxVertexBufferArrayStride", "maxInterStageShaderVariables", "maxColorAttachments", "maxColorAttachmentBytesPerSample", "maxComputeWorkgroupStorageSize", "maxComputeInvocationsPerWorkgroup", "maxComputeWorkgroupSizeX", "maxComputeWorkgroupSizeY", "maxComputeWorkgroupSizeZ", "maxComputeWorkgroupsPerDimension", ], }), inspectOptions, ); }, }); const GPUDeviceLostInfoPrototype = GPUDeviceLostInfo.prototype; ObjectDefineProperty(GPUDeviceLostInfoPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( GPUDeviceLostInfoPrototype, this, ), keys: [ "reason", "message", ], }), inspectOptions, ); }, }); const GPUDevicePrototype = GPUDevice.prototype; ObjectSetPrototypeOf(GPUDevicePrototype, EventTargetPrototype); defineEventHandler(GPUDevicePrototype, "uncapturederror"); ObjectDefineProperty(GPUDevicePrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPUDevicePrototype, this), keys: [ "features", "label", "limits", "lost", "queue", // TODO(lucacasonato): emit an UncapturedErrorEvent // "onuncapturederror" ], }), inspectOptions, ); }, }); const GPUQueuePrototype = GPUQueue.prototype; ObjectDefineProperty(GPUQueuePrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPUQueuePrototype, this), keys: [ "label", ], }), inspectOptions, ); }, }); const GPUBufferPrototype = GPUBuffer.prototype; ObjectDefineProperty(GPUBufferPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPUBufferPrototype, this), keys: [ "label", "mapState", "size", "usage", ], }), inspectOptions, ); }, }); class GPUBufferUsage { constructor() { webidl.illegalConstructor(); } static get MAP_READ() { return 0x0001; } static get MAP_WRITE() { return 0x0002; } static get COPY_SRC() { return 0x0004; } static get COPY_DST() { return 0x0008; } static get INDEX() { return 0x0010; } static get VERTEX() { return 0x0020; } static get UNIFORM() { return 0x0040; } static get STORAGE() { return 0x0080; } static get INDIRECT() { return 0x0100; } static get QUERY_RESOLVE() { return 0x0200; } } class GPUMapMode { constructor() { webidl.illegalConstructor(); } static get READ() { return 0x0001; } static get WRITE() { return 0x0002; } } const GPUTexturePrototype = GPUTexture.prototype; ObjectDefineProperty(GPUTexturePrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPUTexturePrototype, this), keys: [ "label", "width", "height", "depthOrArrayLayers", "mipLevelCount", "sampleCount", "dimension", "format", "usage", ], }), inspectOptions, ); }, }); class GPUTextureUsage { constructor() { webidl.illegalConstructor(); } static get COPY_SRC() { return 0x01; } static get COPY_DST() { return 0x02; } static get TEXTURE_BINDING() { return 0x04; } static get STORAGE_BINDING() { return 0x08; } static get RENDER_ATTACHMENT() { return 0x10; } } const GPUTextureViewPrototype = GPUTextureView.prototype; ObjectDefineProperty(GPUTextureViewPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPUTextureViewPrototype, this), keys: [ "label", ], }), inspectOptions, ); }, }); const GPUSamplerPrototype = GPUSampler.prototype; ObjectDefineProperty(GPUSamplerPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( GPUSamplerPrototype, this, ), keys: [ "label", ], }), inspectOptions, ); }, }); const GPUBindGroupLayoutPrototype = GPUBindGroupLayout.prototype; ObjectDefineProperty(GPUBindGroupLayout, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( GPUBindGroupLayoutPrototype, this, ), keys: [ "label", ], }), inspectOptions, ); }, }); const GPUPipelineLayoutPrototype = GPUPipelineLayout.prototype; ObjectDefineProperty(GPUPipelineLayoutPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( GPUPipelineLayoutPrototype, this, ), keys: [ "label", ], }), inspectOptions, ); }, }); const GPUBindGroupPrototype = GPUBindGroup.prototype; ObjectDefineProperty(GPUBindGroupPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPUBindGroupPrototype, this), keys: [ "label", ], }), inspectOptions, ); }, }); const GPUShaderModulePrototype = GPUShaderModule.prototype; ObjectDefineProperty(GPUShaderModulePrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPUShaderModulePrototype, this), keys: [ "label", ], }), inspectOptions, ); }, }); ObjectDefineProperty(GPUCompilationInfo, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( GPUCompilationInfoPrototype, this, ), keys: [ "messages", ], }), inspectOptions, ); }, }); const GPUCompilationInfoPrototype = GPUCompilationInfo.prototype; ObjectDefineProperty(GPUCompilationMessage, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( GPUCompilationMessagePrototype, this, ), keys: [ "message", "type", "line_num", "line_pos", "offset", "length", ], }), inspectOptions, ); }, }); const GPUCompilationMessagePrototype = GPUCompilationMessage.prototype; class GPUShaderStage { constructor() { webidl.illegalConstructor(); } static get VERTEX() { return 0x1; } static get FRAGMENT() { return 0x2; } static get COMPUTE() { return 0x4; } } const GPUComputePipelinePrototype = GPUComputePipeline.prototype; ObjectDefineProperty(GPUComputePipelinePrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( GPUComputePipelinePrototype, this, ), keys: [ "label", ], }), inspectOptions, ); }, }); const GPURenderPipelinePrototype = GPURenderPipeline.prototype; ObjectDefineProperty(GPURenderPipelinePrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( GPURenderPipelinePrototype, this, ), keys: [ "label", ], }), inspectOptions, ); }, }); class GPUColorWrite { constructor() { webidl.illegalConstructor(); } static get RED() { return 0x1; } static get GREEN() { return 0x2; } static get BLUE() { return 0x4; } static get ALPHA() { return 0x8; } static get ALL() { return 0xF; } } const GPUCommandEncoderPrototype = GPUCommandEncoder.prototype; ObjectDefineProperty(GPUCommandEncoderPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( GPUCommandEncoderPrototype, this, ), keys: [ "label", ], }), inspectOptions, ); }, }); const GPURenderPassEncoderPrototype = GPURenderPassEncoder.prototype; ObjectDefineProperty(GPURenderPassEncoderPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( GPURenderPassEncoderPrototype, this, ), keys: [ "label", ], }), inspectOptions, ); }, }); const GPUComputePassEncoderPrototype = GPUComputePassEncoder.prototype; ObjectDefineProperty(GPUComputePassEncoderPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( GPUComputePassEncoderPrototype, this, ), keys: [ "label", ], }), inspectOptions, ); }, }); const GPUCommandBufferPrototype = GPUCommandBuffer.prototype; ObjectDefineProperty(GPUCommandBufferPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPUCommandBufferPrototype, this), keys: [ "label", ], }), inspectOptions, ); }, }); const GPURenderBundleEncoderPrototype = GPURenderBundleEncoder.prototype; ObjectDefineProperty(GPURenderBundleEncoderPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf( GPURenderBundleEncoderPrototype, this, ), keys: [ "label", ], }), inspectOptions, ); }, }); const GPURenderBundlePrototype = GPURenderBundle.prototype; ObjectDefineProperty(GPURenderBundlePrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPURenderBundlePrototype, this), keys: [ "label", ], }), inspectOptions, ); }, }); const GPUQuerySetPrototype = GPUQuerySet.prototype; ObjectDefineProperty(GPUQuerySetPrototype, privateCustomInspect, { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPUQuerySetPrototype, this), keys: [ "label", "type", "count", ], }), inspectOptions, ); }, }); // Converters webidl.converters["GPUPipelineErrorReason"] = webidl.createEnumConverter( "GPUPipelineErrorReason", [ "validation", "internal", ], ); webidl.converters["GPUPipelineErrorInit"] = webidl.createDictionaryConverter( "GPUPipelineErrorInit", [ { key: "reason", converter: webidl.converters.GPUPipelineErrorReason, required: true, }, ], ); webidl.converters["GPUError"] = webidl.converters.any /* put union here! */; const dictMembersGPUUncapturedErrorEventInit = [ { key: "error", converter: webidl.converters["GPUError"], required: true }, ]; webidl.converters["GPUUncapturedErrorEventInit"] = webidl .createDictionaryConverter( "GPUUncapturedErrorEventInit", // dictMembersEventInit, dictMembersGPUUncapturedErrorEventInit, ); function deviceStartCapture(device) { op_webgpu_device_start_capture(device); } function deviceStopCapture(device) { op_webgpu_device_stop_capture(device); } const denoNsWebGPU = { deviceStartCapture, deviceStopCapture, }; let gpu; function initGPU() { if (!gpu) { gpu = op_create_gpu( webidl.brand, setEventTargetData, GPUUncapturedErrorEvent, GPUPipelineError, ); } } export { denoNsWebGPU, GPU, gpu, GPUAdapter, GPUAdapterInfo, GPUBindGroup, GPUBindGroupLayout, GPUBuffer, GPUBufferUsage, GPUColorWrite, GPUCommandBuffer, GPUCommandEncoder, GPUCompilationInfo, GPUCompilationMessage, GPUComputePassEncoder, GPUComputePipeline, GPUDevice, GPUDeviceLostInfo, GPUError, GPUInternalError, GPUMapMode, GPUOutOfMemoryError, GPUPipelineError, GPUPipelineLayout, GPUQuerySet, GPUQueue, GPURenderBundle, GPURenderBundleEncoder, GPURenderPassEncoder, GPURenderPipeline, GPUSampler, GPUShaderModule, GPUShaderStage, GPUSupportedFeatures, GPUSupportedLimits, GPUTexture, GPUTextureUsage, GPUTextureView, GPUExternalTexture, GPUUncapturedErrorEvent, GPUValidationError, WGSLLanguageFeatures, initGPU, }; ================================================ FILE: deno_webgpu/02_surface.js ================================================ // Copyright 2018-2025 the Deno authors. MIT license. // @ts-check /// /// /// /// import { primordials } from "ext:core/mod.js"; import { GPUCanvasContext, UnsafeWindowSurface } from "ext:core/ops"; const { ObjectDefineProperty, ObjectPrototypeIsPrototypeOf, SymbolFor, } = primordials; import { createFilteredInspectProxy } from "ext:deno_console/01_console.js"; ObjectDefineProperty(GPUCanvasContext, SymbolFor("Deno.privateCustomInspect"), { __proto__: null, value(inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(GPUCanvasContextPrototype, this), keys: [ "canvas", ], }), inspectOptions, ); }, }); const GPUCanvasContextPrototype = GPUCanvasContext.prototype; export { GPUCanvasContext, UnsafeWindowSurface }; ================================================ FILE: deno_webgpu/Cargo.toml ================================================ # Copyright 2018-2025 the Deno authors. MIT license. [package] name = "deno_webgpu" version = "0.181.0" authors = ["the Deno authors"] edition.workspace = true license = "MIT" readme = "README.md" repository = "https://github.com/gfx-rs/wgpu" description = "WebGPU implementation for Deno" [lib] path = "lib.rs" # We make all dependencies conditional on not being wasm, # so the whole workspace can built as wasm. [target.'cfg(not(target_arch = "wasm32"))'.dependencies] wgpu-core = { workspace = true, features = [ "trace", "replay", "serde", "strict_asserts", "wgsl", "gles", ] } wgpu-types = { workspace = true, features = ["serde", "std"] } deno_core.workspace = true deno_error.workspace = true serde = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["full"] } raw-window-handle.workspace = true thiserror.workspace = true indexmap.workspace = true serde_json.workspace = true deno_unsync.workspace = true # Apple Platforms # # We want the Metal backend. [target.'cfg(target_vendor = "apple")'.dependencies] wgpu-core = { workspace = true, features = ["metal"] } # Windows # # We want the DX12 backend. [target.'cfg(windows)'.dependencies] wgpu-core = { workspace = true, features = ["dx12"] } # Windows and Unix (not Emscripten) # # We want the Vulkan backend. [target.'cfg(any(windows, all(unix, not(target_os = "emscripten"))))'.dependencies] wgpu-core = { workspace = true, features = ["vulkan"] } ================================================ FILE: deno_webgpu/LICENSE.md ================================================ MIT License Copyright 2018-2024 the Deno authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: deno_webgpu/README.md ================================================ # deno_webgpu This op crate implements the WebGPU API as defined in https://gpuweb.github.io/gpuweb/ in Deno. The implementation targets the spec draft as of March 31, 2024. The spec is still very much in flux. This extension tries to stay up to date with the spec, but is constrained by the features implemented in our GPU backend library [wgpu](https://github.com/gfx-rs/wgpu). The spec is still very bare bones, and is still missing many details. As the spec becomes more concrete, we will implement to follow the spec more closely. In addition, setting the `DENO_WEBGPU_TRACE` environmental variable will output a [wgpu trace](https://github.com/gfx-rs/wgpu/wiki/Debugging-wgpu-Applications#tracing-infrastructure) to the specified directory. This op crate is tested primarily by running the [WebGPU conformance test suite](https://github.com/gpuweb/cts) using `wgpu`'s [`cts_runner`](https://github.com/gfx-rs/wgpu/blob/trunk/README.md#webgpu-conformance-test-suite). `cts_runner` also has a few [directed tests](https://github.com/gfx-rs/wgpu/tree/trunk/cts_runner/tests) to fill in missing coverage. GPU availability in GitHub CI is limited, so some configurations rely on software like DX WARP & Vulkan lavapipe. ## Links Specification: https://gpuweb.github.io/gpuweb/ Design documents: https://github.com/gpuweb/gpuweb/tree/main/design Conformance tests suite: https://github.com/gpuweb/cts WebGPU examples for Deno: https://github.com/crowlKats/webgpu-examples wgpu-users matrix channel: https://matrix.to/#/#wgpu-users:matrix.org ================================================ FILE: deno_webgpu/adapter.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::ops::BitOr; use std::rc::Rc; use deno_core::cppgc::SameObject; use deno_core::op2; use deno_core::v8; use deno_core::GarbageCollected; use deno_core::OpState; use deno_core::V8TaskSpawner; use deno_core::WebIDL; use super::device::GPUDevice; use super::device::DEVICE_EXTERNAL_MEMORY_SIZE; use super::queue::GPUQueue; use crate::error::GPUGenericError; use crate::webidl::GPUFeatureName; use crate::Instance; #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPURequestAdapterOptions { #[webidl(default = "core".into())] pub feature_level: String, pub power_preference: Option, #[webidl(default = false)] pub force_fallback_adapter: bool, } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUPowerPreference { LowPower, HighPerformance, } #[derive(WebIDL)] #[webidl(dictionary)] struct GPUDeviceDescriptor { #[webidl(default = String::new())] label: String, #[webidl(default = vec![])] required_features: Vec, #[webidl(default = Default::default())] #[options(enforce_range = true)] required_limits: indexmap::IndexMap>, } pub struct GPUAdapter { pub instance: Instance, pub id: wgpu_core::id::AdapterId, pub features: SameObject, pub limits: SameObject, pub info: Rc>, } impl Drop for GPUAdapter { fn drop(&mut self) { self.instance.adapter_drop(self.id); } } impl GarbageCollected for GPUAdapter { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUAdapter" } } #[op2] impl GPUAdapter { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[global] fn info(&self, scope: &mut v8::HandleScope) -> v8::Global { self.info.get(scope, |_| { let info = self.instance.adapter_get_info(self.id); GPUAdapterInfo { info } }) } #[getter] #[global] fn features(&self, scope: &mut v8::HandleScope) -> v8::Global { self.features.get(scope, |scope| { let features = self.instance.adapter_features(self.id); // Only expose WebGPU features, not wgpu native-only features let features = features & wgpu_types::Features::all_webgpu_mask(); GPUSupportedFeatures::new(scope, features) }) } #[getter] #[global] fn limits(&self, scope: &mut v8::HandleScope) -> v8::Global { self.limits.get(scope, |_| { let adapter_limits = self.instance.adapter_limits(self.id); GPUSupportedLimits(adapter_limits) }) } #[async_method(fake)] #[global] fn request_device( &self, state: &mut OpState, scope: &mut v8::HandleScope, #[webidl] descriptor: GPUDeviceDescriptor, ) -> Result, CreateDeviceError> { let supported_features = self.instance.adapter_features(self.id); let required_features = descriptor .required_features .iter() .map(|f| wgpu_types::Features::from(*f)) .fold(wgpu_types::Features::empty(), BitOr::bitor); // External textures are a required part of WebGPU, and `external-texture` // is not a WebGPU-defined feature. `wgpu` has it behind a feature for now, // because support is not complete. Allow applications to request that // feature even though it is not reported as an adapter-supported feature. // // There is probably not anything useful that Deno applications can do with // external textures, but it is useful to be able to enable it in // `cts_runner`. if !required_features .difference(supported_features | wgpu_types::Features::EXTERNAL_TEXTURE) .is_empty() { return Err(CreateDeviceError::RequiredFeaturesNotASubset); } // When support for compatibility mode is added, this will need to look // at whether the adapter is "compatibility-defaulting" or // "core-defaulting", and choose the appropriate set of defaults. // // Support for compatibility mode is tracked in // https://github.com/gfx-rs/wgpu/issues/8124. let required_limits = serde_json::from_value::( serde_json::to_value(descriptor.required_limits)?, )? .or_better_values_from(&wgpu_types::Limits::default()); let trace = std::env::var_os("DENO_WEBGPU_TRACE") .map(|path| wgpu_types::Trace::Directory(std::path::PathBuf::from(path))) .unwrap_or_default(); let wgpu_descriptor = wgpu_types::DeviceDescriptor { label: crate::transform_label(descriptor.label.clone()), required_features, required_limits, experimental_features: wgpu_types::ExperimentalFeatures::disabled(), memory_hints: Default::default(), trace, }; let (device, queue) = self.instance.adapter_request_device( self.id, &wgpu_descriptor, None, None, )?; // Associate external memory with the device to encourage V8 to garbage // collect devices promptly. scope .adjust_amount_of_external_allocated_memory(DEVICE_EXTERNAL_MEMORY_SIZE); let spawner = state.borrow::().clone(); let lost_resolver = v8::PromiseResolver::new(scope).unwrap(); let lost_promise = lost_resolver.get_promise(scope); let error_handler = Rc::new(super::error::DeviceErrorHandler::new( v8::Global::new(scope, lost_resolver), spawner, )); // Create the queue object eagerly so that the wgpu-core queue resource // gets cleaned up when the device is garbage collected, even if JS code // never accesses the queue property. let queue_obj = deno_core::cppgc::make_cppgc_object( scope, GPUQueue { instance: self.instance.clone(), error_handler: error_handler.clone(), label: descriptor.label.clone(), id: queue, device, }, ); let queue_obj = v8::Global::new(scope, queue_obj); let device = GPUDevice { instance: self.instance.clone(), id: device, label: descriptor.label, queue_obj, adapter_info: self.info.clone(), error_handler, adapter: self.id, lost_promise: v8::Global::new(scope, lost_promise), limits: SameObject::new(), features: SameObject::new(), weak: std::sync::OnceLock::new(), }; let device = deno_core::cppgc::make_cppgc_object(scope, device); let weak_device = v8::Weak::new(scope, device); let event_target_setup = state.borrow::(); let webidl_brand = v8::Local::new(scope, event_target_setup.brand.clone()); device.set(scope, webidl_brand, webidl_brand); let set_event_target_data = v8::Local::new(scope, event_target_setup.set_event_target_data.clone()) .cast::(); let null = v8::null(scope); set_event_target_data.call(scope, null.into(), &[device.into()]); let finalizer = v8::Weak::with_finalizer( scope, device, Box::new(move |isolate| { isolate.adjust_amount_of_external_allocated_memory( -DEVICE_EXTERNAL_MEMORY_SIZE, ); }), ); // Now that the device is fully constructed, give the error handler a // weak reference to it, and store the finalizer weak reference. let device = device.cast::(); let device_ref = deno_core::cppgc::try_unwrap_cppgc_object::(scope, device) .unwrap(); device_ref.error_handler.set_device(weak_device); device_ref.weak.set(finalizer).unwrap(); Ok(v8::Global::new(scope, device)) } } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum CreateDeviceError { #[class(type)] #[error("requiredFeatures must be a subset of the adapter features")] RequiredFeaturesNotASubset, #[class(inherit)] #[error(transparent)] Serde(#[from] serde_json::Error), #[class("DOMExceptionOperationError")] #[error(transparent)] Device(#[from] wgpu_core::instance::RequestDeviceError), } pub struct GPUSupportedLimits(pub wgpu_types::Limits); impl GarbageCollected for GPUSupportedLimits { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUSupportedLimits" } } #[op2] impl GPUSupportedLimits { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] fn maxTextureDimension1D(&self) -> u32 { self.0.max_texture_dimension_1d } #[getter] fn maxTextureDimension2D(&self) -> u32 { self.0.max_texture_dimension_2d } #[getter] fn maxTextureDimension3D(&self) -> u32 { self.0.max_texture_dimension_3d } #[getter] fn maxTextureArrayLayers(&self) -> u32 { self.0.max_texture_array_layers } #[getter] fn maxBindGroups(&self) -> u32 { self.0.max_bind_groups } // TODO(@crowlKats): support max_bind_groups_plus_vertex_buffers #[getter] fn maxBindingsPerBindGroup(&self) -> u32 { self.0.max_bindings_per_bind_group } #[getter] fn maxDynamicUniformBuffersPerPipelineLayout(&self) -> u32 { self.0.max_dynamic_uniform_buffers_per_pipeline_layout } #[getter] fn maxDynamicStorageBuffersPerPipelineLayout(&self) -> u32 { self.0.max_dynamic_storage_buffers_per_pipeline_layout } #[getter] fn maxSampledTexturesPerShaderStage(&self) -> u32 { self.0.max_sampled_textures_per_shader_stage } #[getter] fn maxSamplersPerShaderStage(&self) -> u32 { self.0.max_samplers_per_shader_stage } #[getter] fn maxStorageBuffersPerShaderStage(&self) -> u32 { self.0.max_storage_buffers_per_shader_stage } #[getter] fn maxStorageBuffersInVertexStage(&self) -> u32 { // TODO(https://github.com/gfx-rs/wgpu/issues/8748): InVertexStage limit // not implemented; return the PerShaderStage limit. self.0.max_storage_buffers_per_shader_stage } #[getter] fn maxStorageBuffersInFragmentStage(&self) -> u32 { // TODO(https://github.com/gfx-rs/wgpu/issues/8748): InFragmentStage limit // not implemented; return the PerShaderStage limit. self.0.max_storage_buffers_per_shader_stage } #[getter] fn maxStorageTexturesPerShaderStage(&self) -> u32 { self.0.max_storage_textures_per_shader_stage } #[getter] fn maxStorageTexturesInVertexStage(&self) -> u32 { // TODO(https://github.com/gfx-rs/wgpu/issues/8748): InVertexStage limit // not implemented; return the PerShaderStage limit. self.0.max_storage_textures_per_shader_stage } #[getter] fn maxStorageTexturesInFragmentStage(&self) -> u32 { // TODO(https://github.com/gfx-rs/wgpu/issues/8748): InFragmentStage limit // not implemented; return the PerShaderStage limit. self.0.max_storage_textures_per_shader_stage } #[getter] fn maxUniformBuffersPerShaderStage(&self) -> u32 { self.0.max_uniform_buffers_per_shader_stage } #[getter] #[number] fn maxUniformBufferBindingSize(&self) -> u64 { self.0.max_uniform_buffer_binding_size } #[getter] #[number] fn maxStorageBufferBindingSize(&self) -> u64 { self.0.max_storage_buffer_binding_size } #[getter] fn minUniformBufferOffsetAlignment(&self) -> u32 { self.0.min_uniform_buffer_offset_alignment } #[getter] fn minStorageBufferOffsetAlignment(&self) -> u32 { self.0.min_storage_buffer_offset_alignment } #[getter] fn maxVertexBuffers(&self) -> u32 { self.0.max_vertex_buffers } #[getter] #[number] fn maxBufferSize(&self) -> u64 { self.0.max_buffer_size } #[getter] fn maxVertexAttributes(&self) -> u32 { self.0.max_vertex_attributes } #[getter] fn maxVertexBufferArrayStride(&self) -> u32 { self.0.max_vertex_buffer_array_stride } #[getter] fn maxInterStageShaderVariables(&self) -> u32 { self.0.max_inter_stage_shader_variables } #[getter] fn maxColorAttachments(&self) -> u32 { self.0.max_color_attachments } #[getter] fn maxColorAttachmentBytesPerSample(&self) -> u32 { self.0.max_color_attachment_bytes_per_sample } #[getter] fn maxComputeWorkgroupStorageSize(&self) -> u32 { self.0.max_compute_workgroup_storage_size } #[getter] fn maxComputeInvocationsPerWorkgroup(&self) -> u32 { self.0.max_compute_invocations_per_workgroup } #[getter] fn maxComputeWorkgroupSizeX(&self) -> u32 { self.0.max_compute_workgroup_size_x } #[getter] fn maxComputeWorkgroupSizeY(&self) -> u32 { self.0.max_compute_workgroup_size_y } #[getter] fn maxComputeWorkgroupSizeZ(&self) -> u32 { self.0.max_compute_workgroup_size_z } #[getter] fn maxComputeWorkgroupsPerDimension(&self) -> u32 { self.0.max_compute_workgroups_per_dimension } } pub struct GPUSupportedFeatures(v8::Global); impl GarbageCollected for GPUSupportedFeatures { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUSupportedFeatures" } } impl GPUSupportedFeatures { pub fn new( scope: &mut v8::HandleScope, features: wgpu_types::Features, ) -> Self { let set = v8::Set::new(scope); for feature in features.iter() { let key = v8::String::new(scope, feature.as_str().unwrap()).unwrap(); set.add(scope, key.into()); } Self(v8::Global::new(scope, >::from(set))) } } #[op2] impl GPUSupportedFeatures { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[global] #[symbol("setlike_set")] fn set(&self) -> v8::Global { self.0.clone() } } pub struct GPUAdapterInfo { pub info: wgpu_types::AdapterInfo, } impl GarbageCollected for GPUAdapterInfo { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUAdapterInfo" } } #[op2] impl GPUAdapterInfo { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn vendor(&self) -> String { self.info.vendor.to_string() } #[getter] #[string] fn architecture(&self) -> &'static str { "" // TODO(https://github.com/gfx-rs/wgpu/issues/8649): implement when wgpu has architecture detection } #[getter] #[string] fn device(&self) -> String { self.info.device.to_string() } #[getter] #[string] fn description(&self) -> String { self.info.name.clone() } #[getter] fn subgroup_min_size(&self) -> u32 { self.info.subgroup_min_size } #[getter] fn subgroup_max_size(&self) -> u32 { self.info.subgroup_max_size } #[getter] fn is_fallback_adapter(&self) -> bool { self.info.device_type == wgpu_types::DeviceType::Cpu } } ================================================ FILE: deno_webgpu/bind_group.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::borrow::Cow; use deno_core::cppgc::Ptr; use deno_core::op2; use deno_core::v8::HandleScope; use deno_core::v8::Local; use deno_core::v8::Value; use deno_core::webidl::ContextFn; use deno_core::webidl::WebIdlConverter; use deno_core::webidl::WebIdlError; use deno_core::webidl::WebIdlInterfaceConverter; use deno_core::GarbageCollected; use deno_core::WebIDL; use crate::buffer::GPUBuffer; use crate::error::GPUGenericError; use crate::sampler::GPUSampler; use crate::texture::GPUExternalTexture; use crate::texture::GPUTexture; use crate::texture::GPUTextureView; use crate::Instance; pub struct GPUBindGroup { pub instance: Instance, pub id: wgpu_core::id::BindGroupId, pub label: String, } impl Drop for GPUBindGroup { fn drop(&mut self) { self.instance.bind_group_drop(self.id); } } impl WebIdlInterfaceConverter for GPUBindGroup { const NAME: &'static str = "GPUBindGroup"; } impl GarbageCollected for GPUBindGroup { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUBindGroup" } } #[op2] impl GPUBindGroup { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUBindGroupDescriptor { #[webidl(default = String::new())] pub label: String, pub layout: Ptr, pub entries: Vec, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUBindGroupEntry { #[options(enforce_range = true)] pub binding: u32, pub resource: GPUBindingResource, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUBufferBinding { pub buffer: Ptr, #[webidl(default = 0)] #[options(enforce_range = true)] pub offset: u64, #[options(enforce_range = true)] pub size: Option, } pub(crate) enum GPUBindingResource { Sampler(Ptr), Texture(Ptr), TextureView(Ptr), Buffer(Ptr), BufferBinding(GPUBufferBinding), ExternalTexture(Ptr), } impl<'a> WebIdlConverter<'a> for GPUBindingResource { type Options = (); fn convert<'b>( scope: &mut HandleScope<'a>, value: Local<'a, Value>, prefix: Cow<'static, str>, context: ContextFn<'b>, options: &Self::Options, ) -> Result { >::convert( scope, value, prefix.clone(), context.borrowed(), options, ) .map(Self::Sampler) .or_else(|_| { >::convert( scope, value, prefix.clone(), context.borrowed(), options, ) .map(Self::Texture) }) .or_else(|_| { >::convert( scope, value, prefix.clone(), context.borrowed(), options, ) .map(Self::TextureView) }) .or_else(|_| { >::convert( scope, value, prefix.clone(), context.borrowed(), options, ) .map(Self::Buffer) }) .or_else(|_| { >::convert( scope, value, prefix.clone(), context.borrowed(), options, ) .map(Self::ExternalTexture) }) .or_else(|_| { GPUBufferBinding::convert(scope, value, prefix, context, options) .map(Self::BufferBinding) }) } } ================================================ FILE: deno_webgpu/bind_group_layout.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use deno_core::op2; use deno_core::GarbageCollected; use deno_core::WebIDL; use crate::error::GPUGenericError; use crate::texture::GPUTextureViewDimension; use crate::webidl::GPUShaderStageFlags; use crate::Instance; pub struct GPUBindGroupLayout { pub instance: Instance, pub id: wgpu_core::id::BindGroupLayoutId, pub label: String, } impl Drop for GPUBindGroupLayout { fn drop(&mut self) { self.instance.bind_group_layout_drop(self.id); } } impl deno_core::webidl::WebIdlInterfaceConverter for GPUBindGroupLayout { const NAME: &'static str = "GPUBindGroupLayout"; } impl GarbageCollected for GPUBindGroupLayout { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUBindGroupLayout" } } #[op2] impl GPUBindGroupLayout { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUBindGroupLayoutDescriptor { #[webidl(default = String::new())] pub label: String, pub entries: Vec, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUBindGroupLayoutEntry { #[options(enforce_range = true)] pub binding: u32, pub visibility: GPUShaderStageFlags, pub buffer: Option, pub sampler: Option, pub texture: Option, pub storage_texture: Option, pub external_texture: Option, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUBufferBindingLayout { #[webidl(default = GPUBufferBindingType::Uniform)] pub r#type: GPUBufferBindingType, #[webidl(default = false)] pub has_dynamic_offset: bool, #[webidl(default = 0)] pub min_binding_size: u64, } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUBufferBindingType { Uniform, Storage, ReadOnlyStorage, } impl From for wgpu_types::BufferBindingType { fn from(value: GPUBufferBindingType) -> Self { match value { GPUBufferBindingType::Uniform => Self::Uniform, GPUBufferBindingType::Storage => Self::Storage { read_only: false }, GPUBufferBindingType::ReadOnlyStorage => { Self::Storage { read_only: true } } } } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUSamplerBindingLayout { #[webidl(default = GPUSamplerBindingType::Filtering)] pub r#type: GPUSamplerBindingType, } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUSamplerBindingType { Filtering, NonFiltering, Comparison, } impl From for wgpu_types::SamplerBindingType { fn from(value: GPUSamplerBindingType) -> Self { match value { GPUSamplerBindingType::Filtering => Self::Filtering, GPUSamplerBindingType::NonFiltering => Self::NonFiltering, GPUSamplerBindingType::Comparison => Self::Comparison, } } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUTextureBindingLayout { #[webidl(default = GPUTextureSampleType::Float)] pub sample_type: GPUTextureSampleType, #[webidl(default = GPUTextureViewDimension::D2)] pub view_dimension: GPUTextureViewDimension, #[webidl(default = false)] pub multisampled: bool, } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUTextureSampleType { Float, UnfilterableFloat, Depth, Sint, Uint, } impl From for wgpu_types::TextureSampleType { fn from(value: GPUTextureSampleType) -> Self { match value { GPUTextureSampleType::Float => Self::Float { filterable: true }, GPUTextureSampleType::UnfilterableFloat => { Self::Float { filterable: false } } GPUTextureSampleType::Depth => Self::Depth, GPUTextureSampleType::Sint => Self::Sint, GPUTextureSampleType::Uint => Self::Uint, } } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUStorageTextureBindingLayout { #[webidl(default = GPUStorageTextureAccess::WriteOnly)] pub access: GPUStorageTextureAccess, pub format: super::texture::GPUTextureFormat, #[webidl(default = GPUTextureViewDimension::D2)] pub view_dimension: GPUTextureViewDimension, } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUStorageTextureAccess { WriteOnly, ReadOnly, ReadWrite, } impl From for wgpu_types::StorageTextureAccess { fn from(value: GPUStorageTextureAccess) -> Self { match value { GPUStorageTextureAccess::WriteOnly => Self::WriteOnly, GPUStorageTextureAccess::ReadOnly => Self::ReadOnly, GPUStorageTextureAccess::ReadWrite => Self::ReadWrite, } } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUExternalTextureBindingLayout {} ================================================ FILE: deno_webgpu/buffer.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::cell::RefCell; use std::rc::Rc; use std::time::Duration; use deno_core::futures::channel::oneshot; use deno_core::op2; use deno_core::v8; use deno_core::webidl::WebIdlInterfaceConverter; use deno_core::GarbageCollected; use deno_core::WebIDL; use deno_error::JsErrorBox; use wgpu_core::device::HostMap as MapMode; use crate::error::GPUGenericError; use crate::Instance; #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUBufferDescriptor { #[webidl(default = String::new())] pub label: String, pub size: u64, #[options(enforce_range = true)] pub usage: u32, #[webidl(default = false)] pub mapped_at_creation: bool, } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum BufferError { #[class(generic)] #[error(transparent)] Canceled(#[from] oneshot::Canceled), #[class("DOMExceptionOperationError")] #[error(transparent)] Access(#[from] wgpu_core::resource::BufferAccessError), #[class("DOMExceptionOperationError")] #[error("{0}")] Operation(&'static str), #[class(inherit)] #[error(transparent)] Other(#[from] JsErrorBox), } pub struct GPUBuffer { pub instance: Instance, pub error_handler: super::error::ErrorHandler, pub id: wgpu_core::id::BufferId, pub device: wgpu_core::id::DeviceId, pub label: String, pub size: u64, pub usage: u32, pub map_state: RefCell<&'static str>, pub map_mode: RefCell>, pub mapped_js_buffers: RefCell>>, } impl Drop for GPUBuffer { fn drop(&mut self) { self.instance.buffer_drop(self.id); } } impl WebIdlInterfaceConverter for GPUBuffer { const NAME: &'static str = "GPUBuffer"; } impl GarbageCollected for GPUBuffer { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUBuffer" } } #[op2] impl GPUBuffer { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } #[getter] #[number] fn size(&self) -> u64 { self.size } #[getter] fn usage(&self) -> u32 { self.usage } #[getter] #[string] fn map_state(&self) -> &'static str { *self.map_state.borrow() } // In the successful case, the promise should resolve to undefined, but // `#[undefined]` does not seem to work here. // https://github.com/denoland/deno/issues/29603 #[async_method] async fn map_async( &self, #[webidl(options(enforce_range = true))] mode: u32, #[webidl(default = 0)] offset: u64, #[webidl] size: Option, ) -> Result<(), BufferError> { let read_mode = (mode & 0x0001) == 0x0001; let write_mode = (mode & 0x0002) == 0x0002; if (read_mode && write_mode) || (!read_mode && !write_mode) { return Err(BufferError::Operation( "exactly one of READ or WRITE map mode must be set", )); } let mode = if read_mode { MapMode::Read } else { assert!(write_mode); MapMode::Write }; { *self.map_state.borrow_mut() = "pending"; } let (sender, receiver) = oneshot::channel::(); { let callback = Box::new(move |status| { sender.send(status).unwrap(); }); let err = self .instance .buffer_map_async( self.id, offset, size, wgpu_core::resource::BufferMapOperation { host: mode, callback: Some(callback), }, ) .err(); if err.is_some() { self.error_handler.push_error(err); return Err(BufferError::Operation("validation error occurred")); } } let done = Rc::new(RefCell::new(false)); let done_ = done.clone(); let device_poll_fut = async move { while !*done.borrow() { { self .instance .device_poll(self.device, wgpu_types::PollType::wait_indefinitely()) .unwrap(); } tokio::time::sleep(Duration::from_millis(10)).await; } Ok::<(), BufferError>(()) }; let receiver_fut = async move { receiver.await??; let mut done = done_.borrow_mut(); *done = true; Ok::<(), BufferError>(()) }; tokio::try_join!(device_poll_fut, receiver_fut)?; *self.map_state.borrow_mut() = "mapped"; *self.map_mode.borrow_mut() = Some(mode); Ok(()) } fn get_mapped_range<'s>( &self, scope: &mut v8::HandleScope<'s>, #[webidl(default = 0)] offset: u64, #[webidl] size: Option, ) -> Result, BufferError> { let (slice_pointer, range_size) = self .instance .buffer_get_mapped_range(self.id, offset, size) .map_err(BufferError::Access)?; let mode = self.map_mode.borrow(); let mode = mode.as_ref().unwrap(); let bs = if mode == &MapMode::Write { unsafe extern "C" fn noop_deleter_callback( _data: *mut std::ffi::c_void, _byte_length: usize, _deleter_data: *mut std::ffi::c_void, ) { } // SAFETY: creating a backing store from the pointer and length provided by wgpu unsafe { v8::ArrayBuffer::new_backing_store_from_ptr( slice_pointer.as_ptr() as _, range_size as usize, noop_deleter_callback, std::ptr::null_mut(), ) } } else { // SAFETY: creating a vector from the pointer and length provided by wgpu let slice = unsafe { std::slice::from_raw_parts(slice_pointer.as_ptr(), range_size as usize) }; v8::ArrayBuffer::new_backing_store_from_vec(slice.to_vec()) }; let shared_bs = bs.make_shared(); let ab = v8::ArrayBuffer::with_backing_store(scope, &shared_bs); if mode == &MapMode::Write { self .mapped_js_buffers .borrow_mut() .push(v8::Global::new(scope, ab)); } Ok(ab) } #[nofast] #[undefined] fn unmap(&self, scope: &mut v8::HandleScope) -> Result<(), BufferError> { for ab in self.mapped_js_buffers.replace(vec![]) { let ab = ab.open(scope); ab.detach(None); } self .instance .buffer_unmap(self.id) .map_err(BufferError::Access)?; *self.map_state.borrow_mut() = "unmapped"; Ok(()) } #[fast] #[undefined] fn destroy(&self) { self.instance.buffer_destroy(self.id); } } ================================================ FILE: deno_webgpu/byow.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::cell::RefCell; use std::ffi::c_void; #[cfg(any( target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] use std::ptr::NonNull; use deno_core::cppgc::SameObject; use deno_core::op2; use deno_core::v8; use deno_core::v8::Local; use deno_core::v8::Value; use deno_core::FromV8; use deno_core::GarbageCollected; use deno_core::OpState; use deno_error::JsErrorBox; use crate::surface::GPUCanvasContext; #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum ByowError { #[cfg(not(any( target_os = "macos", target_os = "windows", target_os = "linux", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", )))] #[class(type)] #[error("Unsupported platform")] Unsupported, #[class(type)] #[error( "Cannot create surface outside of WebGPU context. Did you forget to call `navigator.gpu.requestAdapter()`?" )] WebGPUNotInitiated, #[class(type)] #[error("Invalid parameters")] InvalidParameters, #[class(generic)] #[error(transparent)] CreateSurface(wgpu_core::instance::CreateSurfaceError), #[cfg(target_os = "windows")] #[class(type)] #[error("Invalid system on Windows")] InvalidSystem, #[cfg(target_os = "macos")] #[class(type)] #[error("Invalid system on macOS")] InvalidSystem, #[cfg(any( target_os = "linux", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] #[class(type)] #[error("Invalid system on Linux/BSD")] InvalidSystem, #[cfg(any( target_os = "windows", target_os = "linux", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] #[class(type)] #[error("window is null")] NullWindow, #[cfg(any( target_os = "linux", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] #[class(type)] #[error("display is null")] NullDisplay, #[cfg(target_os = "macos")] #[class(type)] #[error("ns_view is null")] NSViewDisplay, } // TODO(@littledivy): This will extend `OffscreenCanvas` when we add it. pub struct UnsafeWindowSurface { pub id: wgpu_core::id::SurfaceId, pub width: RefCell, pub height: RefCell, pub context: SameObject, } impl GarbageCollected for UnsafeWindowSurface { fn get_name(&self) -> &'static std::ffi::CStr { c"UnsafeWindowSurface" } } #[op2] impl UnsafeWindowSurface { #[constructor] #[cppgc] fn new( state: &mut OpState, #[from_v8] options: UnsafeWindowSurfaceOptions, ) -> Result { let instance = state .try_borrow::() .ok_or(ByowError::WebGPUNotInitiated)?; // Security note: // // The `window_handle` and `display_handle` options are pointers to // platform-specific window handles. // // The code below works under the assumption that: // // - handles can only be created by the FFI interface which // enforces --allow-ffi. // // - `*const c_void` deserializes null and v8::External. // // - Only FFI can export v8::External to user code. if options.window_handle.is_null() { return Err(ByowError::InvalidParameters); } let (win_handle, display_handle) = raw_window( options.system, options.window_handle, options.display_handle, )?; // SAFETY: see above comment let id = unsafe { instance .instance_create_surface(display_handle, win_handle, None) .map_err(ByowError::CreateSurface)? }; Ok(UnsafeWindowSurface { id, width: RefCell::new(options.width), height: RefCell::new(options.height), context: SameObject::new(), }) } #[global] fn get_context( &self, #[this] this: v8::Global, scope: &mut v8::HandleScope, ) -> v8::Global { self.context.get(scope, |_| GPUCanvasContext { surface_id: self.id, width: self.width.clone(), height: self.height.clone(), config: RefCell::new(None), texture: RefCell::new(None), canvas: this, }) } #[nofast] fn present(&self, scope: &mut v8::HandleScope) -> Result<(), JsErrorBox> { let Some(context) = self.context.try_unwrap(scope) else { return Err(JsErrorBox::type_error("getContext was never called")); }; context.present().map_err(JsErrorBox::from_err) } #[fast] fn resize(&self, width: u32, height: u32, scope: &mut v8::HandleScope) { self.width.replace(width); self.height.replace(height); let Some(context) = self.context.try_unwrap(scope) else { return; }; context.resize_configure(width, height); } } struct UnsafeWindowSurfaceOptions { system: UnsafeWindowSurfaceSystem, window_handle: *const c_void, display_handle: *const c_void, width: u32, height: u32, } #[derive(Eq, PartialEq)] enum UnsafeWindowSurfaceSystem { Cocoa, Win32, X11, Wayland, } impl<'a> FromV8<'a> for UnsafeWindowSurfaceOptions { type Error = JsErrorBox; fn from_v8( scope: &mut v8::HandleScope<'a>, value: Local<'a, Value>, ) -> Result { let obj = value .try_cast::() .map_err(|_| JsErrorBox::type_error("is not an object"))?; let key = v8::String::new(scope, "system").unwrap(); let val = obj .get(scope, key.into()) .ok_or_else(|| JsErrorBox::type_error("missing field 'system'"))?; let s = String::from_v8(scope, val).unwrap(); let system = match s.as_str() { "cocoa" => UnsafeWindowSurfaceSystem::Cocoa, "win32" => UnsafeWindowSurfaceSystem::Win32, "x11" => UnsafeWindowSurfaceSystem::X11, "wayland" => UnsafeWindowSurfaceSystem::Wayland, _ => { return Err(JsErrorBox::type_error(format!( "Invalid system kind '{s}'" ))); } }; let key = v8::String::new(scope, "windowHandle").unwrap(); let val = obj .get(scope, key.into()) .ok_or_else(|| JsErrorBox::type_error("missing field 'windowHandle'"))?; let Some(window_handle) = deno_core::_ops::to_external_option(&val) else { return Err(JsErrorBox::type_error("expected external")); }; let key = v8::String::new(scope, "displayHandle").unwrap(); let val = obj .get(scope, key.into()) .ok_or_else(|| JsErrorBox::type_error("missing field 'displayHandle'"))?; let Some(display_handle) = deno_core::_ops::to_external_option(&val) else { return Err(JsErrorBox::type_error("expected external")); }; let key = v8::String::new(scope, "width").unwrap(); let val = obj .get(scope, key.into()) .ok_or_else(|| JsErrorBox::type_error("missing field 'width'"))?; let width = deno_core::convert::Number::::from_v8(scope, val)?.0; let key = v8::String::new(scope, "height").unwrap(); let val = obj .get(scope, key.into()) .ok_or_else(|| JsErrorBox::type_error("missing field 'height'"))?; let height = deno_core::convert::Number::::from_v8(scope, val)?.0; Ok(Self { system, window_handle, display_handle, width, height, }) } } type RawHandles = ( raw_window_handle::RawWindowHandle, Option, ); #[cfg(target_os = "macos")] fn raw_window( system: UnsafeWindowSurfaceSystem, _ns_window: *const c_void, ns_view: *const c_void, ) -> Result { if system != UnsafeWindowSurfaceSystem::Cocoa { return Err(ByowError::InvalidSystem); } let win_handle = raw_window_handle::RawWindowHandle::AppKit( raw_window_handle::AppKitWindowHandle::new( NonNull::new(ns_view as *mut c_void).ok_or(ByowError::NSViewDisplay)?, ), ); let display_handle = raw_window_handle::RawDisplayHandle::AppKit( raw_window_handle::AppKitDisplayHandle::new(), ); Ok((win_handle, Some(display_handle))) } #[cfg(target_os = "windows")] fn raw_window( system: UnsafeWindowSurfaceSystem, window: *const c_void, hinstance: *const c_void, ) -> Result { use raw_window_handle::WindowsDisplayHandle; if system != UnsafeWindowSurfaceSystem::Win32 { return Err(ByowError::InvalidSystem); } let win_handle = { let mut handle = raw_window_handle::Win32WindowHandle::new( std::num::NonZeroIsize::new(window as isize) .ok_or(ByowError::NullWindow)?, ); handle.hinstance = std::num::NonZeroIsize::new(hinstance as isize); raw_window_handle::RawWindowHandle::Win32(handle) }; let display_handle = raw_window_handle::RawDisplayHandle::Windows(WindowsDisplayHandle::new()); Ok((win_handle, Some(display_handle))) } #[cfg(any( target_os = "linux", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] fn raw_window( system: UnsafeWindowSurfaceSystem, window: *const c_void, display: *const c_void, ) -> Result { let (win_handle, display_handle); if system == UnsafeWindowSurfaceSystem::X11 { win_handle = raw_window_handle::RawWindowHandle::Xlib( raw_window_handle::XlibWindowHandle::new(window as *mut c_void as _), ); display_handle = raw_window_handle::RawDisplayHandle::Xlib( raw_window_handle::XlibDisplayHandle::new( NonNull::new(display as *mut c_void), 0, ), ); } else if system == UnsafeWindowSurfaceSystem::Wayland { win_handle = raw_window_handle::RawWindowHandle::Wayland( raw_window_handle::WaylandWindowHandle::new( NonNull::new(window as *mut c_void).ok_or(ByowError::NullWindow)?, ), ); display_handle = raw_window_handle::RawDisplayHandle::Wayland( raw_window_handle::WaylandDisplayHandle::new( NonNull::new(display as *mut c_void).ok_or(ByowError::NullDisplay)?, ), ); } else { return Err(ByowError::InvalidSystem); } Ok((win_handle, Some(display_handle))) } #[cfg(not(any( target_os = "macos", target_os = "windows", target_os = "linux", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", )))] fn raw_window( _system: UnsafeWindowSurfaceSystem, _window: *const c_void, _display: *const c_void, ) -> Result { Err(ByowError::Unsupported) } ================================================ FILE: deno_webgpu/command_buffer.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use deno_core::op2; use deno_core::GarbageCollected; use deno_core::WebIDL; use crate::error::GPUGenericError; use crate::Instance; pub struct GPUCommandBuffer { pub instance: Instance, pub id: wgpu_core::id::CommandBufferId, pub label: String, } impl Drop for GPUCommandBuffer { fn drop(&mut self) { self.instance.command_buffer_drop(self.id); } } impl deno_core::webidl::WebIdlInterfaceConverter for GPUCommandBuffer { const NAME: &'static str = "GPUCommandBuffer"; } impl GarbageCollected for GPUCommandBuffer { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUCommandBuffer" } } #[op2] impl GPUCommandBuffer { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUCommandBufferDescriptor { #[webidl(default = String::new())] pub label: String, } ================================================ FILE: deno_webgpu/command_encoder.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::borrow::Cow; use std::cell::RefCell; use std::num::NonZero; #[cfg(target_vendor = "apple")] use std::sync::OnceLock; use deno_core::cppgc::Ptr; use deno_core::op2; use deno_core::v8; use deno_core::webidl::{IntOptions, WebIdlConverter, WebIdlError}; use deno_core::GarbageCollected; use deno_core::WebIDL; use deno_error::JsErrorBox; use wgpu_core::command::PassChannel; use wgpu_types::{BufferAddress, TexelCopyBufferInfo}; use crate::buffer::GPUBuffer; use crate::command_buffer::GPUCommandBuffer; use crate::compute_pass::GPUComputePassEncoder; use crate::error::GPUGenericError; use crate::queue::GPUTexelCopyTextureInfo; use crate::render_pass::GPURenderPassEncoder; use crate::webidl::GPUExtent3D; use crate::Instance; pub struct GPUCommandEncoder { pub instance: Instance, pub error_handler: super::error::ErrorHandler, pub id: wgpu_core::id::CommandEncoderId, pub label: String, // Weak reference to the JS object so we can attach a finalizer. // See `GPUDevice::create_command_encoder`. #[cfg(target_vendor = "apple")] pub(crate) weak: OnceLock>, } impl Drop for GPUCommandEncoder { fn drop(&mut self) { self.instance.command_encoder_drop(self.id); } } impl GarbageCollected for GPUCommandEncoder { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUCommandEncoder" } } #[op2] impl GPUCommandEncoder { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } #[required(1)] #[cppgc] fn begin_render_pass( &self, #[webidl] descriptor: crate::render_pass::GPURenderPassDescriptor, ) -> Result { let color_attachments = Cow::Owned( descriptor .color_attachments .into_iter() .map(|attachment| { attachment.into_option().map(|attachment| { wgpu_core::command::RenderPassColorAttachment { view: attachment.view.to_view_id(), depth_slice: attachment.depth_slice, resolve_target: attachment .resolve_target .map(|target| target.to_view_id()), load_op: attachment .load_op .with_default_value(attachment.clear_value.map(Into::into)), store_op: attachment.store_op.into(), } }) }) .collect::>(), ); let depth_stencil_attachment = descriptor.depth_stencil_attachment.map(|attachment| { wgpu_core::command::RenderPassDepthStencilAttachment { view: attachment.view.to_view_id(), depth: PassChannel { load_op: attachment .depth_load_op .map(|load_op| load_op.with_value(attachment.depth_clear_value)), store_op: attachment.depth_store_op.map(Into::into), read_only: attachment.depth_read_only, }, stencil: PassChannel { load_op: attachment.stencil_load_op.map(|load_op| { load_op.with_value(Some(attachment.stencil_clear_value)) }), store_op: attachment.stencil_store_op.map(Into::into), read_only: attachment.stencil_read_only, }, } }); let timestamp_writes = descriptor.timestamp_writes.map(|timestamp_writes| { wgpu_core::command::PassTimestampWrites { query_set: timestamp_writes.query_set.id, beginning_of_pass_write_index: timestamp_writes .beginning_of_pass_write_index, end_of_pass_write_index: timestamp_writes.end_of_pass_write_index, } }); let wgpu_descriptor = wgpu_core::command::RenderPassDescriptor { label: crate::transform_label(descriptor.label.clone()), color_attachments, depth_stencil_attachment: depth_stencil_attachment.as_ref(), timestamp_writes: timestamp_writes.as_ref(), occlusion_query_set: descriptor .occlusion_query_set .map(|query_set| query_set.id), multiview_mask: NonZero::new(descriptor.multiview_mask), }; let (render_pass, err) = self .instance .command_encoder_begin_render_pass(self.id, &wgpu_descriptor); self.error_handler.push_error(err); Ok(GPURenderPassEncoder { instance: self.instance.clone(), error_handler: self.error_handler.clone(), render_pass: RefCell::new(render_pass), label: descriptor.label, }) } #[cppgc] fn begin_compute_pass( &self, #[webidl] descriptor: crate::compute_pass::GPUComputePassDescriptor, ) -> GPUComputePassEncoder { let timestamp_writes = descriptor.timestamp_writes.map(|timestamp_writes| { wgpu_core::command::PassTimestampWrites { query_set: timestamp_writes.query_set.id, beginning_of_pass_write_index: timestamp_writes .beginning_of_pass_write_index, end_of_pass_write_index: timestamp_writes.end_of_pass_write_index, } }); let wgpu_descriptor = wgpu_core::command::ComputePassDescriptor { label: crate::transform_label(descriptor.label.clone()), timestamp_writes, }; let (compute_pass, err) = self .instance .command_encoder_begin_compute_pass(self.id, &wgpu_descriptor); self.error_handler.push_error(err); GPUComputePassEncoder { instance: self.instance.clone(), error_handler: self.error_handler.clone(), compute_pass: RefCell::new(compute_pass), label: descriptor.label, } } #[required(2)] #[undefined] fn copy_buffer_to_buffer<'a>( &self, scope: &mut v8::HandleScope<'a>, #[webidl] source: Ptr, arg2: v8::Local<'a, v8::Value>, arg3: v8::Local<'a, v8::Value>, arg4: v8::Local<'a, v8::Value>, arg5: v8::Local<'a, v8::Value>, ) -> Result<(), WebIdlError> { let prefix = "Failed to execute 'GPUCommandEncoder.copyBufferToBuffer'"; let int_options = IntOptions { clamp: false, enforce_range: true, }; let source_offset: BufferAddress; let destination: Ptr; let destination_offset: BufferAddress; let size: Option; // Note that the last argument to either overload of `copy_buffer_to_buffer` // is optional, so `arg5.is_undefined()` would not work here. if arg4.is_undefined() { // 3-argument overload source_offset = 0; destination = Ptr::::convert( scope, arg2, Cow::Borrowed(prefix), (|| Cow::Borrowed("destination")).into(), &(), )?; destination_offset = 0; size = >::convert( scope, arg3, Cow::Borrowed(prefix), (|| Cow::Borrowed("size")).into(), &int_options, )?; } else { // 5-argument overload source_offset = u64::convert( scope, arg2, Cow::Borrowed(prefix), (|| Cow::Borrowed("sourceOffset")).into(), &int_options, )?; destination = Ptr::::convert( scope, arg3, Cow::Borrowed(prefix), (|| Cow::Borrowed("destination")).into(), &(), )?; destination_offset = u64::convert( scope, arg4, Cow::Borrowed(prefix), (|| Cow::Borrowed("destinationOffset")).into(), &int_options, )?; size = >::convert( scope, arg5, Cow::Borrowed(prefix), (|| Cow::Borrowed("size")).into(), &int_options, )?; } let err = self .instance .command_encoder_copy_buffer_to_buffer( self.id, source.id, source_offset, destination.id, destination_offset, size, ) .err(); self.error_handler.push_error(err); Ok(()) } #[required(3)] #[undefined] fn copy_buffer_to_texture( &self, #[webidl] source: GPUTexelCopyBufferInfo, #[webidl] destination: GPUTexelCopyTextureInfo, #[webidl] copy_size: GPUExtent3D, ) { let source = TexelCopyBufferInfo { buffer: source.buffer.id, layout: wgpu_types::TexelCopyBufferLayout { offset: source.offset, bytes_per_row: source.bytes_per_row, rows_per_image: source.rows_per_image, }, }; let destination = wgpu_types::TexelCopyTextureInfo { texture: destination.texture.id, mip_level: destination.mip_level, origin: destination.origin.into(), aspect: destination.aspect.into(), }; let err = self .instance .command_encoder_copy_buffer_to_texture( self.id, &source, &destination, ©_size.into(), ) .err(); self.error_handler.push_error(err); } #[required(3)] #[undefined] fn copy_texture_to_buffer( &self, #[webidl] source: GPUTexelCopyTextureInfo, #[webidl] destination: GPUTexelCopyBufferInfo, #[webidl] copy_size: GPUExtent3D, ) { let source = wgpu_types::TexelCopyTextureInfo { texture: source.texture.id, mip_level: source.mip_level, origin: source.origin.into(), aspect: source.aspect.into(), }; let destination = TexelCopyBufferInfo { buffer: destination.buffer.id, layout: wgpu_types::TexelCopyBufferLayout { offset: destination.offset, bytes_per_row: destination.bytes_per_row, rows_per_image: destination.rows_per_image, }, }; let err = self .instance .command_encoder_copy_texture_to_buffer( self.id, &source, &destination, ©_size.into(), ) .err(); self.error_handler.push_error(err); } #[required(3)] #[undefined] fn copy_texture_to_texture( &self, #[webidl] source: GPUTexelCopyTextureInfo, #[webidl] destination: GPUTexelCopyTextureInfo, #[webidl] copy_size: GPUExtent3D, ) { let source = wgpu_types::TexelCopyTextureInfo { texture: source.texture.id, mip_level: source.mip_level, origin: source.origin.into(), aspect: source.aspect.into(), }; let destination = wgpu_types::TexelCopyTextureInfo { texture: destination.texture.id, mip_level: destination.mip_level, origin: destination.origin.into(), aspect: destination.aspect.into(), }; let err = self .instance .command_encoder_copy_texture_to_texture( self.id, &source, &destination, ©_size.into(), ) .err(); self.error_handler.push_error(err); } #[required(1)] #[undefined] fn clear_buffer( &self, #[webidl] buffer: Ptr, #[webidl(default = 0, options(enforce_range = true))] offset: u64, #[webidl(options(enforce_range = true))] size: Option, ) { let err = self .instance .command_encoder_clear_buffer(self.id, buffer.id, offset, size) .err(); self.error_handler.push_error(err); } #[required(5)] #[undefined] fn resolve_query_set( &self, #[webidl] query_set: Ptr, #[webidl(options(enforce_range = true))] first_query: u32, #[webidl(options(enforce_range = true))] query_count: u32, #[webidl] destination: Ptr, #[webidl(options(enforce_range = true))] destination_offset: u64, ) { let err = self .instance .command_encoder_resolve_query_set( self.id, query_set.id, first_query, query_count, destination.id, destination_offset, ) .err(); self.error_handler.push_error(err); } #[cppgc] fn finish( &self, #[webidl] descriptor: crate::command_buffer::GPUCommandBufferDescriptor, ) -> GPUCommandBuffer { let wgpu_descriptor = wgpu_types::CommandBufferDescriptor { label: crate::transform_label(descriptor.label.clone()), }; let (id, opt_label_and_err) = self .instance .command_encoder_finish(self.id, &wgpu_descriptor, None); self .error_handler .push_error(opt_label_and_err.map(|(_label, err)| err)); GPUCommandBuffer { instance: self.instance.clone(), id, label: descriptor.label, } } fn push_debug_group(&self, #[webidl] group_label: String) { let err = self .instance .command_encoder_push_debug_group(self.id, &group_label) .err(); self.error_handler.push_error(err); } #[fast] fn pop_debug_group(&self) { let err = self.instance.command_encoder_pop_debug_group(self.id).err(); self.error_handler.push_error(err); } fn insert_debug_marker(&self, #[webidl] marker_label: String) { let err = self .instance .command_encoder_insert_debug_marker(self.id, &marker_label) .err(); self.error_handler.push_error(err); } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUCommandEncoderDescriptor { #[webidl(default = String::new())] pub label: String, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUTexelCopyBufferInfo { pub buffer: Ptr, #[webidl(default = 0)] #[options(enforce_range = true)] offset: u64, #[options(enforce_range = true)] bytes_per_row: Option, #[options(enforce_range = true)] rows_per_image: Option, } ================================================ FILE: deno_webgpu/compute_pass.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::borrow::Cow; use std::cell::RefCell; use deno_core::cppgc::Ptr; use deno_core::op2; use deno_core::v8; use deno_core::webidl::IntOptions; use deno_core::webidl::Nullable; use deno_core::webidl::WebIdlConverter; use deno_core::webidl::WebIdlError; use deno_core::GarbageCollected; use deno_core::WebIDL; use crate::error::GPUGenericError; use crate::Instance; pub struct GPUComputePassEncoder { pub instance: Instance, pub error_handler: super::error::ErrorHandler, pub compute_pass: RefCell, pub label: String, } impl GarbageCollected for GPUComputePassEncoder { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUComputePassEncoder" } } #[op2] impl GPUComputePassEncoder { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } #[undefined] fn set_pipeline( &self, #[webidl] pipeline: Ptr, ) { let err = self .instance .compute_pass_set_pipeline( &mut self.compute_pass.borrow_mut(), pipeline.id, ) .err(); self.error_handler.push_error(err); } #[undefined] fn dispatch_workgroups( &self, #[webidl(options(enforce_range = true))] work_group_count_x: u32, #[webidl(default = 1, options(enforce_range = true))] work_group_count_y: u32, #[webidl(default = 1, options(enforce_range = true))] work_group_count_z: u32, ) { let err = self .instance .compute_pass_dispatch_workgroups( &mut self.compute_pass.borrow_mut(), work_group_count_x, work_group_count_y, work_group_count_z, ) .err(); self.error_handler.push_error(err); } #[undefined] fn dispatch_workgroups_indirect( &self, #[webidl] indirect_buffer: Ptr, #[webidl(options(enforce_range = true))] indirect_offset: u64, ) { let err = self .instance .compute_pass_dispatch_workgroups_indirect( &mut self.compute_pass.borrow_mut(), indirect_buffer.id, indirect_offset, ) .err(); self.error_handler.push_error(err); } #[fast] #[undefined] fn end(&self) { let err = self .instance .compute_pass_end(&mut self.compute_pass.borrow_mut()) .err(); self.error_handler.push_error(err); } #[undefined] fn push_debug_group(&self, #[webidl] group_label: String) { let err = self .instance .compute_pass_push_debug_group( &mut self.compute_pass.borrow_mut(), &group_label, 0, // wgpu#975 ) .err(); self.error_handler.push_error(err); } #[fast] #[undefined] fn pop_debug_group(&self) { let err = self .instance .compute_pass_pop_debug_group(&mut self.compute_pass.borrow_mut()) .err(); self.error_handler.push_error(err); } #[undefined] fn insert_debug_marker(&self, #[webidl] marker_label: String) { let err = self .instance .compute_pass_insert_debug_marker( &mut self.compute_pass.borrow_mut(), &marker_label, 0, // wgpu#975 ) .err(); self.error_handler.push_error(err); } #[undefined] fn set_bind_group<'a>( &self, scope: &mut v8::HandleScope<'a>, #[webidl(options(enforce_range = true))] index: u32, #[webidl] bind_group: Nullable>, dynamic_offsets: v8::Local<'a, v8::Value>, dynamic_offsets_data_start: v8::Local<'a, v8::Value>, dynamic_offsets_data_length: v8::Local<'a, v8::Value>, ) -> Result<(), WebIdlError> { const PREFIX: &str = "Failed to execute 'setBindGroup' on 'GPUComputePassEncoder'"; let err = if let Ok(uint_32) = dynamic_offsets.try_cast::() { let start = u64::convert( scope, dynamic_offsets_data_start, Cow::Borrowed(PREFIX), (|| Cow::Borrowed("Argument 4")).into(), &IntOptions { clamp: false, enforce_range: true, }, )? as usize; let len = u32::convert( scope, dynamic_offsets_data_length, Cow::Borrowed(PREFIX), (|| Cow::Borrowed("Argument 5")).into(), &IntOptions { clamp: false, enforce_range: true, }, )? as usize; let ab = uint_32.buffer(scope).unwrap(); let ptr = ab.data().unwrap(); let ab_len = ab.byte_length() / 4; // SAFETY: compute_pass_set_bind_group internally calls extend_from_slice with this slice let data = unsafe { std::slice::from_raw_parts(ptr.as_ptr() as _, ab_len) }; let offsets = &data[start..(start + len)]; self .instance .compute_pass_set_bind_group( &mut self.compute_pass.borrow_mut(), index, bind_group.into_option().map(|bind_group| bind_group.id), offsets, ) .err() } else { let offsets = >>::convert( scope, dynamic_offsets, Cow::Borrowed(PREFIX), (|| Cow::Borrowed("Argument 3")).into(), &IntOptions { clamp: false, enforce_range: true, }, )? .unwrap_or_default(); self .instance .compute_pass_set_bind_group( &mut self.compute_pass.borrow_mut(), index, bind_group.into_option().map(|bind_group| bind_group.id), &offsets, ) .err() }; self.error_handler.push_error(err); Ok(()) } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUComputePassDescriptor { #[webidl(default = String::new())] pub label: String, pub timestamp_writes: Option, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUComputePassTimestampWrites { pub query_set: Ptr, #[options(enforce_range = true)] pub beginning_of_pass_write_index: Option, #[options(enforce_range = true)] pub end_of_pass_write_index: Option, } ================================================ FILE: deno_webgpu/compute_pipeline.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use deno_core::cppgc::Ptr; use deno_core::op2; use deno_core::webidl::WebIdlInterfaceConverter; use deno_core::GarbageCollected; use deno_core::WebIDL; use indexmap::IndexMap; use crate::bind_group_layout::GPUBindGroupLayout; use crate::error::GPUGenericError; use crate::shader::GPUShaderModule; use crate::webidl::GPUPipelineLayoutOrGPUAutoLayoutMode; use crate::Instance; pub struct GPUComputePipeline { pub instance: Instance, pub error_handler: super::error::ErrorHandler, pub id: wgpu_core::id::ComputePipelineId, pub label: String, } impl Drop for GPUComputePipeline { fn drop(&mut self) { self.instance.compute_pipeline_drop(self.id); } } impl WebIdlInterfaceConverter for GPUComputePipeline { const NAME: &'static str = "GPUComputePipeline"; } impl GarbageCollected for GPUComputePipeline { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUComputePipeline" } } #[op2] impl GPUComputePipeline { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } #[cppgc] fn get_bind_group_layout(&self, #[webidl] index: u32) -> GPUBindGroupLayout { let (id, err) = self .instance .compute_pipeline_get_bind_group_layout(self.id, index, None); self.error_handler.push_error(err); // TODO(wgpu): needs to support retrieving the label GPUBindGroupLayout { instance: self.instance.clone(), id, label: "".to_string(), } } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUComputePipelineDescriptor { #[webidl(default = String::new())] pub label: String, pub compute: GPUProgrammableStage, pub layout: GPUPipelineLayoutOrGPUAutoLayoutMode, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUProgrammableStage { pub module: Ptr, pub entry_point: Option, #[webidl(default = Default::default())] pub constants: IndexMap, } ================================================ FILE: deno_webgpu/device.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::borrow::Cow; use std::cell::RefCell; use std::num::NonZeroU64; use std::rc::Rc; use deno_core::cppgc::{make_cppgc_object, SameObject}; use deno_core::op2; use deno_core::v8; use deno_core::webidl::WebIdlInterfaceConverter; use deno_core::GarbageCollected; use deno_error::JsErrorBox; use wgpu_core::binding_model::BindingResource; use wgpu_core::pipeline::ProgrammableStageDescriptor; use wgpu_types::BindingType; use super::bind_group::GPUBindGroup; use super::bind_group::GPUBindingResource; use super::bind_group_layout::GPUBindGroupLayout; use super::buffer::GPUBuffer; use super::compute_pipeline::GPUComputePipeline; use super::pipeline_layout::GPUPipelineLayout; use super::sampler::GPUSampler; use super::shader::GPUShaderModule; use super::texture::GPUTexture; use crate::adapter::GPUAdapterInfo; use crate::adapter::GPUSupportedFeatures; use crate::adapter::GPUSupportedLimits; use crate::command_encoder::GPUCommandEncoder; use crate::error::{fmt_err, make_pipeline_error}; use crate::error::{GPUError, GPUGenericError, GPUPipelineErrorReason}; use crate::query_set::GPUQuerySet; use crate::render_bundle::GPURenderBundleEncoder; use crate::render_pipeline::GPURenderPipeline; use crate::shader::GPUCompilationInfo; use crate::Instance; /// External memory associated with device and queue, to encourage V8 to garbage /// collect devices promptly. This seems to be particularly important when /// running CTS tests under `webgpu:api,validation,capability_checks,limits,*` /// on DX12 in wgpu CI, where any smaller power of two results in OOM errors. pub(crate) const DEVICE_EXTERNAL_MEMORY_SIZE: i64 = 1 << 24; // 16 MB pub struct GPUDevice { pub instance: Instance, pub id: wgpu_core::id::DeviceId, pub adapter: wgpu_core::id::AdapterId, pub label: String, pub features: SameObject, pub limits: SameObject, pub adapter_info: Rc>, pub queue_obj: v8::Global, pub error_handler: super::error::ErrorHandler, pub lost_promise: v8::Global, // Weak reference to the JS object so we can attach a finalizer. pub(crate) weak: std::sync::OnceLock>, } impl Drop for GPUDevice { fn drop(&mut self) { self.instance.device_drop(self.id); } } impl WebIdlInterfaceConverter for GPUDevice { const NAME: &'static str = "GPUDevice"; } impl GarbageCollected for GPUDevice { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUDevice" } } // EventTarget is extended in JS #[op2] impl GPUDevice { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } #[getter] #[global] fn features(&self, scope: &mut v8::HandleScope) -> v8::Global { self.features.get(scope, |scope| { let features = self.instance.device_features(self.id); GPUSupportedFeatures::new(scope, features) }) } #[getter] #[global] fn limits(&self, scope: &mut v8::HandleScope) -> v8::Global { self.limits.get(scope, |_| { let limits = self.instance.device_limits(self.id); GPUSupportedLimits(limits) }) } #[getter] #[global] fn adapter_info( &self, scope: &mut v8::HandleScope, ) -> v8::Global { self.adapter_info.get(scope, |_| { let info = self.instance.adapter_get_info(self.adapter); GPUAdapterInfo { info } }) } #[getter] #[global] fn queue(&self) -> v8::Global { self.queue_obj.clone() } #[fast] #[undefined] fn destroy(&self) { self.instance.device_destroy(self.id); self .error_handler .push_error(Some(GPUError::Lost(GPUDeviceLostReason::Destroyed))); } #[required(1)] #[cppgc] fn create_buffer( &self, #[webidl] descriptor: super::buffer::GPUBufferDescriptor, ) -> Result { // wgpu-core would also check this, but it needs to be reported via a JS // error, not a validation error. (WebGPU specifies this check on the // content timeline.) if descriptor.mapped_at_creation && !descriptor .size .is_multiple_of(wgpu_types::COPY_BUFFER_ALIGNMENT) { return Err(JsErrorBox::range_error( format!( "The size of a buffer that is mapped at creation must be a multiple of {}", wgpu_types::COPY_BUFFER_ALIGNMENT, ) )); } // Validation of the usage needs to happen on the device timeline, so // don't raise an error immediately if it isn't valid. wgpu will // reject `BufferUsages::empty()`. let usage = wgpu_types::BufferUsages::from_bits(descriptor.usage) .unwrap_or(wgpu_types::BufferUsages::empty()); let wgpu_descriptor = wgpu_core::resource::BufferDescriptor { label: crate::transform_label(descriptor.label.clone()), size: descriptor.size, usage, mapped_at_creation: descriptor.mapped_at_creation, }; let (id, err) = self .instance .device_create_buffer(self.id, &wgpu_descriptor, None); self.error_handler.push_error(err); Ok(GPUBuffer { instance: self.instance.clone(), error_handler: self.error_handler.clone(), id, device: self.id, label: descriptor.label, size: descriptor.size, usage: descriptor.usage, map_state: RefCell::new(if descriptor.mapped_at_creation { "mapped" } else { "unmapped" }), map_mode: RefCell::new(if descriptor.mapped_at_creation { Some(wgpu_core::device::HostMap::Write) } else { None }), mapped_js_buffers: RefCell::new(vec![]), }) } #[required(1)] #[cppgc] fn create_texture( &self, #[webidl] descriptor: super::texture::GPUTextureDescriptor, ) -> Result { let wgpu_descriptor = wgpu_core::resource::TextureDescriptor { label: crate::transform_label(descriptor.label.clone()), size: descriptor.size.into(), mip_level_count: descriptor.mip_level_count, sample_count: descriptor.sample_count, dimension: descriptor.dimension.clone().into(), format: descriptor.format.clone().into(), usage: descriptor.usage.into(), view_formats: descriptor .view_formats .into_iter() .map(Into::into) .collect(), }; let (id, err) = self .instance .device_create_texture(self.id, &wgpu_descriptor, None); self.error_handler.push_error(err); Ok(GPUTexture { instance: self.instance.clone(), error_handler: self.error_handler.clone(), id, default_view_id: Default::default(), label: descriptor.label, size: wgpu_descriptor.size, mip_level_count: wgpu_descriptor.mip_level_count, sample_count: wgpu_descriptor.sample_count, dimension: descriptor.dimension, format: descriptor.format, usage: descriptor.usage, }) } #[cppgc] fn create_sampler( &self, #[webidl] descriptor: super::sampler::GPUSamplerDescriptor, ) -> Result { let wgpu_descriptor = wgpu_core::resource::SamplerDescriptor { label: crate::transform_label(descriptor.label.clone()), address_modes: [ descriptor.address_mode_u.into(), descriptor.address_mode_v.into(), descriptor.address_mode_w.into(), ], mag_filter: descriptor.mag_filter.into(), min_filter: descriptor.min_filter.into(), mipmap_filter: descriptor.mipmap_filter.into(), lod_min_clamp: descriptor.lod_min_clamp, lod_max_clamp: descriptor.lod_max_clamp, compare: descriptor.compare.map(Into::into), anisotropy_clamp: descriptor.max_anisotropy, border_color: None, }; let (id, err) = self .instance .device_create_sampler(self.id, &wgpu_descriptor, None); self.error_handler.push_error(err); Ok(GPUSampler { instance: self.instance.clone(), id, label: descriptor.label, }) } #[required(1)] #[cppgc] fn create_bind_group_layout( &self, #[webidl] descriptor: super::bind_group_layout::GPUBindGroupLayoutDescriptor, ) -> Result { let mut entries = Vec::with_capacity(descriptor.entries.len()); for entry in descriptor.entries { let n_entries = [ entry.buffer.is_some(), entry.sampler.is_some(), entry.texture.is_some(), entry.storage_texture.is_some(), entry.external_texture.is_some(), ] .into_iter() .filter(|t| *t) .count(); if n_entries != 1 { return Err(JsErrorBox::type_error( "Only one of 'buffer', 'sampler', 'texture' and 'storageTexture' may be specified", )); } let ty = if let Some(buffer) = entry.buffer { BindingType::Buffer { ty: buffer.r#type.into(), has_dynamic_offset: buffer.has_dynamic_offset, min_binding_size: NonZeroU64::new(buffer.min_binding_size), } } else if let Some(sampler) = entry.sampler { BindingType::Sampler(sampler.r#type.into()) } else if let Some(texture) = entry.texture { BindingType::Texture { sample_type: texture.sample_type.into(), view_dimension: texture.view_dimension.into(), multisampled: texture.multisampled, } } else if let Some(storage_texture) = entry.storage_texture { BindingType::StorageTexture { access: storage_texture.access.into(), format: storage_texture.format.into(), view_dimension: storage_texture.view_dimension.into(), } } else if entry.external_texture.is_some() { BindingType::ExternalTexture } else { unreachable!() }; entries.push(wgpu_types::BindGroupLayoutEntry { binding: entry.binding, visibility: entry.visibility.into(), ty, count: None, // native-only }); } let wgpu_descriptor = wgpu_core::binding_model::BindGroupLayoutDescriptor { label: crate::transform_label(descriptor.label.clone()), entries: Cow::Owned(entries), }; let (id, err) = self.instance.device_create_bind_group_layout( self.id, &wgpu_descriptor, None, ); self.error_handler.push_error(err); Ok(GPUBindGroupLayout { instance: self.instance.clone(), id, label: descriptor.label, }) } #[required(1)] #[cppgc] fn create_pipeline_layout( &self, #[webidl] descriptor: super::pipeline_layout::GPUPipelineLayoutDescriptor, ) -> GPUPipelineLayout { let bind_group_layouts = descriptor .bind_group_layouts .into_iter() .map(|bind_group_layout| { bind_group_layout .into_option() .map(|bind_group_layout| bind_group_layout.id) }) .collect(); let wgpu_descriptor = wgpu_core::binding_model::PipelineLayoutDescriptor { label: crate::transform_label(descriptor.label.clone()), bind_group_layouts: Cow::Owned(bind_group_layouts), immediate_size: 0, }; let (id, err) = self.instance.device_create_pipeline_layout( self.id, &wgpu_descriptor, None, ); self.error_handler.push_error(err); GPUPipelineLayout { instance: self.instance.clone(), id, label: descriptor.label, } } #[required(1)] #[cppgc] fn create_bind_group( &self, #[webidl] descriptor: super::bind_group::GPUBindGroupDescriptor, ) -> GPUBindGroup { let entries = descriptor .entries .into_iter() .map(|entry| wgpu_core::binding_model::BindGroupEntry { binding: entry.binding, resource: match entry.resource { GPUBindingResource::Sampler(sampler) => { BindingResource::Sampler(sampler.id) } GPUBindingResource::Texture(texture) => { BindingResource::TextureView(texture.default_view_id()) } GPUBindingResource::TextureView(texture_view) => { BindingResource::TextureView(texture_view.id) } GPUBindingResource::Buffer(buffer) => { BindingResource::Buffer(wgpu_core::binding_model::BufferBinding { buffer: buffer.id, offset: 0, size: Some(buffer.size), }) } GPUBindingResource::BufferBinding(buffer_binding) => { BindingResource::Buffer(wgpu_core::binding_model::BufferBinding { buffer: buffer_binding.buffer.id, offset: buffer_binding.offset, size: buffer_binding.size, }) } GPUBindingResource::ExternalTexture(external_texture) => { BindingResource::ExternalTexture(external_texture.id) } }, }) .collect::>(); let wgpu_descriptor = wgpu_core::binding_model::BindGroupDescriptor { label: crate::transform_label(descriptor.label.clone()), layout: descriptor.layout.id, entries: Cow::Owned(entries), }; let (id, err) = self .instance .device_create_bind_group(self.id, &wgpu_descriptor, None); self.error_handler.push_error(err); GPUBindGroup { instance: self.instance.clone(), id, label: descriptor.label, } } #[required(1)] #[cppgc] fn create_shader_module( &self, scope: &mut v8::HandleScope<'_>, #[webidl] descriptor: super::shader::GPUShaderModuleDescriptor, ) -> GPUShaderModule { let wgpu_descriptor = wgpu_core::pipeline::ShaderModuleDescriptor { label: crate::transform_label(descriptor.label.clone()), runtime_checks: wgpu_types::ShaderRuntimeChecks::default(), }; let (id, err) = self.instance.device_create_shader_module( self.id, &wgpu_descriptor, wgpu_core::pipeline::ShaderModuleSource::Wgsl(Cow::Borrowed( &descriptor.code, )), None, ); let compilation_info = GPUCompilationInfo::new(scope, err.iter(), &descriptor.code); let compilation_info = make_cppgc_object(scope, compilation_info); let compilation_info = v8::Global::new(scope, compilation_info); self.error_handler.push_error(err); GPUShaderModule { instance: self.instance.clone(), id, label: descriptor.label, compilation_info, } } #[required(1)] #[cppgc] fn create_compute_pipeline( &self, #[webidl] descriptor: super::compute_pipeline::GPUComputePipelineDescriptor, ) -> GPUComputePipeline { let (pipeline, err) = self.new_compute_pipeline(descriptor); self.error_handler.push_error(err); pipeline } #[required(1)] #[cppgc] fn create_render_pipeline( &self, #[webidl] descriptor: super::render_pipeline::GPURenderPipelineDescriptor, ) -> GPURenderPipeline { let (pipeline, err) = self.new_render_pipeline(descriptor); self.error_handler.push_error(err); pipeline } #[async_method(fake)] #[required(1)] #[cppgc] #[global] fn create_compute_pipeline_async( &self, scope: &mut v8::HandleScope, #[webidl] descriptor: super::compute_pipeline::GPUComputePipelineDescriptor, ) -> v8::Global { let resolver = v8::PromiseResolver::new(scope).unwrap(); let promise = resolver.get_promise(scope); let (pipeline, err) = self.new_compute_pipeline(descriptor); if let Some(err) = err { let err = make_pipeline_error( scope, GPUPipelineErrorReason::Validation, &fmt_err(&err), ); resolver.reject(scope, err.into()); } else { let val = make_cppgc_object(scope, pipeline).into(); resolver.resolve(scope, val); } v8::Global::new(scope, promise) } #[async_method(fake)] #[required(1)] #[cppgc] #[global] fn create_render_pipeline_async( &self, scope: &mut v8::HandleScope, #[webidl] descriptor: super::render_pipeline::GPURenderPipelineDescriptor, ) -> v8::Global { let resolver = v8::PromiseResolver::new(scope).unwrap(); let promise = resolver.get_promise(scope); let (pipeline, err) = self.new_render_pipeline(descriptor); if let Some(err) = err { let err = make_pipeline_error( scope, GPUPipelineErrorReason::Validation, &fmt_err(&err), ); resolver.reject(scope, err.into()); } else { let val = make_cppgc_object(scope, pipeline).into(); resolver.resolve(scope, val); } v8::Global::new(scope, promise) } fn create_command_encoder<'a>( &self, scope: &mut v8::HandleScope<'a>, #[webidl] descriptor: Option< super::command_encoder::GPUCommandEncoderDescriptor, >, ) -> v8::Local<'a, v8::Object> { // Metal imposes a limit on the number of outstanding command buffers. // Attempting to create another command buffer after reaching that limit // will block, which can result in a deadlock if GC is required to // recover old command buffers. To encourage V8 to garbage collect // command buffers before that happens, we associate some external // memory with each command buffer. #[cfg(target_vendor = "apple")] const EXTERNAL_MEMORY_AMOUNT: i64 = 1 << 16; let label = descriptor.map(|d| d.label).unwrap_or_default(); let wgpu_descriptor = wgpu_types::CommandEncoderDescriptor { label: Some(Cow::Owned(label.clone())), }; #[cfg(target_vendor = "apple")] scope.adjust_amount_of_external_allocated_memory(EXTERNAL_MEMORY_AMOUNT); let (id, err) = self.instance.device_create_command_encoder( self.id, &wgpu_descriptor, None, ); self.error_handler.push_error(err); let encoder = GPUCommandEncoder { instance: self.instance.clone(), error_handler: self.error_handler.clone(), id, label, #[cfg(target_vendor = "apple")] weak: std::sync::OnceLock::new(), }; let obj = make_cppgc_object(scope, encoder); #[cfg(target_vendor = "apple")] { let finalizer = v8::Weak::with_finalizer( scope, obj, Box::new(|isolate: &mut v8::Isolate| { isolate.adjust_amount_of_external_allocated_memory( -EXTERNAL_MEMORY_AMOUNT, ); }), ); deno_core::cppgc::try_unwrap_cppgc_object::( scope, obj.into(), ) .unwrap() .weak .set(finalizer) .unwrap(); } obj } #[required(1)] #[cppgc] fn create_render_bundle_encoder( &self, #[webidl] descriptor: super::render_bundle::GPURenderBundleEncoderDescriptor, ) -> GPURenderBundleEncoder { let wgpu_descriptor = wgpu_core::command::RenderBundleEncoderDescriptor { label: crate::transform_label(descriptor.label.clone()), color_formats: Cow::Owned( descriptor .color_formats .into_iter() .map(|format| format.into_option().map(Into::into)) .collect::>(), ), depth_stencil: descriptor.depth_stencil_format.map(|format| { wgpu_types::RenderBundleDepthStencil { format: format.into(), depth_read_only: descriptor.depth_read_only, stencil_read_only: descriptor.stencil_read_only, } }), sample_count: descriptor.sample_count, multiview: None, }; let res = wgpu_core::command::RenderBundleEncoder::new(&wgpu_descriptor, self.id); let (encoder, err) = match res { Ok(encoder) => (encoder, None), Err(e) => ( wgpu_core::command::RenderBundleEncoder::dummy(self.id), Some(e), ), }; self.error_handler.push_error(err); GPURenderBundleEncoder { instance: self.instance.clone(), error_handler: self.error_handler.clone(), encoder: RefCell::new(Some(encoder)), label: descriptor.label, } } #[required(1)] #[cppgc] fn create_query_set( &self, #[webidl] descriptor: crate::query_set::GPUQuerySetDescriptor, ) -> GPUQuerySet { let wgpu_descriptor = wgpu_core::resource::QuerySetDescriptor { label: crate::transform_label(descriptor.label.clone()), ty: descriptor.r#type.clone().into(), count: descriptor.count, }; let (id, err) = self .instance .device_create_query_set(self.id, &wgpu_descriptor, None); self.error_handler.push_error(err); GPUQuerySet { instance: self.instance.clone(), id, r#type: descriptor.r#type, count: descriptor.count, label: descriptor.label, } } #[getter] #[global] fn lost(&self) -> v8::Global { self.lost_promise.clone() } #[required(1)] #[undefined] fn push_error_scope(&self, #[webidl] filter: super::error::GPUErrorFilter) { self .error_handler .scopes .lock() .unwrap() .push((filter, None)); } #[async_method(fake)] #[global] fn pop_error_scope( &self, scope: &mut v8::HandleScope, ) -> Result, JsErrorBox> { if self.error_handler.is_lost.get().is_some() { let val = v8::null(scope).cast::(); return Ok(v8::Global::new(scope, val)); } let Some((_, error)) = self.error_handler.scopes.lock().unwrap().pop() else { return Err(JsErrorBox::new( "DOMExceptionOperationError", "There are no error scopes on the error scope stack", )); }; let val = if let Some(err) = error { deno_core::error::to_v8_error(scope, &err) } else { v8::null(scope).into() }; Ok(v8::Global::new(scope, val)) } #[fast] fn start_capture(&self) { unsafe { self .instance .device_start_graphics_debugger_capture(self.id) }; } #[fast] fn stop_capture(&self) { self .instance .device_poll(self.id, wgpu_types::PollType::wait_indefinitely()) .unwrap(); unsafe { self.instance.device_stop_graphics_debugger_capture(self.id) }; } } impl GPUDevice { fn new_compute_pipeline( &self, descriptor: super::compute_pipeline::GPUComputePipelineDescriptor, ) -> ( GPUComputePipeline, Option, ) { let wgpu_descriptor = wgpu_core::pipeline::ComputePipelineDescriptor { label: crate::transform_label(descriptor.label.clone()), layout: descriptor.layout.into(), stage: ProgrammableStageDescriptor { module: descriptor.compute.module.id, entry_point: descriptor.compute.entry_point.map(Into::into), constants: descriptor.compute.constants.into_iter().collect(), zero_initialize_workgroup_memory: true, }, cache: None, }; let (id, err) = self.instance.device_create_compute_pipeline( self.id, &wgpu_descriptor, None, ); ( GPUComputePipeline { instance: self.instance.clone(), error_handler: self.error_handler.clone(), id, label: descriptor.label.clone(), }, err, ) } fn new_render_pipeline( &self, descriptor: super::render_pipeline::GPURenderPipelineDescriptor, ) -> ( GPURenderPipeline, Option, ) { let vertex = wgpu_core::pipeline::VertexState { stage: ProgrammableStageDescriptor { module: descriptor.vertex.module.id, entry_point: descriptor.vertex.entry_point.map(Into::into), constants: descriptor.vertex.constants.into_iter().collect(), zero_initialize_workgroup_memory: true, }, buffers: Cow::Owned( descriptor .vertex .buffers .into_iter() .map(|b| { b.into_option().map_or_else( wgpu_core::pipeline::VertexBufferLayout::default, |layout| wgpu_core::pipeline::VertexBufferLayout { array_stride: layout.array_stride, step_mode: layout.step_mode.into(), attributes: Cow::Owned( layout .attributes .into_iter() .map(|attr| wgpu_types::VertexAttribute { format: attr.format.into(), offset: attr.offset, shader_location: attr.shader_location, }) .collect(), ), }, ) }) .collect(), ), }; let primitive = wgpu_types::PrimitiveState { topology: descriptor.primitive.topology.into(), strip_index_format: descriptor .primitive .strip_index_format .map(Into::into), front_face: descriptor.primitive.front_face.into(), cull_mode: descriptor.primitive.cull_mode.into(), unclipped_depth: descriptor.primitive.unclipped_depth, polygon_mode: Default::default(), conservative: false, }; let depth_stencil = descriptor.depth_stencil.map(|depth_stencil| { let front = wgpu_types::StencilFaceState { compare: depth_stencil.stencil_front.compare.into(), fail_op: depth_stencil.stencil_front.fail_op.into(), depth_fail_op: depth_stencil.stencil_front.depth_fail_op.into(), pass_op: depth_stencil.stencil_front.pass_op.into(), }; let back = wgpu_types::StencilFaceState { compare: depth_stencil.stencil_back.compare.into(), fail_op: depth_stencil.stencil_back.fail_op.into(), depth_fail_op: depth_stencil.stencil_back.depth_fail_op.into(), pass_op: depth_stencil.stencil_back.pass_op.into(), }; wgpu_types::DepthStencilState { format: depth_stencil.format.into(), depth_write_enabled: depth_stencil.depth_write_enabled, depth_compare: depth_stencil.depth_compare.map(Into::into), stencil: wgpu_types::StencilState { front, back, read_mask: depth_stencil.stencil_read_mask, write_mask: depth_stencil.stencil_write_mask, }, bias: wgpu_types::DepthBiasState { constant: depth_stencil.depth_bias, slope_scale: depth_stencil.depth_bias_slope_scale, clamp: depth_stencil.depth_bias_clamp, }, } }); let multisample = wgpu_types::MultisampleState { count: descriptor.multisample.count, mask: descriptor.multisample.mask as u64, alpha_to_coverage_enabled: descriptor .multisample .alpha_to_coverage_enabled, }; let fragment = descriptor .fragment .map(|fragment| wgpu_core::pipeline::FragmentState { stage: ProgrammableStageDescriptor { module: fragment.module.id, entry_point: fragment.entry_point.map(Into::into), constants: fragment.constants.into_iter().collect(), zero_initialize_workgroup_memory: true, }, targets: Cow::Owned( fragment .targets .into_iter() .map(|target| { target.into_option().map(|target| { wgpu_types::ColorTargetState { format: target.format.into(), blend: target.blend.map(|blend| wgpu_types::BlendState { color: wgpu_types::BlendComponent { src_factor: blend.color.src_factor.into(), dst_factor: blend.color.dst_factor.into(), operation: blend.color.operation.into(), }, alpha: wgpu_types::BlendComponent { src_factor: blend.alpha.src_factor.into(), dst_factor: blend.alpha.dst_factor.into(), operation: blend.alpha.operation.into(), }, }), write_mask: target.write_mask.into(), } }) }) .collect(), ), }); let wgpu_descriptor = wgpu_core::pipeline::RenderPipelineDescriptor { label: crate::transform_label(descriptor.label.clone()), layout: descriptor.layout.into(), vertex, primitive, depth_stencil, multisample, fragment, cache: None, multiview_mask: None, }; let (id, err) = self.instance.device_create_render_pipeline( self.id, &wgpu_descriptor, None, ); ( GPURenderPipeline { instance: self.instance.clone(), error_handler: self.error_handler.clone(), id, label: descriptor.label, }, err, ) } } #[derive(Clone, Debug, Default, Hash, Eq, PartialEq)] pub enum GPUDeviceLostReason { #[default] Unknown, Destroyed, } impl From for GPUDeviceLostReason { fn from(value: wgpu_types::DeviceLostReason) -> Self { match value { wgpu_types::DeviceLostReason::Unknown => Self::Unknown, wgpu_types::DeviceLostReason::Destroyed => Self::Destroyed, } } } #[derive(Default)] pub struct GPUDeviceLostInfo { pub reason: GPUDeviceLostReason, } impl GarbageCollected for GPUDeviceLostInfo { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUDeviceLostInfo" } } #[op2] impl GPUDeviceLostInfo { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn reason(&self) -> &'static str { use GPUDeviceLostReason::*; match self.reason { Unknown => "unknown", Destroyed => "destroyed", } } #[getter] #[string] fn message(&self) -> &'static str { "device was lost" } } #[op2(fast)] pub fn op_webgpu_device_start_capture(#[cppgc] device: &GPUDevice) { unsafe { device .instance .device_start_graphics_debugger_capture(device.id); } } #[op2(fast)] pub fn op_webgpu_device_stop_capture(#[cppgc] device: &GPUDevice) { unsafe { device .instance .device_stop_graphics_debugger_capture(device.id); } } ================================================ FILE: deno_webgpu/error.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::fmt::Display; use std::fmt::Formatter; use std::sync::Mutex; use std::sync::OnceLock; use deno_core::cppgc::make_cppgc_object; use deno_core::v8; use deno_core::JsRuntime; use deno_core::V8TaskSpawner; use wgpu_core::binding_model::CreateBindGroupError; use wgpu_core::binding_model::CreateBindGroupLayoutError; use wgpu_core::binding_model::CreatePipelineLayoutError; use wgpu_core::binding_model::GetBindGroupLayoutError; use wgpu_core::command::ClearError; use wgpu_core::command::CommandEncoderError; use wgpu_core::command::ComputePassError; use wgpu_core::command::CreateRenderBundleError; use wgpu_core::command::EncoderStateError; use wgpu_core::command::PassStateError; use wgpu_core::command::QueryError; use wgpu_core::command::RenderBundleError; use wgpu_core::command::RenderPassError; use wgpu_core::device::queue::QueueSubmitError; use wgpu_core::device::queue::QueueWriteError; use wgpu_core::device::DeviceError; use wgpu_core::pipeline::CreateComputePipelineError; use wgpu_core::pipeline::CreateRenderPipelineError; use wgpu_core::pipeline::CreateShaderModuleError; use wgpu_core::present::ConfigureSurfaceError; use wgpu_core::resource::BufferAccessError; use wgpu_core::resource::CreateBufferError; use wgpu_core::resource::CreateQuerySetError; use wgpu_core::resource::CreateSamplerError; use wgpu_core::resource::CreateTextureError; use wgpu_core::resource::CreateTextureViewError; use wgpu_types::error::{ErrorType, WebGpuError}; use crate::device::GPUDeviceLostInfo; use crate::device::GPUDeviceLostReason; pub type ErrorHandler = std::rc::Rc; pub struct DeviceErrorHandler { pub is_lost: OnceLock<()>, pub scopes: Mutex)>>, lost_resolver: Mutex>>, spawner: V8TaskSpawner, // The error handler is constructed before the device. A weak // reference to the device is placed here with `set_device` // after the device is constructed. device: OnceLock>, } impl DeviceErrorHandler { pub fn new( lost_resolver: v8::Global, spawner: V8TaskSpawner, ) -> Self { Self { is_lost: Default::default(), scopes: Mutex::new(vec![]), lost_resolver: Mutex::new(Some(lost_resolver)), device: OnceLock::new(), spawner, } } pub fn set_device(&self, device: v8::Weak) { self.device.set(device).unwrap() } pub fn push_error>(&self, err: Option) { let Some(err) = err else { return; }; if self.is_lost.get().is_some() { return; } let err = err.into(); if let GPUError::Lost(reason) = err { let _ = self.is_lost.set(()); if let Some(resolver) = self.lost_resolver.lock().unwrap().take() { self.spawner.spawn(move |scope| { let resolver = v8::Local::new(scope, resolver); let info = make_cppgc_object(scope, GPUDeviceLostInfo { reason }); let info = v8::Local::new(scope, info); resolver.resolve(scope, info.into()); }); } return; } let error_filter = match err { GPUError::Lost(_) => unreachable!(), GPUError::Validation(_) => GPUErrorFilter::Validation, GPUError::OutOfMemory => GPUErrorFilter::OutOfMemory, GPUError::Internal => GPUErrorFilter::Internal, }; let mut scopes = self.scopes.lock().unwrap(); let scope = scopes .iter_mut() .rfind(|(filter, _)| filter == &error_filter); if let Some(scope) = scope { // Only saving the first error in the scope as it's likely the culprit. if scope.1.is_none() { scope.1 = Some(err); } } else { let device = self .device .get() .expect("set_device was not called") .clone(); self.spawner.spawn(move |scope| { let state = JsRuntime::op_state_from(&*scope); let Some(device) = device.to_local(scope) else { // The device has already gone away, so we don't have // anywhere to report the error. return; }; let key = v8::String::new(scope, "dispatchEvent").unwrap(); let val = device.get(scope, key.into()).unwrap(); let func = v8::Global::new(scope, val.try_cast::().unwrap()); let device = v8::Global::new(scope, device.cast::()); let error_event_class = state.borrow().borrow::().0.clone(); let error = deno_core::error::to_v8_error(scope, &err); let error_event_class = v8::Local::new(scope, error_event_class.clone()); let constructor = v8::Local::::try_from(error_event_class).unwrap(); let kind = v8::String::new(scope, "uncapturederror").unwrap(); let obj = v8::Object::new(scope); let key = v8::String::new(scope, "error").unwrap(); obj.set(scope, key.into(), error); let event = constructor .new_instance(scope, &[kind.into(), obj.into()]) .unwrap(); let recv = v8::Local::new(scope, device); func.open(scope).call(scope, recv, &[event.into()]); }); } } } #[derive(deno_core::WebIDL, Eq, PartialEq)] #[webidl(enum)] pub enum GPUErrorFilter { Validation, OutOfMemory, Internal, } #[derive(Debug, deno_error::JsError)] pub enum GPUError { // TODO(@crowlKats): consider adding an unreachable value that uses unreachable!() #[class("UNREACHABLE")] Lost(GPUDeviceLostReason), #[class("GPUValidationError")] Validation(String), #[class("GPUOutOfMemoryError")] OutOfMemory, #[class("GPUInternalError")] Internal, } impl Display for GPUError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { GPUError::Lost(_) => Ok(()), GPUError::Validation(s) => f.write_str(s), GPUError::OutOfMemory => f.write_str("not enough memory left"), GPUError::Internal => Ok(()), } } } impl std::error::Error for GPUError {} impl GPUError { fn from_webgpu(e: impl WebGpuError) -> Self { match e.webgpu_error_type() { ErrorType::Internal => GPUError::Internal, ErrorType::DeviceLost => GPUError::Lost(GPUDeviceLostReason::Unknown), // TODO: this variant should be ignored, register the lost callback instead. ErrorType::OutOfMemory => GPUError::OutOfMemory, ErrorType::Validation => GPUError::Validation(fmt_err(&e)), } } } pub(crate) fn fmt_err(err: &(dyn std::error::Error + 'static)) -> String { let mut output = err.to_string(); let mut e = err.source(); while let Some(source) = e { output.push_str(&format!(": {source}")); e = source.source(); } if output.is_empty() { output.push_str("validation error"); } output } impl From for GPUError { fn from(err: EncoderStateError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: PassStateError) -> Self { GPUError::Validation(fmt_err(&err)) } } impl From for GPUError { fn from(err: CreateBufferError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: DeviceError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: BufferAccessError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateBindGroupLayoutError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreatePipelineLayoutError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateBindGroupError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: RenderBundleError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateRenderBundleError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CommandEncoderError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: QueryError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: ComputePassError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateComputePipelineError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: GetBindGroupLayoutError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateRenderPipelineError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: RenderPassError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateSamplerError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateShaderModuleError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateTextureError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateTextureViewError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateQuerySetError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: QueueSubmitError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: QueueWriteError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: ClearError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: ConfigureSurfaceError) -> Self { GPUError::from_webgpu(err) } } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum GPUGenericError { #[class(type)] #[error("Illegal constructor")] InvalidConstructor, } pub enum GPUPipelineErrorReason { Validation, #[expect(dead_code)] Internal, } impl Display for GPUPipelineErrorReason { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::Validation => f.write_str("validation"), Self::Internal => f.write_str("internal"), } } } pub(crate) fn make_pipeline_error<'a>( scope: &mut v8::HandleScope<'a>, reason: GPUPipelineErrorReason, message: &str, ) -> v8::Local<'a, v8::Object> { let state = JsRuntime::op_state_from(scope); let class = state .borrow() .borrow::() .0 .clone(); let constructor = v8::Local::::try_from(v8::Local::new(scope, class)).unwrap(); let message_str = v8::String::new(scope, message).unwrap(); let reason_str = v8::String::new(scope, &reason.to_string()).unwrap(); let options = v8::Object::new(scope); let key = v8::String::new(scope, "reason").unwrap(); options.set(scope, key.into(), reason_str.into()); constructor .new_instance(scope, &[message_str.into(), options.into()]) .unwrap() } ================================================ FILE: deno_webgpu/lib.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. #![cfg(not(target_arch = "wasm32"))] #![warn(unsafe_op_in_unsafe_fn)] use std::cell::RefCell; use std::rc::Rc; use std::sync::Arc; use deno_core::cppgc::SameObject; use deno_core::op2; use deno_core::v8; use deno_core::GarbageCollected; use deno_core::OpState; use serde::de::IntoDeserializer; use serde::Deserialize as _; pub use wgpu_core; pub use wgpu_types; use wgpu_types::PowerPreference; use crate::error::GPUGenericError; mod adapter; mod bind_group; mod bind_group_layout; mod buffer; mod byow; mod command_buffer; mod command_encoder; mod compute_pass; mod compute_pipeline; mod device; mod error; mod pipeline_layout; mod query_set; mod queue; mod render_bundle; mod render_pass; mod render_pipeline; mod sampler; mod shader; mod surface; mod texture; mod webidl; pub const UNSTABLE_FEATURE_NAME: &str = "webgpu"; pub const DX12_COMPILER_ENV_VAR: &str = "DENO_WEBGPU_DX12_COMPILER"; #[allow(clippy::print_stdout)] pub fn print_linker_flags(name: &str) { if cfg!(windows) { // these dls load slowly, so delay loading them let dlls = [ // webgpu "d3dcompiler_47", "OPENGL32", // network related functions "iphlpapi", ]; for dll in dlls { println!("cargo:rustc-link-arg-bin={name}=/delayload:{dll}.dll"); } // enable delay loading println!("cargo:rustc-link-arg-bin={name}=delayimp.lib"); } } pub type Instance = Arc; deno_core::extension!( deno_webgpu, deps = [deno_webidl, deno_web], ops = [ op_create_gpu, device::op_webgpu_device_start_capture, device::op_webgpu_device_stop_capture, ], objects = [ GPU, WGSLLanguageFeatures, adapter::GPUAdapter, adapter::GPUAdapterInfo, bind_group::GPUBindGroup, bind_group_layout::GPUBindGroupLayout, buffer::GPUBuffer, command_buffer::GPUCommandBuffer, command_encoder::GPUCommandEncoder, compute_pass::GPUComputePassEncoder, compute_pipeline::GPUComputePipeline, device::GPUDevice, device::GPUDeviceLostInfo, pipeline_layout::GPUPipelineLayout, query_set::GPUQuerySet, queue::GPUQueue, render_bundle::GPURenderBundle, render_bundle::GPURenderBundleEncoder, render_pass::GPURenderPassEncoder, render_pipeline::GPURenderPipeline, sampler::GPUSampler, shader::GPUCompilationInfo, shader::GPUCompilationMessage, shader::GPUShaderModule, adapter::GPUSupportedFeatures, adapter::GPUSupportedLimits, texture::GPUTexture, texture::GPUTextureView, texture::GPUExternalTexture, byow::UnsafeWindowSurface, surface::GPUCanvasContext, ], esm = ["00_init.js", "02_surface.js"], lazy_loaded_esm = ["01_webgpu.js"], ); #[op2] #[cppgc] pub fn op_create_gpu( state: &mut OpState, scope: &mut v8::HandleScope, webidl_brand: v8::Local, set_event_target_data: v8::Local, uncaptured_error_event_class: v8::Local, pipeline_error_class: v8::Local, ) -> GPU { state.put(EventTargetSetup { brand: v8::Global::new(scope, webidl_brand), set_event_target_data: v8::Global::new(scope, set_event_target_data), }); state.put(ErrorEventClass(v8::Global::new( scope, uncaptured_error_event_class, ))); state.put(PipelineErrorClass(v8::Global::new( scope, pipeline_error_class, ))); GPU { wgsl_language_features: SameObject::new(), } } struct EventTargetSetup { brand: v8::Global, set_event_target_data: v8::Global, } struct ErrorEventClass(v8::Global); struct PipelineErrorClass(v8::Global); pub struct GPU { pub wgsl_language_features: SameObject, } impl GarbageCollected for GPU { fn get_name(&self) -> &'static std::ffi::CStr { c"GPU" } } #[op2] impl GPU { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[async_method] #[cppgc] async fn request_adapter( &self, state: Rc>, #[webidl] options: adapter::GPURequestAdapterOptions, ) -> Option { let mut state = state.borrow_mut(); let dx12_compiler = std::env::var(DX12_COMPILER_ENV_VAR) .ok() .and_then(|s| s.parse().ok()); let backends = std::env::var("DENO_WEBGPU_BACKEND").map_or_else( |_| wgpu_types::Backends::all(), |s| wgpu_types::Backends::from_comma_list(&s), ); let instance = if let Some(instance) = state.try_borrow::() { instance } else { state.put(Arc::new(wgpu_core::global::Global::new( "webgpu", wgpu_types::InstanceDescriptor { backends, flags: wgpu_types::InstanceFlags::from_build_config(), memory_budget_thresholds: wgpu_types::MemoryBudgetThresholds { for_resource_creation: Some(97), for_device_loss: Some(99), }, backend_options: wgpu_types::BackendOptions { dx12: wgpu_types::Dx12BackendOptions { shader_compiler: dx12_compiler .unwrap_or(wgpu_types::Dx12Compiler::Fxc), ..Default::default() }, gl: wgpu_types::GlBackendOptions::default(), noop: wgpu_types::NoopBackendOptions::default(), }, display: None, }, None, ))); state.borrow::() }; // Check that the feature level string is valid. // `wgpu` does not support compatibility-level adapters. As permitted // by the spec, we always return a core-level adapter. wgpu_types::FeatureLevel::deserialize(IntoDeserializer::< serde::de::value::Error, >::into_deserializer( options.feature_level.as_str() )) .ok()?; let descriptor = wgpu_core::instance::RequestAdapterOptions { power_preference: options .power_preference .map(|pp| match pp { adapter::GPUPowerPreference::LowPower => PowerPreference::LowPower, adapter::GPUPowerPreference::HighPerformance => { PowerPreference::HighPerformance } }) .unwrap_or_default(), force_fallback_adapter: options.force_fallback_adapter, compatible_surface: None, // windowless }; let id = instance.request_adapter(&descriptor, backends, None).ok()?; Some(adapter::GPUAdapter { instance: instance.clone(), features: SameObject::new(), limits: SameObject::new(), info: Rc::new(SameObject::new()), id, }) } #[string] fn getPreferredCanvasFormat(&self) -> &'static str { // https://github.com/mozilla/gecko-dev/blob/b75080bb8b11844d18cb5f9ac6e68a866ef8e243/dom/webgpu/Instance.h#L42-L47 if cfg!(target_os = "android") { texture::GPUTextureFormat::Rgba8unorm.as_str() } else { texture::GPUTextureFormat::Bgra8unorm.as_str() } } #[getter] #[global] fn wgslLanguageFeatures( &self, scope: &mut v8::HandleScope, ) -> v8::Global { self .wgsl_language_features .get(scope, WGSLLanguageFeatures::new) } } pub struct WGSLLanguageFeatures(v8::Global); impl GarbageCollected for WGSLLanguageFeatures { fn get_name(&self) -> &'static std::ffi::CStr { c"WGSLLanguageFeatures" } } impl WGSLLanguageFeatures { pub fn new(scope: &mut v8::HandleScope) -> Self { use wgpu_core::naga::front::wgsl::ImplementedLanguageExtension; let set = v8::Set::new(scope); for ext in ImplementedLanguageExtension::all() { let key = v8::String::new(scope, ext.to_ident()).unwrap(); set.add(scope, key.into()); } Self(v8::Global::new(scope, >::from(set))) } } #[op2] impl WGSLLanguageFeatures { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[global] #[symbol("setlike_set")] fn set(&self) -> v8::Global { self.0.clone() } } fn transform_label<'a>(label: String) -> Option> { if label.is_empty() { None } else { Some(std::borrow::Cow::Owned(label)) } } ================================================ FILE: deno_webgpu/pipeline_layout.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use deno_core::cppgc::Ptr; use deno_core::op2; use deno_core::webidl::Nullable; use deno_core::webidl::WebIdlInterfaceConverter; use deno_core::GarbageCollected; use deno_core::WebIDL; use crate::error::GPUGenericError; use crate::Instance; pub struct GPUPipelineLayout { pub instance: Instance, pub id: wgpu_core::id::PipelineLayoutId, pub label: String, } impl Drop for GPUPipelineLayout { fn drop(&mut self) { self.instance.pipeline_layout_drop(self.id); } } impl WebIdlInterfaceConverter for GPUPipelineLayout { const NAME: &'static str = "GPUPipelineLayout"; } impl GarbageCollected for GPUPipelineLayout { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUPipelineLayout" } } #[op2] impl GPUPipelineLayout { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUPipelineLayoutDescriptor { #[webidl(default = String::new())] pub label: String, pub bind_group_layouts: Vec>>, } ================================================ FILE: deno_webgpu/query_set.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use deno_core::op2; use deno_core::webidl::WebIdlInterfaceConverter; use deno_core::GarbageCollected; use deno_core::WebIDL; use deno_error::JsErrorBox; use crate::error::GPUGenericError; use crate::Instance; pub struct GPUQuerySet { pub instance: Instance, pub id: wgpu_core::id::QuerySetId, pub r#type: GPUQueryType, pub count: u32, pub label: String, } impl Drop for GPUQuerySet { fn drop(&mut self) { self.instance.query_set_drop(self.id); } } impl WebIdlInterfaceConverter for GPUQuerySet { const NAME: &'static str = "GPUQuerySet"; } impl GarbageCollected for GPUQuerySet { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUQuerySet" } } #[op2] impl GPUQuerySet { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } #[fast] #[undefined] fn destroy(&self) -> Result<(), JsErrorBox> { // TODO(https://github.com/gfx-rs/wgpu/issues/6495): Destroy the query // set. Until that is supported, it is okay to do nothing here, the // query set will be garbage collected and dropped eventually. Ok(()) } #[getter] #[string] #[rename("type")] fn r#type(&self) -> &'static str { self.r#type.as_str() } #[getter] fn count(&self) -> u32 { self.count } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUQuerySetDescriptor { #[webidl(default = String::new())] pub label: String, pub r#type: GPUQueryType, #[options(enforce_range = true)] pub count: u32, } #[derive(WebIDL, Clone)] #[webidl(enum)] pub(crate) enum GPUQueryType { Occlusion, Timestamp, } impl From for wgpu_types::QueryType { fn from(value: GPUQueryType) -> Self { match value { GPUQueryType::Occlusion => Self::Occlusion, GPUQueryType::Timestamp => Self::Timestamp, } } } ================================================ FILE: deno_webgpu/queue.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::cell::RefCell; use std::rc::Rc; use std::time::Duration; use deno_core::cppgc::Ptr; use deno_core::futures::channel::oneshot; use deno_core::op2; use deno_core::GarbageCollected; use deno_core::WebIDL; use deno_error::JsErrorBox; use crate::buffer::GPUBuffer; use crate::command_buffer::GPUCommandBuffer; use crate::error::GPUGenericError; use crate::texture::GPUTexture; use crate::texture::GPUTextureAspect; use crate::webidl::GPUExtent3D; use crate::webidl::GPUOrigin3D; use crate::Instance; pub struct GPUQueue { pub instance: Instance, pub error_handler: super::error::ErrorHandler, pub label: String, pub id: wgpu_core::id::QueueId, pub device: wgpu_core::id::DeviceId, } impl Drop for GPUQueue { fn drop(&mut self) { self.instance.queue_drop(self.id); } } impl GarbageCollected for GPUQueue { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUQueue" } } #[op2] impl GPUQueue { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } #[required(1)] #[undefined] fn submit( &self, #[webidl] command_buffers: Vec>, ) -> Result<(), JsErrorBox> { let ids = command_buffers .into_iter() .map(|cb| cb.id) .collect::>(); let err = self.instance.queue_submit(self.id, &ids).err(); if let Some((_, err)) = err { self.error_handler.push_error(Some(err)); } Ok(()) } // In the successful case, the promise should resolve to undefined, but // `#[undefined]` does not seem to work here. // https://github.com/denoland/deno/issues/29603 #[async_method] async fn on_submitted_work_done(&self) -> Result<(), JsErrorBox> { let (sender, receiver) = oneshot::channel::<()>(); let callback = Box::new(move || { sender.send(()).unwrap(); }); self .instance .queue_on_submitted_work_done(self.id, callback); let done = Rc::new(RefCell::new(false)); let done_ = done.clone(); let device_poll_fut = async move { while !*done.borrow() { { self .instance .device_poll(self.device, wgpu_types::PollType::wait_indefinitely()) .unwrap(); } tokio::time::sleep(Duration::from_millis(10)).await; } Ok::<(), JsErrorBox>(()) }; let receiver_fut = async move { receiver .await .map_err(|e| JsErrorBox::generic(e.to_string()))?; let mut done = done_.borrow_mut(); *done = true; Ok::<(), JsErrorBox>(()) }; tokio::try_join!(device_poll_fut, receiver_fut)?; Ok(()) } #[required(3)] #[undefined] fn write_buffer( &self, #[webidl] buffer: Ptr, #[webidl(options(enforce_range = true))] buffer_offset: u64, #[anybuffer] buf: &[u8], #[webidl(default = 0, options(enforce_range = true))] data_offset: u64, #[webidl(options(enforce_range = true))] size: Option, ) { let data = match size { Some(size) => { &buf[(data_offset as usize)..((data_offset + size) as usize)] } None => &buf[(data_offset as usize)..], }; let err = self .instance .queue_write_buffer(self.id, buffer.id, buffer_offset, data) .err(); self.error_handler.push_error(err); } #[required(4)] #[undefined] fn write_texture( &self, #[webidl] destination: GPUTexelCopyTextureInfo, #[anybuffer] buf: &[u8], #[webidl] data_layout: GPUTexelCopyBufferLayout, #[webidl] size: GPUExtent3D, ) { let destination = wgpu_types::TexelCopyTextureInfo { texture: destination.texture.id, mip_level: destination.mip_level, origin: destination.origin.into(), aspect: destination.aspect.into(), }; let data_layout = wgpu_types::TexelCopyBufferLayout { offset: data_layout.offset, bytes_per_row: data_layout.bytes_per_row, rows_per_image: data_layout.rows_per_image, }; let err = self .instance .queue_write_texture( self.id, &destination, buf, &data_layout, &size.into(), ) .err(); self.error_handler.push_error(err); } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUTexelCopyTextureInfo { pub texture: Ptr, #[webidl(default = 0)] #[options(enforce_range = true)] pub mip_level: u32, #[webidl(default = Default::default())] pub origin: GPUOrigin3D, #[webidl(default = GPUTextureAspect::All)] pub aspect: GPUTextureAspect, } #[derive(WebIDL)] #[webidl(dictionary)] struct GPUTexelCopyBufferLayout { #[webidl(default = 0)] #[options(enforce_range = true)] offset: u64, #[options(enforce_range = true)] bytes_per_row: Option, #[options(enforce_range = true)] rows_per_image: Option, } ================================================ FILE: deno_webgpu/render_bundle.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::borrow::Cow; use std::cell::RefCell; use std::num::NonZeroU64; use deno_core::cppgc::Ptr; use deno_core::op2; use deno_core::v8; use deno_core::webidl::IntOptions; use deno_core::webidl::Nullable; use deno_core::webidl::WebIdlConverter; use deno_core::webidl::WebIdlError; use deno_core::webidl::WebIdlInterfaceConverter; use deno_core::GarbageCollected; use deno_core::WebIDL; use deno_error::JsErrorBox; use crate::buffer::GPUBuffer; use crate::error::GPUGenericError; use crate::texture::GPUTextureFormat; use crate::Instance; fn c_string_truncated_at_first_nul>>( src: T, ) -> std::ffi::CString { std::ffi::CString::new(src).unwrap_or_else(|err| { let nul_pos = err.nul_position(); std::ffi::CString::new(err.into_vec().split_at(nul_pos).0).unwrap() }) } pub struct GPURenderBundleEncoder { pub instance: Instance, pub error_handler: super::error::ErrorHandler, pub encoder: RefCell>, pub label: String, } impl GarbageCollected for GPURenderBundleEncoder { fn get_name(&self) -> &'static std::ffi::CStr { c"GPURenderBundleEncoder" } } #[op2] impl GPURenderBundleEncoder { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } #[cppgc] fn finish( &self, #[webidl] descriptor: GPURenderBundleDescriptor, ) -> GPURenderBundle { let wgpu_descriptor = wgpu_core::command::RenderBundleDescriptor { label: crate::transform_label(descriptor.label.clone()), }; let (id, err) = self.instance.render_bundle_encoder_finish( self.encoder.borrow_mut().take().unwrap(), &wgpu_descriptor, None, ); self.error_handler.push_error(err); GPURenderBundle { instance: self.instance.clone(), id, label: descriptor.label.clone(), } } #[undefined] fn push_debug_group( &self, #[webidl] group_label: String, ) -> Result<(), JsErrorBox> { let mut encoder = self.encoder.borrow_mut(); let encoder = encoder.as_mut().ok_or_else(|| { JsErrorBox::generic("Encoder has already been finished") })?; let label = c_string_truncated_at_first_nul(group_label); // SAFETY: the string the raw pointer points to lives longer than the below // function invocation. unsafe { wgpu_core::command::bundle_ffi::wgpu_render_bundle_push_debug_group( encoder, label.as_ptr(), ); } Ok(()) } #[fast] #[undefined] fn pop_debug_group(&self) -> Result<(), JsErrorBox> { let mut encoder = self.encoder.borrow_mut(); let encoder = encoder.as_mut().ok_or_else(|| { JsErrorBox::generic("Encoder has already been finished") })?; wgpu_core::command::bundle_ffi::wgpu_render_bundle_pop_debug_group(encoder); Ok(()) } #[undefined] fn insert_debug_marker( &self, #[webidl] marker_label: String, ) -> Result<(), JsErrorBox> { let mut encoder = self.encoder.borrow_mut(); let encoder = encoder.as_mut().ok_or_else(|| { JsErrorBox::generic("Encoder has already been finished") })?; let label = c_string_truncated_at_first_nul(marker_label); // SAFETY: the string the raw pointer points to lives longer than the below // function invocation. unsafe { wgpu_core::command::bundle_ffi::wgpu_render_bundle_insert_debug_marker( encoder, label.as_ptr(), ); } Ok(()) } #[undefined] fn set_bind_group<'a>( &self, scope: &mut v8::HandleScope<'a>, #[webidl(options(enforce_range = true))] index: u32, #[webidl] bind_group: Nullable>, dynamic_offsets: v8::Local<'a, v8::Value>, dynamic_offsets_data_start: v8::Local<'a, v8::Value>, dynamic_offsets_data_length: v8::Local<'a, v8::Value>, ) -> Result<(), SetBindGroupError> { let mut encoder = self.encoder.borrow_mut(); let encoder = encoder.as_mut().ok_or_else(|| { JsErrorBox::generic("Encoder has already been finished") })?; const PREFIX: &str = "Failed to execute 'setBindGroup' on 'GPUComputePassEncoder'"; if let Ok(uint_32) = dynamic_offsets.try_cast::() { let start = u64::convert( scope, dynamic_offsets_data_start, Cow::Borrowed(PREFIX), (|| Cow::Borrowed("Argument 4")).into(), &IntOptions { clamp: false, enforce_range: true, }, )? as usize; let len = u32::convert( scope, dynamic_offsets_data_length, Cow::Borrowed(PREFIX), (|| Cow::Borrowed("Argument 5")).into(), &IntOptions { clamp: false, enforce_range: true, }, )? as usize; let ab = uint_32.buffer(scope).unwrap(); let ptr = ab.data().unwrap(); let ab_len = ab.byte_length() / 4; // SAFETY: created from an array buffer, slice is dropped at end of function call let data = unsafe { std::slice::from_raw_parts(ptr.as_ptr() as _, ab_len) }; let offsets = &data[start..(start + len)]; // SAFETY: wgpu FFI call unsafe { wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_bind_group( encoder, index, bind_group.into_option().map(|bind_group| bind_group.id), offsets.as_ptr(), offsets.len(), ); } } else { let offsets = >>::convert( scope, dynamic_offsets, Cow::Borrowed(PREFIX), (|| Cow::Borrowed("Argument 3")).into(), &IntOptions { clamp: false, enforce_range: true, }, )? .unwrap_or_default(); // SAFETY: wgpu FFI call unsafe { wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_bind_group( encoder, index, bind_group.into_option().map(|bind_group| bind_group.id), offsets.as_ptr(), offsets.len(), ); } } Ok(()) } #[undefined] fn set_pipeline( &self, #[webidl] pipeline: Ptr, ) -> Result<(), JsErrorBox> { let mut encoder = self.encoder.borrow_mut(); let encoder = encoder.as_mut().ok_or_else(|| { JsErrorBox::generic("Encoder has already been finished") })?; wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_pipeline( encoder, pipeline.id, ); Ok(()) } #[required(2)] #[undefined] fn set_index_buffer( &self, #[webidl] buffer: Ptr, #[webidl] index_format: crate::render_pipeline::GPUIndexFormat, #[webidl(default = 0, options(enforce_range = true))] offset: u64, #[webidl(options(enforce_range = true))] size: Option, ) -> Result<(), JsErrorBox> { let mut encoder = self.encoder.borrow_mut(); let encoder = encoder.as_mut().ok_or_else(|| { JsErrorBox::generic("Encoder has already been finished") })?; encoder.set_index_buffer( buffer.id, index_format.into(), offset, size.and_then(NonZeroU64::new), ); Ok(()) } #[required(2)] #[undefined] fn set_vertex_buffer( &self, #[webidl(options(enforce_range = true))] slot: u32, #[webidl] buffer: Ptr, // TODO(wgpu): support nullable buffer #[webidl(default = 0, options(enforce_range = true))] offset: u64, #[webidl(options(enforce_range = true))] size: Option, ) -> Result<(), JsErrorBox> { let mut encoder = self.encoder.borrow_mut(); let encoder = encoder.as_mut().ok_or_else(|| { JsErrorBox::generic("Encoder has already been finished") })?; wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_vertex_buffer( encoder, slot, buffer.id, offset, size.and_then(NonZeroU64::new), ); Ok(()) } #[required(1)] #[undefined] fn draw( &self, #[webidl(options(enforce_range = true))] vertex_count: u32, #[webidl(default = 1, options(enforce_range = true))] instance_count: u32, #[webidl(default = 0, options(enforce_range = true))] first_vertex: u32, #[webidl(default = 0, options(enforce_range = true))] first_instance: u32, ) -> Result<(), JsErrorBox> { let mut encoder = self.encoder.borrow_mut(); let encoder = encoder.as_mut().ok_or_else(|| { JsErrorBox::generic("Encoder has already been finished") })?; wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw( encoder, vertex_count, instance_count, first_vertex, first_instance, ); Ok(()) } #[required(1)] #[undefined] fn draw_indexed( &self, #[webidl(options(enforce_range = true))] index_count: u32, #[webidl(default = 1, options(enforce_range = true))] instance_count: u32, #[webidl(default = 0, options(enforce_range = true))] first_index: u32, #[webidl(default = 0, options(enforce_range = true))] base_vertex: i32, #[webidl(default = 0, options(enforce_range = true))] first_instance: u32, ) -> Result<(), JsErrorBox> { let mut encoder = self.encoder.borrow_mut(); let encoder = encoder.as_mut().ok_or_else(|| { JsErrorBox::generic("Encoder has already been finished") })?; wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indexed( encoder, index_count, instance_count, first_index, base_vertex, first_instance, ); Ok(()) } #[required(2)] #[undefined] fn draw_indirect( &self, #[webidl] indirect_buffer: Ptr, #[webidl(options(enforce_range = true))] indirect_offset: u64, ) -> Result<(), JsErrorBox> { let mut encoder = self.encoder.borrow_mut(); let encoder = encoder.as_mut().ok_or_else(|| { JsErrorBox::generic("Encoder has already been finished") })?; wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indirect( encoder, indirect_buffer.id, indirect_offset, ); Ok(()) } #[required(2)] #[undefined] fn draw_indexed_indirect( &self, #[webidl] indirect_buffer: Ptr, #[webidl(options(enforce_range = true))] indirect_offset: u64, ) -> Result<(), JsErrorBox> { let mut encoder = self.encoder.borrow_mut(); let encoder = encoder.as_mut().ok_or_else(|| { JsErrorBox::generic("Encoder has already been finished") })?; wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indexed_indirect( encoder, indirect_buffer.id, indirect_offset, ); Ok(()) } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPURenderBundleEncoderDescriptor { #[webidl(default = String::new())] pub label: String, pub color_formats: Vec>, pub depth_stencil_format: Option, #[webidl(default = 1)] #[options(enforce_range = true)] pub sample_count: u32, #[webidl(default = false)] pub depth_read_only: bool, #[webidl(default = false)] pub stencil_read_only: bool, } #[derive(Debug, thiserror::Error, deno_error::JsError)] enum SetBindGroupError { #[class(inherit)] #[error(transparent)] WebIDL(#[from] WebIdlError), #[class(inherit)] #[error(transparent)] Other(#[from] JsErrorBox), } pub struct GPURenderBundle { pub instance: Instance, pub id: wgpu_core::id::RenderBundleId, pub label: String, } impl Drop for GPURenderBundle { fn drop(&mut self) { self.instance.render_bundle_drop(self.id); } } impl WebIdlInterfaceConverter for GPURenderBundle { const NAME: &'static str = "GPURenderBundle"; } impl GarbageCollected for GPURenderBundle { fn get_name(&self) -> &'static std::ffi::CStr { c"GPURenderBundle" } } #[op2] impl GPURenderBundle { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPURenderBundleDescriptor { #[webidl(default = String::new())] pub label: String, } ================================================ FILE: deno_webgpu/render_pass.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::borrow::Cow; use std::cell::RefCell; use std::num::NonZeroU64; use deno_core::cppgc::Ptr; use deno_core::op2; use deno_core::v8; use deno_core::v8::HandleScope; use deno_core::v8::Local; use deno_core::v8::Value; use deno_core::webidl::ContextFn; use deno_core::webidl::IntOptions; use deno_core::webidl::Nullable; use deno_core::webidl::WebIdlConverter; use deno_core::webidl::WebIdlError; use deno_core::GarbageCollected; use deno_core::WebIDL; use crate::buffer::GPUBuffer; use crate::error::GPUGenericError; use crate::render_bundle::GPURenderBundle; use crate::texture::GPUTexture; use crate::texture::GPUTextureView; use crate::webidl::GPUColor; use crate::Instance; pub struct GPURenderPassEncoder { pub instance: Instance, pub error_handler: super::error::ErrorHandler, pub render_pass: RefCell, pub label: String, } impl GarbageCollected for GPURenderPassEncoder { fn get_name(&self) -> &'static std::ffi::CStr { c"GPURenderPassEncoder" } } #[op2] impl GPURenderPassEncoder { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } #[required(6)] #[undefined] fn set_viewport( &self, #[webidl] x: f32, #[webidl] y: f32, #[webidl] width: f32, #[webidl] height: f32, #[webidl] min_depth: f32, #[webidl] max_depth: f32, ) { let err = self .instance .render_pass_set_viewport( &mut self.render_pass.borrow_mut(), x, y, width, height, min_depth, max_depth, ) .err(); self.error_handler.push_error(err); } #[required(4)] #[undefined] fn set_scissor_rect( &self, #[webidl(options(enforce_range = true))] x: u32, #[webidl(options(enforce_range = true))] y: u32, #[webidl(options(enforce_range = true))] width: u32, #[webidl(options(enforce_range = true))] height: u32, ) { let err = self .instance .render_pass_set_scissor_rect( &mut self.render_pass.borrow_mut(), x, y, width, height, ) .err(); self.error_handler.push_error(err); } #[required(1)] #[undefined] fn set_blend_constant(&self, #[webidl] color: GPUColor) { let err = self .instance .render_pass_set_blend_constant( &mut self.render_pass.borrow_mut(), color.into(), ) .err(); self.error_handler.push_error(err); } #[required(1)] #[undefined] fn set_stencil_reference( &self, #[webidl(options(enforce_range = true))] reference: u32, ) { let err = self .instance .render_pass_set_stencil_reference( &mut self.render_pass.borrow_mut(), reference, ) .err(); self.error_handler.push_error(err); } #[required(1)] #[undefined] fn begin_occlusion_query( &self, #[webidl(options(enforce_range = true))] query_index: u32, ) { let err = self .instance .render_pass_begin_occlusion_query( &mut self.render_pass.borrow_mut(), query_index, ) .err(); self.error_handler.push_error(err); } #[fast] #[undefined] fn end_occlusion_query(&self) { let err = self .instance .render_pass_end_occlusion_query(&mut self.render_pass.borrow_mut()) .err(); self.error_handler.push_error(err); } #[required(1)] #[undefined] fn execute_bundles(&self, #[webidl] bundles: Vec>) { let err = self .instance .render_pass_execute_bundles( &mut self.render_pass.borrow_mut(), &bundles .into_iter() .map(|bundle| bundle.id) .collect::>(), ) .err(); self.error_handler.push_error(err); } #[fast] #[undefined] fn end(&self) { let err = self .instance .render_pass_end(&mut self.render_pass.borrow_mut()) .err(); self.error_handler.push_error(err); } #[undefined] fn push_debug_group(&self, #[webidl] group_label: String) { let err = self .instance .render_pass_push_debug_group( &mut self.render_pass.borrow_mut(), &group_label, 0, // wgpu#975 ) .err(); self.error_handler.push_error(err); } #[fast] #[undefined] fn pop_debug_group(&self) { let err = self .instance .render_pass_pop_debug_group(&mut self.render_pass.borrow_mut()) .err(); self.error_handler.push_error(err); } #[undefined] fn insert_debug_marker(&self, #[webidl] marker_label: String) { let err = self .instance .render_pass_insert_debug_marker( &mut self.render_pass.borrow_mut(), &marker_label, 0, // wgpu#975 ) .err(); self.error_handler.push_error(err); } #[undefined] fn set_bind_group<'a>( &self, scope: &mut v8::HandleScope<'a>, #[webidl(options(enforce_range = true))] index: u32, #[webidl] bind_group: Nullable>, dynamic_offsets: v8::Local<'a, v8::Value>, dynamic_offsets_data_start: v8::Local<'a, v8::Value>, dynamic_offsets_data_length: v8::Local<'a, v8::Value>, ) -> Result<(), WebIdlError> { const PREFIX: &str = "Failed to execute 'setBindGroup' on 'GPUComputePassEncoder'"; let err = if let Ok(uint_32) = dynamic_offsets.try_cast::() { let start = u64::convert( scope, dynamic_offsets_data_start, Cow::Borrowed(PREFIX), (|| Cow::Borrowed("Argument 4")).into(), &IntOptions { clamp: false, enforce_range: true, }, )? as usize; let len = u32::convert( scope, dynamic_offsets_data_length, Cow::Borrowed(PREFIX), (|| Cow::Borrowed("Argument 5")).into(), &IntOptions { clamp: false, enforce_range: true, }, )? as usize; let ab = uint_32.buffer(scope).unwrap(); let ptr = ab.data().unwrap(); let ab_len = ab.byte_length() / 4; // SAFETY: created from an array buffer, slice is dropped at end of function call let data = unsafe { std::slice::from_raw_parts(ptr.as_ptr() as _, ab_len) }; let offsets = &data[start..(start + len)]; self .instance .render_pass_set_bind_group( &mut self.render_pass.borrow_mut(), index, bind_group.into_option().map(|bind_group| bind_group.id), offsets, ) .err() } else { let offsets = >>::convert( scope, dynamic_offsets, Cow::Borrowed(PREFIX), (|| Cow::Borrowed("Argument 3")).into(), &IntOptions { clamp: false, enforce_range: true, }, )? .unwrap_or_default(); self .instance .render_pass_set_bind_group( &mut self.render_pass.borrow_mut(), index, bind_group.into_option().map(|bind_group| bind_group.id), &offsets, ) .err() }; self.error_handler.push_error(err); Ok(()) } #[undefined] fn set_pipeline( &self, #[webidl] pipeline: Ptr, ) { let err = self .instance .render_pass_set_pipeline(&mut self.render_pass.borrow_mut(), pipeline.id) .err(); self.error_handler.push_error(err); } #[required(2)] #[undefined] fn set_index_buffer( &self, #[webidl] buffer: Ptr, #[webidl] index_format: crate::render_pipeline::GPUIndexFormat, #[webidl(default = 0, options(enforce_range = true))] offset: u64, #[webidl(options(enforce_range = true))] size: Option, ) { let err = self .instance .render_pass_set_index_buffer( &mut self.render_pass.borrow_mut(), buffer.id, index_format.into(), offset, size.and_then(NonZeroU64::new), ) .err(); self.error_handler.push_error(err); } #[required(2)] #[undefined] fn set_vertex_buffer( &self, #[webidl(options(enforce_range = true))] slot: u32, #[webidl] buffer: Ptr, // TODO(wgpu): support nullable buffer #[webidl(default = 0, options(enforce_range = true))] offset: u64, #[webidl(options(enforce_range = true))] size: Option, ) { let err = self .instance .render_pass_set_vertex_buffer( &mut self.render_pass.borrow_mut(), slot, buffer.id, offset, size.and_then(NonZeroU64::new), ) .err(); self.error_handler.push_error(err); } #[required(1)] #[undefined] fn draw( &self, #[webidl(options(enforce_range = true))] vertex_count: u32, #[webidl(default = 1, options(enforce_range = true))] instance_count: u32, #[webidl(default = 0, options(enforce_range = true))] first_vertex: u32, #[webidl(default = 0, options(enforce_range = true))] first_instance: u32, ) { let err = self .instance .render_pass_draw( &mut self.render_pass.borrow_mut(), vertex_count, instance_count, first_vertex, first_instance, ) .err(); self.error_handler.push_error(err); } #[required(1)] #[undefined] fn draw_indexed( &self, #[webidl(options(enforce_range = true))] index_count: u32, #[webidl(default = 1, options(enforce_range = true))] instance_count: u32, #[webidl(default = 0, options(enforce_range = true))] first_index: u32, #[webidl(default = 0, options(enforce_range = true))] base_vertex: i32, #[webidl(default = 0, options(enforce_range = true))] first_instance: u32, ) { let err = self .instance .render_pass_draw_indexed( &mut self.render_pass.borrow_mut(), index_count, instance_count, first_index, base_vertex, first_instance, ) .err(); self.error_handler.push_error(err); } #[required(2)] #[undefined] fn draw_indirect( &self, #[webidl] indirect_buffer: Ptr, #[webidl(options(enforce_range = true))] indirect_offset: u64, ) { let err = self .instance .render_pass_draw_indirect( &mut self.render_pass.borrow_mut(), indirect_buffer.id, indirect_offset, ) .err(); self.error_handler.push_error(err); } #[required(2)] #[undefined] fn draw_indexed_indirect( &self, #[webidl] indirect_buffer: Ptr, #[webidl(options(enforce_range = true))] indirect_offset: u64, ) { let err = self .instance .render_pass_draw_indexed_indirect( &mut self.render_pass.borrow_mut(), indirect_buffer.id, indirect_offset, ) .err(); self.error_handler.push_error(err); } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPURenderPassDescriptor { #[webidl(default = String::new())] pub label: String, pub color_attachments: Vec>, pub depth_stencil_attachment: Option, pub occlusion_query_set: Option>, pub timestamp_writes: Option, /*#[webidl(default = 50000000)] #[options(enforce_range = true)] pub max_draw_count: u64,*/ #[webidl(default = 0)] #[options(enforce_range = true)] pub multiview_mask: u32, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPURenderPassColorAttachment { pub view: GPUTextureOrView, #[options(enforce_range = true)] pub depth_slice: Option, pub resolve_target: Option, pub clear_value: Option, pub load_op: GPULoadOp, pub store_op: GPUStoreOp, } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPULoadOp { Load, Clear, } impl GPULoadOp { pub fn with_default_value( self, val: Option, ) -> wgpu_core::command::LoadOp { match self { GPULoadOp::Load => wgpu_core::command::LoadOp::Load, GPULoadOp::Clear => { wgpu_core::command::LoadOp::Clear(val.unwrap_or_default()) } } } pub fn with_value(self, val: V) -> wgpu_core::command::LoadOp { match self { GPULoadOp::Load => wgpu_core::command::LoadOp::Load, GPULoadOp::Clear => wgpu_core::command::LoadOp::Clear(val), } } } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUStoreOp { Store, Discard, } impl From for wgpu_core::command::StoreOp { fn from(value: GPUStoreOp) -> Self { match value { GPUStoreOp::Store => Self::Store, GPUStoreOp::Discard => Self::Discard, } } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPURenderPassDepthStencilAttachment { pub view: GPUTextureOrView, pub depth_clear_value: Option, pub depth_load_op: Option, pub depth_store_op: Option, #[webidl(default = false)] pub depth_read_only: bool, #[webidl(default = 0)] #[options(enforce_range = true)] pub stencil_clear_value: u32, pub stencil_load_op: Option, pub stencil_store_op: Option, #[webidl(default = false)] pub stencil_read_only: bool, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPURenderPassTimestampWrites { pub query_set: Ptr, #[options(enforce_range = true)] pub beginning_of_pass_write_index: Option, #[options(enforce_range = true)] pub end_of_pass_write_index: Option, } pub(crate) enum GPUTextureOrView { Texture(Ptr), TextureView(Ptr), } impl GPUTextureOrView { pub(crate) fn to_view_id(&self) -> wgpu_core::id::TextureViewId { match self { Self::Texture(texture) => texture.default_view_id(), Self::TextureView(texture_view) => texture_view.id, } } } impl<'a> WebIdlConverter<'a> for GPUTextureOrView { type Options = (); fn convert<'b>( scope: &mut HandleScope<'a>, value: Local<'a, Value>, prefix: Cow<'static, str>, context: ContextFn<'b>, options: &Self::Options, ) -> Result { >::convert( scope, value, prefix.clone(), context.borrowed(), options, ) .map(Self::Texture) .or_else(|_| { >::convert( scope, value, prefix.clone(), context.borrowed(), options, ) .map(Self::TextureView) }) } } ================================================ FILE: deno_webgpu/render_pipeline.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use deno_core::cppgc::Ptr; use deno_core::op2; use deno_core::webidl::Nullable; use deno_core::webidl::WebIdlInterfaceConverter; use deno_core::GarbageCollected; use deno_core::WebIDL; use indexmap::IndexMap; use crate::bind_group_layout::GPUBindGroupLayout; use crate::error::GPUGenericError; use crate::sampler::GPUCompareFunction; use crate::shader::GPUShaderModule; use crate::texture::GPUTextureFormat; use crate::webidl::GPUColorWriteFlags; use crate::webidl::GPUPipelineLayoutOrGPUAutoLayoutMode; use crate::Instance; pub struct GPURenderPipeline { pub instance: Instance, pub error_handler: super::error::ErrorHandler, pub id: wgpu_core::id::RenderPipelineId, pub label: String, } impl Drop for GPURenderPipeline { fn drop(&mut self) { self.instance.render_pipeline_drop(self.id); } } impl WebIdlInterfaceConverter for GPURenderPipeline { const NAME: &'static str = "GPURenderPipeline"; } impl GarbageCollected for GPURenderPipeline { fn get_name(&self) -> &'static std::ffi::CStr { c"GPURenderPipeline" } } #[op2] impl GPURenderPipeline { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } #[cppgc] fn get_bind_group_layout(&self, #[webidl] index: u32) -> GPUBindGroupLayout { let (id, err) = self .instance .render_pipeline_get_bind_group_layout(self.id, index, None); self.error_handler.push_error(err); // TODO(wgpu): needs to add a way to retrieve the label GPUBindGroupLayout { instance: self.instance.clone(), id, label: "".to_string(), } } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPURenderPipelineDescriptor { #[webidl(default = String::new())] pub label: String, pub layout: GPUPipelineLayoutOrGPUAutoLayoutMode, pub vertex: GPUVertexState, pub primitive: GPUPrimitiveState, pub depth_stencil: Option, pub multisample: GPUMultisampleState, pub fragment: Option, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUMultisampleState { #[webidl(default = 1)] #[options(enforce_range = true)] pub count: u32, #[webidl(default = 0xFFFFFFFF)] #[options(enforce_range = true)] pub mask: u32, #[webidl(default = false)] pub alpha_to_coverage_enabled: bool, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUDepthStencilState { pub format: GPUTextureFormat, pub depth_write_enabled: Option, pub depth_compare: Option, pub stencil_front: GPUStencilFaceState, pub stencil_back: GPUStencilFaceState, #[webidl(default = 0xFFFFFFFF)] #[options(enforce_range = true)] pub stencil_read_mask: u32, #[webidl(default = 0xFFFFFFFF)] #[options(enforce_range = true)] pub stencil_write_mask: u32, #[webidl(default = 0)] #[options(enforce_range = true)] pub depth_bias: i32, #[webidl(default = 0.0)] pub depth_bias_slope_scale: f32, #[webidl(default = 0.0)] pub depth_bias_clamp: f32, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUStencilFaceState { #[webidl(default = GPUCompareFunction::Always)] pub compare: GPUCompareFunction, #[webidl(default = GPUStencilOperation::Keep)] pub fail_op: GPUStencilOperation, #[webidl(default = GPUStencilOperation::Keep)] pub depth_fail_op: GPUStencilOperation, #[webidl(default = GPUStencilOperation::Keep)] pub pass_op: GPUStencilOperation, } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUStencilOperation { Keep, Zero, Replace, Invert, IncrementClamp, DecrementClamp, IncrementWrap, DecrementWrap, } impl From for wgpu_types::StencilOperation { fn from(value: GPUStencilOperation) -> Self { match value { GPUStencilOperation::Keep => Self::Keep, GPUStencilOperation::Zero => Self::Zero, GPUStencilOperation::Replace => Self::Replace, GPUStencilOperation::Invert => Self::Invert, GPUStencilOperation::IncrementClamp => Self::IncrementClamp, GPUStencilOperation::DecrementClamp => Self::DecrementClamp, GPUStencilOperation::IncrementWrap => Self::IncrementWrap, GPUStencilOperation::DecrementWrap => Self::DecrementWrap, } } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUVertexState { pub module: Ptr, pub entry_point: Option, #[webidl(default = Default::default())] pub constants: IndexMap, #[webidl(default = vec![])] pub buffers: Vec>, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUFragmentState { pub module: Ptr, pub entry_point: Option, #[webidl(default = Default::default())] pub constants: IndexMap, pub targets: Vec>, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUColorTargetState { pub format: GPUTextureFormat, pub blend: Option, #[webidl(default = GPUColorWriteFlags(wgpu_types::ColorWrites::ALL))] pub write_mask: GPUColorWriteFlags, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUBlendState { pub color: GPUBlendComponent, pub alpha: GPUBlendComponent, } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUBlendComponent { #[webidl(default = GPUBlendOperation::Add)] pub operation: GPUBlendOperation, #[webidl(default = GPUBlendFactor::One)] pub src_factor: GPUBlendFactor, #[webidl(default = GPUBlendFactor::Zero)] pub dst_factor: GPUBlendFactor, } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUBlendOperation { Add, Subtract, ReverseSubtract, Min, Max, } impl From for wgpu_types::BlendOperation { fn from(value: GPUBlendOperation) -> Self { match value { GPUBlendOperation::Add => Self::Add, GPUBlendOperation::Subtract => Self::Subtract, GPUBlendOperation::ReverseSubtract => Self::ReverseSubtract, GPUBlendOperation::Min => Self::Min, GPUBlendOperation::Max => Self::Max, } } } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUBlendFactor { #[webidl(rename = "zero")] Zero, #[webidl(rename = "one")] One, #[webidl(rename = "src")] Src, #[webidl(rename = "one-minus-src")] OneMinusSrc, #[webidl(rename = "src-alpha")] SrcAlpha, #[webidl(rename = "one-minus-src-alpha")] OneMinusSrcAlpha, #[webidl(rename = "dst")] Dst, #[webidl(rename = "one-minus-dst")] OneMinusDst, #[webidl(rename = "dst-alpha")] DstAlpha, #[webidl(rename = "one-minus-dst-alpha")] OneMinusDstAlpha, #[webidl(rename = "src-alpha-saturated")] SrcAlphaSaturated, #[webidl(rename = "constant")] Constant, #[webidl(rename = "one-minus-constant")] OneMinusConstant, #[webidl(rename = "src1")] Src1, #[webidl(rename = "one-minus-src1")] OneMinusSrc1, #[webidl(rename = "src1-alpha")] Src1Alpha, #[webidl(rename = "one-minus-src1-alpha")] OneMinusSrc1Alpha, } impl From for wgpu_types::BlendFactor { fn from(value: GPUBlendFactor) -> Self { match value { GPUBlendFactor::Zero => Self::Zero, GPUBlendFactor::One => Self::One, GPUBlendFactor::Src => Self::Src, GPUBlendFactor::OneMinusSrc => Self::OneMinusSrc, GPUBlendFactor::SrcAlpha => Self::SrcAlpha, GPUBlendFactor::OneMinusSrcAlpha => Self::OneMinusSrcAlpha, GPUBlendFactor::Dst => Self::Dst, GPUBlendFactor::OneMinusDst => Self::OneMinusDst, GPUBlendFactor::DstAlpha => Self::DstAlpha, GPUBlendFactor::OneMinusDstAlpha => Self::OneMinusDstAlpha, GPUBlendFactor::SrcAlphaSaturated => Self::SrcAlphaSaturated, GPUBlendFactor::Constant => Self::Constant, GPUBlendFactor::OneMinusConstant => Self::OneMinusConstant, GPUBlendFactor::Src1 => Self::Src1, GPUBlendFactor::OneMinusSrc1 => Self::OneMinusSrc1, GPUBlendFactor::Src1Alpha => Self::Src1Alpha, GPUBlendFactor::OneMinusSrc1Alpha => Self::OneMinusSrc1Alpha, } } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUPrimitiveState { #[webidl(default = GPUPrimitiveTopology::TriangleList)] pub topology: GPUPrimitiveTopology, pub strip_index_format: Option, #[webidl(default = GPUFrontFace::Ccw)] pub front_face: GPUFrontFace, #[webidl(default = GPUCullMode::None)] pub cull_mode: GPUCullMode, #[webidl(default = false)] pub unclipped_depth: bool, } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUPrimitiveTopology { PointList, LineList, LineStrip, TriangleList, TriangleStrip, } impl From for wgpu_types::PrimitiveTopology { fn from(value: GPUPrimitiveTopology) -> Self { match value { GPUPrimitiveTopology::PointList => Self::PointList, GPUPrimitiveTopology::LineList => Self::LineList, GPUPrimitiveTopology::LineStrip => Self::LineStrip, GPUPrimitiveTopology::TriangleList => Self::TriangleList, GPUPrimitiveTopology::TriangleStrip => Self::TriangleStrip, } } } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUIndexFormat { #[webidl(rename = "uint16")] Uint16, #[webidl(rename = "uint32")] Uint32, } impl From for wgpu_types::IndexFormat { fn from(value: GPUIndexFormat) -> Self { match value { GPUIndexFormat::Uint16 => Self::Uint16, GPUIndexFormat::Uint32 => Self::Uint32, } } } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUFrontFace { Ccw, Cw, } impl From for wgpu_types::FrontFace { fn from(value: GPUFrontFace) -> Self { match value { GPUFrontFace::Ccw => Self::Ccw, GPUFrontFace::Cw => Self::Cw, } } } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUCullMode { None, Front, Back, } impl From for Option { fn from(value: GPUCullMode) -> Self { match value { GPUCullMode::None => None, GPUCullMode::Front => Some(wgpu_types::Face::Front), GPUCullMode::Back => Some(wgpu_types::Face::Back), } } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUVertexBufferLayout { #[options(enforce_range = true)] pub array_stride: u64, #[webidl(default = GPUVertexStepMode::Vertex)] pub step_mode: GPUVertexStepMode, pub attributes: Vec, } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUVertexStepMode { Vertex, Instance, } impl From for wgpu_types::VertexStepMode { fn from(value: GPUVertexStepMode) -> Self { match value { GPUVertexStepMode::Vertex => Self::Vertex, GPUVertexStepMode::Instance => Self::Instance, } } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUVertexAttribute { pub format: GPUVertexFormat, #[options(enforce_range = true)] pub offset: u64, #[options(enforce_range = true)] pub shader_location: u32, } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUVertexFormat { #[webidl(rename = "uint8")] Uint8, #[webidl(rename = "uint8x2")] Uint8x2, #[webidl(rename = "uint8x4")] Uint8x4, #[webidl(rename = "sint8")] Sint8, #[webidl(rename = "sint8x2")] Sint8x2, #[webidl(rename = "sint8x4")] Sint8x4, #[webidl(rename = "unorm8")] Unorm8, #[webidl(rename = "unorm8x2")] Unorm8x2, #[webidl(rename = "unorm8x4")] Unorm8x4, #[webidl(rename = "snorm8")] Snorm8, #[webidl(rename = "snorm8x2")] Snorm8x2, #[webidl(rename = "snorm8x4")] Snorm8x4, #[webidl(rename = "uint16")] Uint16, #[webidl(rename = "uint16x2")] Uint16x2, #[webidl(rename = "uint16x4")] Uint16x4, #[webidl(rename = "sint16")] Sint16, #[webidl(rename = "sint16x2")] Sint16x2, #[webidl(rename = "sint16x4")] Sint16x4, #[webidl(rename = "unorm16")] Unorm16, #[webidl(rename = "unorm16x2")] Unorm16x2, #[webidl(rename = "unorm16x4")] Unorm16x4, #[webidl(rename = "snorm16")] Snorm16, #[webidl(rename = "snorm16x2")] Snorm16x2, #[webidl(rename = "snorm16x4")] Snorm16x4, #[webidl(rename = "float16")] Float16, #[webidl(rename = "float16x2")] Float16x2, #[webidl(rename = "float16x4")] Float16x4, #[webidl(rename = "float32")] Float32, #[webidl(rename = "float32x2")] Float32x2, #[webidl(rename = "float32x3")] Float32x3, #[webidl(rename = "float32x4")] Float32x4, #[webidl(rename = "uint32")] Uint32, #[webidl(rename = "uint32x2")] Uint32x2, #[webidl(rename = "uint32x3")] Uint32x3, #[webidl(rename = "uint32x4")] Uint32x4, #[webidl(rename = "sint32")] Sint32, #[webidl(rename = "sint32x2")] Sint32x2, #[webidl(rename = "sint32x3")] Sint32x3, #[webidl(rename = "sint32x4")] Sint32x4, #[webidl(rename = "unorm10-10-10-2")] Unorm1010102, #[webidl(rename = "unorm8x4-bgra")] Unorm8x4Bgra, } impl From for wgpu_types::VertexFormat { fn from(value: GPUVertexFormat) -> Self { match value { GPUVertexFormat::Uint8 => Self::Uint8, GPUVertexFormat::Uint8x2 => Self::Uint8x2, GPUVertexFormat::Uint8x4 => Self::Uint8x4, GPUVertexFormat::Sint8 => Self::Sint8, GPUVertexFormat::Sint8x2 => Self::Sint8x2, GPUVertexFormat::Sint8x4 => Self::Sint8x4, GPUVertexFormat::Unorm8 => Self::Unorm8, GPUVertexFormat::Unorm8x2 => Self::Unorm8x2, GPUVertexFormat::Unorm8x4 => Self::Unorm8x4, GPUVertexFormat::Snorm8 => Self::Snorm8, GPUVertexFormat::Snorm8x2 => Self::Snorm8x2, GPUVertexFormat::Snorm8x4 => Self::Snorm8x4, GPUVertexFormat::Uint16 => Self::Uint16, GPUVertexFormat::Uint16x2 => Self::Uint16x2, GPUVertexFormat::Uint16x4 => Self::Uint16x4, GPUVertexFormat::Sint16 => Self::Sint16, GPUVertexFormat::Sint16x2 => Self::Sint16x2, GPUVertexFormat::Sint16x4 => Self::Sint16x4, GPUVertexFormat::Unorm16 => Self::Unorm16, GPUVertexFormat::Unorm16x2 => Self::Unorm16x2, GPUVertexFormat::Unorm16x4 => Self::Unorm16x4, GPUVertexFormat::Snorm16 => Self::Snorm16, GPUVertexFormat::Snorm16x2 => Self::Snorm16x2, GPUVertexFormat::Snorm16x4 => Self::Snorm16x4, GPUVertexFormat::Float16 => Self::Float16, GPUVertexFormat::Float16x2 => Self::Float16x2, GPUVertexFormat::Float16x4 => Self::Float16x4, GPUVertexFormat::Float32 => Self::Float32, GPUVertexFormat::Float32x2 => Self::Float32x2, GPUVertexFormat::Float32x3 => Self::Float32x3, GPUVertexFormat::Float32x4 => Self::Float32x4, GPUVertexFormat::Uint32 => Self::Uint32, GPUVertexFormat::Uint32x2 => Self::Uint32x2, GPUVertexFormat::Uint32x3 => Self::Uint32x3, GPUVertexFormat::Uint32x4 => Self::Uint32x4, GPUVertexFormat::Sint32 => Self::Sint32, GPUVertexFormat::Sint32x2 => Self::Sint32x2, GPUVertexFormat::Sint32x3 => Self::Sint32x3, GPUVertexFormat::Sint32x4 => Self::Sint32x4, GPUVertexFormat::Unorm1010102 => Self::Unorm10_10_10_2, GPUVertexFormat::Unorm8x4Bgra => Self::Unorm8x4Bgra, } } } ================================================ FILE: deno_webgpu/rustfmt.toml ================================================ max_width = 80 tab_spaces = 2 edition = "2021" ================================================ FILE: deno_webgpu/sampler.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use deno_core::op2; use deno_core::webidl::WebIdlInterfaceConverter; use deno_core::GarbageCollected; use deno_core::WebIDL; use crate::error::GPUGenericError; use crate::Instance; pub struct GPUSampler { pub instance: Instance, pub id: wgpu_core::id::SamplerId, pub label: String, } impl Drop for GPUSampler { fn drop(&mut self) { self.instance.sampler_drop(self.id); } } impl WebIdlInterfaceConverter for GPUSampler { const NAME: &'static str = "GPUSampler"; } impl GarbageCollected for GPUSampler { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUSampler" } } #[op2] impl GPUSampler { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } } #[derive(WebIDL)] #[webidl(dictionary)] pub(super) struct GPUSamplerDescriptor { #[webidl(default = String::new())] pub label: String, #[webidl(default = GPUAddressMode::ClampToEdge)] pub address_mode_u: GPUAddressMode, #[webidl(default = GPUAddressMode::ClampToEdge)] pub address_mode_v: GPUAddressMode, #[webidl(default = GPUAddressMode::ClampToEdge)] pub address_mode_w: GPUAddressMode, #[webidl(default = GPUFilterMode::Nearest)] pub mag_filter: GPUFilterMode, #[webidl(default = GPUFilterMode::Nearest)] pub min_filter: GPUFilterMode, #[webidl(default = GPUMipmapFilterMode::Nearest)] pub mipmap_filter: GPUMipmapFilterMode, #[webidl(default = 0.0)] pub lod_min_clamp: f32, #[webidl(default = 32.0)] pub lod_max_clamp: f32, pub compare: Option, #[webidl(default = 1)] #[options(clamp = true)] pub max_anisotropy: u16, } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUAddressMode { ClampToEdge, Repeat, MirrorRepeat, } impl From for wgpu_types::AddressMode { fn from(value: GPUAddressMode) -> Self { match value { GPUAddressMode::ClampToEdge => Self::ClampToEdge, GPUAddressMode::Repeat => Self::Repeat, GPUAddressMode::MirrorRepeat => Self::MirrorRepeat, } } } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUFilterMode { Nearest, Linear, } impl From for wgpu_types::FilterMode { fn from(value: GPUFilterMode) -> Self { match value { GPUFilterMode::Nearest => Self::Nearest, GPUFilterMode::Linear => Self::Linear, } } } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUMipmapFilterMode { Nearest, Linear, } impl From for wgpu_types::MipmapFilterMode { fn from(value: GPUMipmapFilterMode) -> Self { match value { GPUMipmapFilterMode::Nearest => Self::Nearest, GPUMipmapFilterMode::Linear => Self::Linear, } } } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUCompareFunction { Never, Less, Equal, LessEqual, Greater, NotEqual, GreaterEqual, Always, } impl From for wgpu_types::CompareFunction { fn from(value: GPUCompareFunction) -> Self { match value { GPUCompareFunction::Never => Self::Never, GPUCompareFunction::Less => Self::Less, GPUCompareFunction::Equal => Self::Equal, GPUCompareFunction::LessEqual => Self::LessEqual, GPUCompareFunction::Greater => Self::Greater, GPUCompareFunction::NotEqual => Self::NotEqual, GPUCompareFunction::GreaterEqual => Self::GreaterEqual, GPUCompareFunction::Always => Self::Always, } } } ================================================ FILE: deno_webgpu/shader.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use deno_core::cppgc::make_cppgc_object; use deno_core::op2; use deno_core::v8; use deno_core::webidl::WebIdlInterfaceConverter; use deno_core::GarbageCollected; use deno_core::WebIDL; use wgpu_core::pipeline; use crate::error::GPUGenericError; use crate::Instance; pub struct GPUShaderModule { pub instance: Instance, pub id: wgpu_core::id::ShaderModuleId, pub label: String, pub compilation_info: v8::Global, } impl Drop for GPUShaderModule { fn drop(&mut self) { self.instance.shader_module_drop(self.id); } } impl WebIdlInterfaceConverter for GPUShaderModule { const NAME: &'static str = "GPUShaderModule"; } impl GarbageCollected for GPUShaderModule { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUShaderModule" } } #[op2] impl GPUShaderModule { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } fn get_compilation_info<'a>( &self, scope: &mut v8::HandleScope<'a>, ) -> v8::Local<'a, v8::Promise> { let resolver = v8::PromiseResolver::new(scope).unwrap(); let info = v8::Local::new(scope, self.compilation_info.clone()); resolver.resolve(scope, info.into()).unwrap(); resolver.get_promise(scope) } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUShaderModuleDescriptor { #[webidl(default = String::new())] pub label: String, pub code: String, } pub struct GPUCompilationMessage { message: String, r#type: GPUCompilationMessageType, line_num: u64, line_pos: u64, offset: u64, length: u64, } impl GarbageCollected for GPUCompilationMessage { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUCompilationMessage" } } #[op2] impl GPUCompilationMessage { #[getter] #[string] fn message(&self) -> String { self.message.clone() } #[getter] #[string] #[rename("type")] fn r#type(&self) -> &'static str { self.r#type.as_str() } #[getter] #[number] fn line_num(&self) -> u64 { self.line_num } #[getter] #[number] fn line_pos(&self) -> u64 { self.line_pos } #[getter] #[number] fn offset(&self) -> u64 { self.offset } #[getter] #[number] fn length(&self) -> u64 { self.length } } impl GPUCompilationMessage { fn new(error: &pipeline::CreateShaderModuleError, source: &str) -> Self { let message = error.to_string(); let loc = match error { pipeline::CreateShaderModuleError::Parsing(e) => e.inner.location(source), pipeline::CreateShaderModuleError::Validation(e) => { e.inner.location(source) } _ => None, }; match loc { Some(loc) => { let len_utf16 = |s: &str| s.chars().map(|c| c.len_utf16() as u64).sum(); let start = loc.offset as usize; // Naga reports a `line_pos` using UTF-8 bytes, so we cannot use it. let line_start = source[0..start].rfind('\n').map(|pos| pos + 1).unwrap_or(0); let line_pos = len_utf16(&source[line_start..start]) + 1; Self { message, r#type: GPUCompilationMessageType::Error, line_num: loc.line_number.into(), line_pos, offset: len_utf16(&source[0..start]), length: len_utf16(&source[start..start + loc.length as usize]), } } _ => Self { message, r#type: GPUCompilationMessageType::Error, line_num: 0, line_pos: 0, offset: 0, length: 0, }, } } } pub struct GPUCompilationInfo { messages: v8::Global, } impl GarbageCollected for GPUCompilationInfo { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUCompilationInfo" } } #[op2] impl GPUCompilationInfo { #[getter] #[global] fn messages(&self) -> v8::Global { self.messages.clone() } } impl GPUCompilationInfo { pub fn new<'args, 'scope>( scope: &mut v8::HandleScope<'scope>, messages: impl ExactSizeIterator< Item = &'args pipeline::CreateShaderModuleError, >, source: &'args str, ) -> Self { let array = v8::Array::new(scope, messages.len().try_into().unwrap()); for (i, message) in messages.enumerate() { let message_object = make_cppgc_object(scope, GPUCompilationMessage::new(message, source)); array.set_index(scope, i.try_into().unwrap(), message_object.into()); } let object: v8::Local = array.into(); object .set_integrity_level(scope, v8::IntegrityLevel::Frozen) .unwrap(); Self { messages: v8::Global::new(scope, object), } } } #[derive(WebIDL, Clone)] #[webidl(enum)] pub(crate) enum GPUCompilationMessageType { Error, Warning, Info, } ================================================ FILE: deno_webgpu/surface.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::cell::RefCell; use deno_core::GarbageCollected; use deno_core::WebIDL; use deno_core::_ops::make_cppgc_object; use deno_core::cppgc::Ptr; use deno_core::op2; use deno_core::v8; use deno_error::JsErrorBox; use wgpu_types::SurfaceStatus; use crate::device::GPUDevice; use crate::error::GPUGenericError; use crate::texture::GPUTexture; use crate::texture::GPUTextureFormat; use crate::webidl::GPUTextureUsageFlags; #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum SurfaceError { #[class("DOMExceptionInvalidStateError")] #[error("Context is not configured")] UnconfiguredContext, #[class(generic)] #[error("Invalid Surface Status")] InvalidStatus, #[class(generic)] #[error(transparent)] Surface(#[from] wgpu_core::present::SurfaceError), } pub struct Configuration { pub device: Ptr, pub usage: GPUTextureUsageFlags, pub format: GPUTextureFormat, pub surface_config: wgpu_types::SurfaceConfiguration>, } pub struct GPUCanvasContext { pub surface_id: wgpu_core::id::SurfaceId, pub width: RefCell, pub height: RefCell, pub config: RefCell>, pub texture: RefCell>>, pub canvas: v8::Global, } impl GarbageCollected for GPUCanvasContext { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUCanvasContext" } } #[op2] impl GPUCanvasContext { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[global] fn canvas(&self) -> v8::Global { self.canvas.clone() } #[undefined] fn configure( &self, #[webidl] configuration: GPUCanvasConfiguration, ) -> Result<(), JsErrorBox> { let format = configuration.format.clone().into(); let conf = wgpu_types::SurfaceConfiguration { usage: configuration.usage.into(), format, width: *self.width.borrow(), height: *self.height.borrow(), present_mode: configuration .present_mode .map(Into::into) .unwrap_or_default(), alpha_mode: configuration.alpha_mode.into(), view_formats: configuration .view_formats .into_iter() .map(Into::into) .collect(), desired_maximum_frame_latency: 2, }; let device = configuration.device; let err = device .instance .surface_configure(self.surface_id, device.id, &conf); device.error_handler.push_error(err); self.config.borrow_mut().replace(Configuration { device, usage: configuration.usage, format: configuration.format, surface_config: conf, }); Ok(()) } #[fast] #[undefined] fn unconfigure(&self) { *self.config.borrow_mut() = None; } #[global] fn get_current_texture( &self, scope: &mut v8::HandleScope, ) -> Result, SurfaceError> { let config = self.config.borrow(); let Some(config) = config.as_ref() else { return Err(SurfaceError::UnconfiguredContext); }; { if let Some(obj) = self.texture.borrow().as_ref() { return Ok(obj.clone()); } } let output = config .device .instance .surface_get_current_texture(self.surface_id, None)?; match output.status { SurfaceStatus::Good | SurfaceStatus::Suboptimal => { let id = output.texture.unwrap(); let texture = GPUTexture { instance: config.device.instance.clone(), error_handler: config.device.error_handler.clone(), id, default_view_id: Default::default(), label: "".to_string(), size: wgpu_types::Extent3d { width: *self.width.borrow(), height: *self.height.borrow(), depth_or_array_layers: 1, }, mip_level_count: 0, sample_count: 0, dimension: crate::texture::GPUTextureDimension::D2, format: config.format.clone(), usage: config.usage, }; let obj = make_cppgc_object(scope, texture); let obj = v8::Global::new(scope, obj); *self.texture.borrow_mut() = Some(obj.clone()); Ok(obj) } _ => Err(SurfaceError::InvalidStatus), } } } impl GPUCanvasContext { pub fn present(&self) -> Result<(), SurfaceError> { let config = self.config.borrow(); let Some(config) = config.as_ref() else { return Err(SurfaceError::UnconfiguredContext); }; config.device.instance.surface_present(self.surface_id)?; // next `get_current_texture` call would get a new texture *self.texture.borrow_mut() = None; Ok(()) } pub fn resize_configure(&self, width: u32, height: u32) { self.width.replace(width); self.height.replace(height); let mut config = self.config.borrow_mut(); let Some(config) = &mut *config else { return; }; config.surface_config.width = width; config.surface_config.height = height; let err = config.device.instance.surface_configure( self.surface_id, config.device.id, &config.surface_config, ); config.device.error_handler.push_error(err); } } #[derive(WebIDL)] #[webidl(dictionary)] struct GPUCanvasConfiguration { device: Ptr, format: GPUTextureFormat, #[webidl(default = GPUTextureUsageFlags(wgpu_types::TextureUsages::RENDER_ATTACHMENT))] usage: GPUTextureUsageFlags, #[webidl(default = GPUCanvasAlphaMode::Opaque)] alpha_mode: GPUCanvasAlphaMode, // Extended from spec present_mode: Option, #[webidl(default = vec![])] view_formats: Vec, } #[derive(WebIDL)] #[webidl(enum)] enum GPUCanvasAlphaMode { Opaque, Premultiplied, } impl From for wgpu_types::CompositeAlphaMode { fn from(value: GPUCanvasAlphaMode) -> Self { match value { GPUCanvasAlphaMode::Opaque => Self::Opaque, GPUCanvasAlphaMode::Premultiplied => Self::PreMultiplied, } } } // Extended from spec #[derive(WebIDL)] #[webidl(enum)] enum GPUPresentMode { #[webidl(rename = "autoVsync")] AutoVsync, #[webidl(rename = "autoNoVsync")] AutoNoVsync, #[webidl(rename = "fifo")] Fifo, #[webidl(rename = "fifoRelaxed")] FifoRelaxed, #[webidl(rename = "immediate")] Immediate, #[webidl(rename = "mailbox")] Mailbox, } impl From for wgpu_types::PresentMode { fn from(value: GPUPresentMode) -> Self { match value { GPUPresentMode::AutoVsync => Self::AutoVsync, GPUPresentMode::AutoNoVsync => Self::AutoNoVsync, GPUPresentMode::Fifo => Self::Fifo, GPUPresentMode::FifoRelaxed => Self::FifoRelaxed, GPUPresentMode::Immediate => Self::Immediate, GPUPresentMode::Mailbox => Self::Mailbox, } } } ================================================ FILE: deno_webgpu/texture.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::sync::OnceLock; use deno_core::op2; use deno_core::webidl::WebIdlInterfaceConverter; use deno_core::GarbageCollected; use deno_core::WebIDL; use deno_error::JsErrorBox; use wgpu_types::AstcBlock; use wgpu_types::AstcChannel; use wgpu_types::Extent3d; use wgpu_types::TextureAspect; use wgpu_types::TextureDimension; use wgpu_types::TextureFormat; use wgpu_types::TextureViewDimension; use crate::error::GPUGenericError; use crate::webidl::GPUTextureUsageFlags; use crate::Instance; #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUTextureDescriptor { #[webidl(default = String::new())] pub label: String, pub size: super::webidl::GPUExtent3D, #[webidl(default = 1)] #[options(enforce_range = true)] pub mip_level_count: u32, #[webidl(default = 1)] #[options(enforce_range = true)] pub sample_count: u32, #[webidl(default = GPUTextureDimension::D2)] pub dimension: GPUTextureDimension, pub format: GPUTextureFormat, pub usage: GPUTextureUsageFlags, #[webidl(default = vec![])] pub view_formats: Vec, } pub struct GPUTexture { pub instance: Instance, pub error_handler: super::error::ErrorHandler, pub id: wgpu_core::id::TextureId, pub default_view_id: OnceLock, pub label: String, pub size: Extent3d, pub mip_level_count: u32, pub sample_count: u32, pub dimension: GPUTextureDimension, pub format: GPUTextureFormat, pub usage: GPUTextureUsageFlags, } impl GPUTexture { pub(crate) fn default_view_id(&self) -> wgpu_core::id::TextureViewId { *self.default_view_id.get_or_init(|| { let (id, err) = self .instance .texture_create_view(self.id, &Default::default(), None); if let Some(err) = err { use wgpu_types::error::WebGpuError; assert_ne!( err.webgpu_error_type(), wgpu_types::error::ErrorType::Validation, concat!( "getting default view for a texture ", "caused a validation error (!?)" ) ); self.error_handler.push_error(Some(err)); } id }) } } impl Drop for GPUTexture { fn drop(&mut self) { if let Some(id) = self.default_view_id.take() { self.instance.texture_view_drop(id); } self.instance.texture_drop(self.id); } } impl WebIdlInterfaceConverter for GPUTexture { const NAME: &'static str = "GPUTexture"; } impl GarbageCollected for GPUTexture { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUTexture" } } #[op2] impl GPUTexture { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } #[getter] fn width(&self) -> u32 { self.size.width } #[getter] fn height(&self) -> u32 { self.size.height } #[getter] fn depth_or_array_layers(&self) -> u32 { self.size.depth_or_array_layers } #[getter] fn mip_level_count(&self) -> u32 { self.mip_level_count } #[getter] fn sample_count(&self) -> u32 { self.sample_count } #[getter] #[string] fn dimension(&self) -> &'static str { self.dimension.as_str() } #[getter] #[string] fn format(&self) -> &'static str { self.format.as_str() } #[getter] fn usage(&self) -> u32 { self.usage.bits() } #[fast] #[undefined] fn destroy(&self) { self.instance.texture_destroy(self.id); } #[cppgc] fn create_view( &self, #[webidl] descriptor: GPUTextureViewDescriptor, ) -> Result { let wgpu_descriptor = wgpu_core::resource::TextureViewDescriptor { label: crate::transform_label(descriptor.label.clone()), format: descriptor.format.map(Into::into), dimension: descriptor.dimension.map(Into::into), usage: Some(descriptor.usage.into()), range: wgpu_types::ImageSubresourceRange { aspect: descriptor.aspect.into(), base_mip_level: descriptor.base_mip_level, mip_level_count: descriptor.mip_level_count, base_array_layer: descriptor.base_array_layer, array_layer_count: descriptor.array_layer_count, }, }; let (id, err) = self .instance .texture_create_view(self.id, &wgpu_descriptor, None); self.error_handler.push_error(err); Ok(GPUTextureView { instance: self.instance.clone(), id, label: descriptor.label, }) } } #[derive(WebIDL)] #[webidl(dictionary)] struct GPUTextureViewDescriptor { #[webidl(default = String::new())] label: String, format: Option, dimension: Option, #[webidl(default = GPUTextureUsageFlags(wgpu_types::TextureUsages::empty()))] usage: GPUTextureUsageFlags, #[webidl(default = GPUTextureAspect::All)] aspect: GPUTextureAspect, #[webidl(default = 0)] #[options(enforce_range = true)] base_mip_level: u32, #[options(enforce_range = true)] mip_level_count: Option, #[webidl(default = 0)] #[options(enforce_range = true)] base_array_layer: u32, #[options(enforce_range = true)] array_layer_count: Option, } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUTextureViewDimension { #[webidl(rename = "1d")] D1, #[webidl(rename = "2d")] D2, #[webidl(rename = "2d-array")] D2Array, #[webidl(rename = "cube")] Cube, #[webidl(rename = "cube-array")] CubeArray, #[webidl(rename = "3d")] D3, } impl From for TextureViewDimension { fn from(value: GPUTextureViewDimension) -> Self { match value { GPUTextureViewDimension::D1 => Self::D1, GPUTextureViewDimension::D2 => Self::D2, GPUTextureViewDimension::D3 => Self::D3, GPUTextureViewDimension::D2Array => Self::D2Array, GPUTextureViewDimension::Cube => Self::Cube, GPUTextureViewDimension::CubeArray => Self::CubeArray, } } } #[derive(WebIDL)] #[webidl(enum)] pub enum GPUTextureAspect { All, StencilOnly, DepthOnly, } impl From for TextureAspect { fn from(value: GPUTextureAspect) -> Self { match value { GPUTextureAspect::All => Self::All, GPUTextureAspect::StencilOnly => Self::StencilOnly, GPUTextureAspect::DepthOnly => Self::DepthOnly, } } } pub struct GPUTextureView { pub instance: Instance, pub id: wgpu_core::id::TextureViewId, pub label: String, } impl Drop for GPUTextureView { fn drop(&mut self) { self.instance.texture_view_drop(self.id); } } impl WebIdlInterfaceConverter for GPUTextureView { const NAME: &'static str = "GPUTextureView"; } impl GarbageCollected for GPUTextureView { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUTextureView" } } // TODO(@crowlKats): weakref in texture for view #[op2] impl GPUTextureView { #[constructor] #[cppgc] fn constructor(_: bool) -> Result { Err(GPUGenericError::InvalidConstructor) } #[getter] #[string] fn label(&self) -> String { self.label.clone() } #[setter] #[string] fn label(&self, #[webidl] _label: String) { // TODO(@crowlKats): no-op, needs wpgu to implement changing the label } } #[derive(WebIDL, Clone)] #[webidl(enum)] pub enum GPUTextureDimension { #[webidl(rename = "1d")] D1, #[webidl(rename = "2d")] D2, #[webidl(rename = "3d")] D3, } impl From for TextureDimension { fn from(value: GPUTextureDimension) -> Self { match value { GPUTextureDimension::D1 => Self::D1, GPUTextureDimension::D2 => Self::D2, GPUTextureDimension::D3 => Self::D3, } } } #[derive(WebIDL, Clone)] #[webidl(enum)] pub(crate) enum GPUTextureFormat { #[webidl(rename = "r8unorm")] R8unorm, #[webidl(rename = "r8snorm")] R8snorm, #[webidl(rename = "r8uint")] R8uint, #[webidl(rename = "r8sint")] R8sint, #[webidl(rename = "r16uint")] R16uint, #[webidl(rename = "r16sint")] R16sint, #[webidl(rename = "r16float")] R16float, #[webidl(rename = "rg8unorm")] Rg8unorm, #[webidl(rename = "rg8snorm")] Rg8snorm, #[webidl(rename = "rg8uint")] Rg8uint, #[webidl(rename = "rg8sint")] Rg8sint, #[webidl(rename = "r32uint")] R32uint, #[webidl(rename = "r32sint")] R32sint, #[webidl(rename = "r32float")] R32float, #[webidl(rename = "rg16uint")] Rg16uint, #[webidl(rename = "rg16sint")] Rg16sint, #[webidl(rename = "rg16float")] Rg16float, #[webidl(rename = "rgba8unorm")] Rgba8unorm, #[webidl(rename = "rgba8unorm-srgb")] Rgba8unormSrgb, #[webidl(rename = "rgba8snorm")] Rgba8snorm, #[webidl(rename = "rgba8uint")] Rgba8uint, #[webidl(rename = "rgba8sint")] Rgba8sint, #[webidl(rename = "bgra8unorm")] Bgra8unorm, #[webidl(rename = "bgra8unorm-srgb")] Bgra8unormSrgb, #[webidl(rename = "rgb9e5ufloat")] Rgb9e5ufloat, #[webidl(rename = "rgb10a2uint")] Rgb10a2uint, #[webidl(rename = "rgb10a2unorm")] Rgb10a2unorm, #[webidl(rename = "rg11b10ufloat")] Rg11b10ufloat, #[webidl(rename = "rg32uint")] Rg32uint, #[webidl(rename = "rg32sint")] Rg32sint, #[webidl(rename = "rg32float")] Rg32float, #[webidl(rename = "rgba16uint")] Rgba16uint, #[webidl(rename = "rgba16sint")] Rgba16sint, #[webidl(rename = "rgba16float")] Rgba16float, #[webidl(rename = "rgba32uint")] Rgba32uint, #[webidl(rename = "rgba32sint")] Rgba32sint, #[webidl(rename = "rgba32float")] Rgba32float, #[webidl(rename = "stencil8")] Stencil8, #[webidl(rename = "depth16unorm")] Depth16unorm, #[webidl(rename = "depth24plus")] Depth24plus, #[webidl(rename = "depth24plus-stencil8")] Depth24plusStencil8, #[webidl(rename = "depth32float")] Depth32float, #[webidl(rename = "depth32float-stencil8")] Depth32floatStencil8, #[webidl(rename = "bc1-rgba-unorm")] Bc1RgbaUnorm, #[webidl(rename = "bc1-rgba-unorm-srgb")] Bc1RgbaUnormSrgb, #[webidl(rename = "bc2-rgba-unorm")] Bc2RgbaUnorm, #[webidl(rename = "bc2-rgba-unorm-srgb")] Bc2RgbaUnormSrgb, #[webidl(rename = "bc3-rgba-unorm")] Bc3RgbaUnorm, #[webidl(rename = "bc3-rgba-unorm-srgb")] Bc3RgbaUnormSrgb, #[webidl(rename = "bc4-r-unorm")] Bc4RUnorm, #[webidl(rename = "bc4-r-snorm")] Bc4RSnorm, #[webidl(rename = "bc5-rg-unorm")] Bc5RgUnorm, #[webidl(rename = "bc5-rg-snorm")] Bc5RgSnorm, #[webidl(rename = "bc6h-rgb-ufloat")] Bc6hRgbUfloat, #[webidl(rename = "bc6h-rgb-float")] Bc6hRgbFloat, #[webidl(rename = "bc7-rgba-unorm")] Bc7RgbaUnorm, #[webidl(rename = "bc7-rgba-unorm-srgb")] Bc7RgbaUnormSrgb, #[webidl(rename = "etc2-rgb8unorm")] Etc2Rgb8unorm, #[webidl(rename = "etc2-rgb8unorm-srgb")] Etc2Rgb8unormSrgb, #[webidl(rename = "etc2-rgb8a1unorm")] Etc2Rgb8a1unorm, #[webidl(rename = "etc2-rgb8a1unorm-srgb")] Etc2Rgb8a1unormSrgb, #[webidl(rename = "etc2-rgba8unorm")] Etc2Rgba8unorm, #[webidl(rename = "etc2-rgba8unorm-srgb")] Etc2Rgba8unormSrgb, #[webidl(rename = "eac-r11unorm")] EacR11unorm, #[webidl(rename = "eac-r11snorm")] EacR11snorm, #[webidl(rename = "eac-rg11unorm")] EacRg11unorm, #[webidl(rename = "eac-rg11snorm")] EacRg11snorm, #[webidl(rename = "astc-4x4-unorm")] Astc4x4Unorm, #[webidl(rename = "astc-4x4-unorm-srgb")] Astc4x4UnormSrgb, #[webidl(rename = "astc-5x4-unorm")] Astc5x4Unorm, #[webidl(rename = "astc-5x4-unorm-srgb")] Astc5x4UnormSrgb, #[webidl(rename = "astc-5x5-unorm")] Astc5x5Unorm, #[webidl(rename = "astc-5x5-unorm-srgb")] Astc5x5UnormSrgb, #[webidl(rename = "astc-6x5-unorm")] Astc6x5Unorm, #[webidl(rename = "astc-6x5-unorm-srgb")] Astc6x5UnormSrgb, #[webidl(rename = "astc-6x6-unorm")] Astc6x6Unorm, #[webidl(rename = "astc-6x6-unorm-srgb")] Astc6x6UnormSrgb, #[webidl(rename = "astc-8x5-unorm")] Astc8x5Unorm, #[webidl(rename = "astc-8x5-unorm-srgb")] Astc8x5UnormSrgb, #[webidl(rename = "astc-8x6-unorm")] Astc8x6Unorm, #[webidl(rename = "astc-8x6-unorm-srgb")] Astc8x6UnormSrgb, #[webidl(rename = "astc-8x8-unorm")] Astc8x8Unorm, #[webidl(rename = "astc-8x8-unorm-srgb")] Astc8x8UnormSrgb, #[webidl(rename = "astc-10x5-unorm")] Astc10x5Unorm, #[webidl(rename = "astc-10x5-unorm-srgb")] Astc10x5UnormSrgb, #[webidl(rename = "astc-10x6-unorm")] Astc10x6Unorm, #[webidl(rename = "astc-10x6-unorm-srgb")] Astc10x6UnormSrgb, #[webidl(rename = "astc-10x8-unorm")] Astc10x8Unorm, #[webidl(rename = "astc-10x8-unorm-srgb")] Astc10x8UnormSrgb, #[webidl(rename = "astc-10x10-unorm")] Astc10x10Unorm, #[webidl(rename = "astc-10x10-unorm-srgb")] Astc10x10UnormSrgb, #[webidl(rename = "astc-12x10-unorm")] Astc12x10Unorm, #[webidl(rename = "astc-12x10-unorm-srgb")] Astc12x10UnormSrgb, #[webidl(rename = "astc-12x12-unorm")] Astc12x12Unorm, #[webidl(rename = "astc-12x12-unorm-srgb")] Astc12x12UnormSrgb, } impl From for TextureFormat { fn from(value: GPUTextureFormat) -> Self { match value { GPUTextureFormat::R8unorm => Self::R8Unorm, GPUTextureFormat::R8snorm => Self::R8Snorm, GPUTextureFormat::R8uint => Self::R8Uint, GPUTextureFormat::R8sint => Self::R8Sint, GPUTextureFormat::R16uint => Self::R16Uint, GPUTextureFormat::R16sint => Self::R16Sint, GPUTextureFormat::R16float => Self::R16Float, GPUTextureFormat::Rg8unorm => Self::Rg8Unorm, GPUTextureFormat::Rg8snorm => Self::Rg8Snorm, GPUTextureFormat::Rg8uint => Self::Rg8Uint, GPUTextureFormat::Rg8sint => Self::Rg8Sint, GPUTextureFormat::R32uint => Self::R32Uint, GPUTextureFormat::R32sint => Self::R32Sint, GPUTextureFormat::R32float => Self::R32Float, GPUTextureFormat::Rg16uint => Self::Rg16Uint, GPUTextureFormat::Rg16sint => Self::Rg16Sint, GPUTextureFormat::Rg16float => Self::Rg16Float, GPUTextureFormat::Rgba8unorm => Self::Rgba8Unorm, GPUTextureFormat::Rgba8unormSrgb => Self::Rgba8UnormSrgb, GPUTextureFormat::Rgba8snorm => Self::Rgba8Snorm, GPUTextureFormat::Rgba8uint => Self::Rgba8Uint, GPUTextureFormat::Rgba8sint => Self::Rgba8Sint, GPUTextureFormat::Bgra8unorm => Self::Bgra8Unorm, GPUTextureFormat::Bgra8unormSrgb => Self::Bgra8UnormSrgb, GPUTextureFormat::Rgb9e5ufloat => Self::Rgb9e5Ufloat, GPUTextureFormat::Rgb10a2uint => Self::Rgb10a2Uint, GPUTextureFormat::Rgb10a2unorm => Self::Rgb10a2Unorm, GPUTextureFormat::Rg11b10ufloat => Self::Rg11b10Ufloat, GPUTextureFormat::Rg32uint => Self::Rg32Uint, GPUTextureFormat::Rg32sint => Self::Rg32Sint, GPUTextureFormat::Rg32float => Self::Rg32Float, GPUTextureFormat::Rgba16uint => Self::Rgba16Uint, GPUTextureFormat::Rgba16sint => Self::Rgba16Sint, GPUTextureFormat::Rgba16float => Self::Rgba16Float, GPUTextureFormat::Rgba32uint => Self::Rgba32Uint, GPUTextureFormat::Rgba32sint => Self::Rgba32Sint, GPUTextureFormat::Rgba32float => Self::Rgba32Float, GPUTextureFormat::Stencil8 => Self::Stencil8, GPUTextureFormat::Depth16unorm => Self::Depth16Unorm, GPUTextureFormat::Depth24plus => Self::Depth24Plus, GPUTextureFormat::Depth24plusStencil8 => Self::Depth24PlusStencil8, GPUTextureFormat::Depth32float => Self::Depth32Float, GPUTextureFormat::Depth32floatStencil8 => Self::Depth32FloatStencil8, GPUTextureFormat::Bc1RgbaUnorm => Self::Bc1RgbaUnorm, GPUTextureFormat::Bc1RgbaUnormSrgb => Self::Bc1RgbaUnormSrgb, GPUTextureFormat::Bc2RgbaUnorm => Self::Bc2RgbaUnorm, GPUTextureFormat::Bc2RgbaUnormSrgb => Self::Bc2RgbaUnormSrgb, GPUTextureFormat::Bc3RgbaUnorm => Self::Bc3RgbaUnorm, GPUTextureFormat::Bc3RgbaUnormSrgb => Self::Bc3RgbaUnormSrgb, GPUTextureFormat::Bc4RUnorm => Self::Bc4RUnorm, GPUTextureFormat::Bc4RSnorm => Self::Bc4RSnorm, GPUTextureFormat::Bc5RgUnorm => Self::Bc5RgUnorm, GPUTextureFormat::Bc5RgSnorm => Self::Bc5RgSnorm, GPUTextureFormat::Bc6hRgbUfloat => Self::Bc6hRgbUfloat, GPUTextureFormat::Bc6hRgbFloat => Self::Bc6hRgbFloat, GPUTextureFormat::Bc7RgbaUnorm => Self::Bc7RgbaUnorm, GPUTextureFormat::Bc7RgbaUnormSrgb => Self::Bc7RgbaUnormSrgb, GPUTextureFormat::Etc2Rgb8unorm => Self::Etc2Rgb8Unorm, GPUTextureFormat::Etc2Rgb8unormSrgb => Self::Etc2Rgb8UnormSrgb, GPUTextureFormat::Etc2Rgb8a1unorm => Self::Etc2Rgb8A1Unorm, GPUTextureFormat::Etc2Rgb8a1unormSrgb => Self::Etc2Rgb8A1UnormSrgb, GPUTextureFormat::Etc2Rgba8unorm => Self::Etc2Rgba8Unorm, GPUTextureFormat::Etc2Rgba8unormSrgb => Self::Etc2Rgba8UnormSrgb, GPUTextureFormat::EacR11unorm => Self::EacR11Unorm, GPUTextureFormat::EacR11snorm => Self::EacR11Snorm, GPUTextureFormat::EacRg11unorm => Self::EacRg11Unorm, GPUTextureFormat::EacRg11snorm => Self::EacRg11Snorm, GPUTextureFormat::Astc4x4Unorm => Self::Astc { block: AstcBlock::B4x4, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc4x4UnormSrgb => Self::Astc { block: AstcBlock::B4x4, channel: AstcChannel::UnormSrgb, }, GPUTextureFormat::Astc5x4Unorm => Self::Astc { block: AstcBlock::B5x4, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc5x4UnormSrgb => Self::Astc { block: AstcBlock::B5x4, channel: AstcChannel::UnormSrgb, }, GPUTextureFormat::Astc5x5Unorm => Self::Astc { block: AstcBlock::B5x5, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc5x5UnormSrgb => Self::Astc { block: AstcBlock::B5x5, channel: AstcChannel::UnormSrgb, }, GPUTextureFormat::Astc6x5Unorm => Self::Astc { block: AstcBlock::B6x5, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc6x5UnormSrgb => Self::Astc { block: AstcBlock::B6x5, channel: AstcChannel::UnormSrgb, }, GPUTextureFormat::Astc6x6Unorm => Self::Astc { block: AstcBlock::B6x6, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc6x6UnormSrgb => Self::Astc { block: AstcBlock::B6x6, channel: AstcChannel::UnormSrgb, }, GPUTextureFormat::Astc8x5Unorm => Self::Astc { block: AstcBlock::B8x5, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc8x5UnormSrgb => Self::Astc { block: AstcBlock::B8x5, channel: AstcChannel::UnormSrgb, }, GPUTextureFormat::Astc8x6Unorm => Self::Astc { block: AstcBlock::B8x6, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc8x6UnormSrgb => Self::Astc { block: AstcBlock::B8x6, channel: AstcChannel::UnormSrgb, }, GPUTextureFormat::Astc8x8Unorm => Self::Astc { block: AstcBlock::B8x8, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc8x8UnormSrgb => Self::Astc { block: AstcBlock::B8x8, channel: AstcChannel::UnormSrgb, }, GPUTextureFormat::Astc10x5Unorm => Self::Astc { block: AstcBlock::B10x5, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc10x5UnormSrgb => Self::Astc { block: AstcBlock::B10x5, channel: AstcChannel::UnormSrgb, }, GPUTextureFormat::Astc10x6Unorm => Self::Astc { block: AstcBlock::B10x6, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc10x6UnormSrgb => Self::Astc { block: AstcBlock::B10x6, channel: AstcChannel::UnormSrgb, }, GPUTextureFormat::Astc10x8Unorm => Self::Astc { block: AstcBlock::B10x8, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc10x8UnormSrgb => Self::Astc { block: AstcBlock::B10x8, channel: AstcChannel::UnormSrgb, }, GPUTextureFormat::Astc10x10Unorm => Self::Astc { block: AstcBlock::B10x10, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc10x10UnormSrgb => Self::Astc { block: AstcBlock::B10x10, channel: AstcChannel::UnormSrgb, }, GPUTextureFormat::Astc12x10Unorm => Self::Astc { block: AstcBlock::B12x10, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc12x10UnormSrgb => Self::Astc { block: AstcBlock::B12x10, channel: AstcChannel::UnormSrgb, }, GPUTextureFormat::Astc12x12Unorm => Self::Astc { block: AstcBlock::B12x12, channel: AstcChannel::Unorm, }, GPUTextureFormat::Astc12x12UnormSrgb => Self::Astc { block: AstcBlock::B12x12, channel: AstcChannel::UnormSrgb, }, } } } pub struct GPUExternalTexture { pub id: wgpu_core::id::ExternalTextureId, } impl WebIdlInterfaceConverter for GPUExternalTexture { const NAME: &'static str = "GPUExternalTexture"; } impl GarbageCollected for GPUExternalTexture { fn get_name(&self) -> &'static std::ffi::CStr { c"GPUExternalTexture" } } #[op2] impl GPUExternalTexture {} ================================================ FILE: deno_webgpu/webidl.rs ================================================ // Copyright 2018-2025 the Deno authors. MIT license. use std::borrow::Cow; use deno_core::cppgc::Ptr; use deno_core::v8; use deno_core::webidl::ContextFn; use deno_core::webidl::IntOptions; use deno_core::webidl::WebIdlConverter; use deno_core::webidl::WebIdlError; use deno_core::webidl::WebIdlErrorKind; use deno_core::WebIDL; use deno_error::JsErrorBox; #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUExtent3DDict { #[options(enforce_range = true)] width: u32, #[webidl(default = 1)] #[options(enforce_range = true)] height: u32, #[webidl(default = 1)] #[options(enforce_range = true)] depth_or_array_layers: u32, } pub(crate) enum GPUExtent3D { Dict(GPUExtent3DDict), Sequence((u32, u32, u32)), } impl<'a> WebIdlConverter<'a> for GPUExtent3D { type Options = (); fn convert<'b>( scope: &mut v8::HandleScope<'a>, value: v8::Local<'a, v8::Value>, prefix: Cow<'static, str>, context: ContextFn<'b>, options: &Self::Options, ) -> Result { if value.is_null_or_undefined() { return Ok(GPUExtent3D::Dict(GPUExtent3DDict::convert( scope, value, prefix, context.borrowed(), options, )?)); } if let Ok(obj) = value.try_cast::() { let iter = v8::Symbol::get_iterator(scope); if let Some(iter) = obj.get(scope, iter.into()) { if !iter.is_undefined() { let conv = >::convert( scope, value, prefix.clone(), context.borrowed(), &IntOptions { clamp: false, enforce_range: true, }, )?; if conv.is_empty() || conv.len() > 3 { return Err(WebIdlError::other( prefix, context, JsErrorBox::type_error(format!( "A sequence of number used as a GPUExtent3D must have between 1 and 3 elements, received {} elements", conv.len() )), )); } let mut iter = conv.into_iter(); return Ok(GPUExtent3D::Sequence(( iter.next().unwrap(), iter.next().unwrap_or(1), iter.next().unwrap_or(1), ))); } } return Ok(GPUExtent3D::Dict(GPUExtent3DDict::convert( scope, value, prefix, context, options, )?)); } Err(WebIdlError::new( prefix, context, WebIdlErrorKind::ConvertToConverterType( "sequence or GPUExtent3DDict", ), )) } } impl From for wgpu_types::Extent3d { fn from(value: GPUExtent3D) -> Self { match value { GPUExtent3D::Dict(dict) => Self { width: dict.width, height: dict.height, depth_or_array_layers: dict.depth_or_array_layers, }, GPUExtent3D::Sequence((width, height, depth)) => Self { width, height, depth_or_array_layers: depth, }, } } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUOrigin3DDict { #[webidl(default = 0)] #[options(enforce_range = true)] x: u32, #[webidl(default = 0)] #[options(enforce_range = true)] y: u32, #[webidl(default = 0)] #[options(enforce_range = true)] z: u32, } pub(crate) enum GPUOrigin3D { Dict(GPUOrigin3DDict), Sequence((u32, u32, u32)), } impl Default for GPUOrigin3D { fn default() -> Self { GPUOrigin3D::Sequence((0, 0, 0)) } } impl<'a> WebIdlConverter<'a> for GPUOrigin3D { type Options = (); fn convert<'b>( scope: &mut v8::HandleScope<'a>, value: v8::Local<'a, v8::Value>, prefix: Cow<'static, str>, context: ContextFn<'b>, options: &Self::Options, ) -> Result { if value.is_null_or_undefined() { return Ok(GPUOrigin3D::Dict(GPUOrigin3DDict::convert( scope, value, prefix, context.borrowed(), options, )?)); } if let Ok(obj) = value.try_cast::() { let iter = v8::Symbol::get_iterator(scope); if let Some(iter) = obj.get(scope, iter.into()) { if !iter.is_undefined() { let conv = >::convert( scope, value, prefix.clone(), context.borrowed(), &IntOptions { clamp: false, enforce_range: true, }, )?; if conv.len() > 3 { return Err(WebIdlError::other( prefix, context, JsErrorBox::type_error(format!( "A sequence of number used as a GPUOrigin3D must have at most 3 elements, received {} elements", conv.len() )), )); } let mut iter = conv.into_iter(); return Ok(GPUOrigin3D::Sequence(( iter.next().unwrap_or(0), iter.next().unwrap_or(0), iter.next().unwrap_or(0), ))); } } return Ok(GPUOrigin3D::Dict(GPUOrigin3DDict::convert( scope, value, prefix, context, options, )?)); } Err(WebIdlError::new( prefix, context, WebIdlErrorKind::ConvertToConverterType( "sequence or GPUOrigin3DDict", ), )) } } impl From for wgpu_types::Origin3d { fn from(value: GPUOrigin3D) -> Self { match value { GPUOrigin3D::Dict(dict) => Self { x: dict.x, y: dict.y, z: dict.z, }, GPUOrigin3D::Sequence((x, y, z)) => Self { x, y, z }, } } } #[derive(WebIDL)] #[webidl(dictionary)] pub(crate) struct GPUColorDict { r: f64, g: f64, b: f64, a: f64, } pub(crate) enum GPUColor { Dict(GPUColorDict), Sequence((f64, f64, f64, f64)), } impl<'a> WebIdlConverter<'a> for GPUColor { type Options = (); fn convert<'b>( scope: &mut v8::HandleScope<'a>, value: v8::Local<'a, v8::Value>, prefix: Cow<'static, str>, context: ContextFn<'b>, options: &Self::Options, ) -> Result { if value.is_null_or_undefined() { return Ok(GPUColor::Dict(GPUColorDict::convert( scope, value, prefix, context.borrowed(), options, )?)); } if let Ok(obj) = value.try_cast::() { let iter = v8::Symbol::get_iterator(scope); if let Some(iter) = obj.get(scope, iter.into()) { if !iter.is_undefined() { let conv = >::convert( scope, value, prefix.clone(), context.borrowed(), options, )?; if conv.len() != 4 { return Err(WebIdlError::other( prefix, context, JsErrorBox::type_error(format!( "A sequence of number used as a GPUColor must have exactly 4 elements, received {} elements", conv.len() )), )); } let mut iter = conv.into_iter(); return Ok(GPUColor::Sequence(( iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), ))); } } return Ok(GPUColor::Dict(GPUColorDict::convert( scope, value, prefix, context, options, )?)); } Err(WebIdlError::new( prefix, context, WebIdlErrorKind::ConvertToConverterType( "sequence or GPUOrigin3DDict", ), )) } } impl From for wgpu_types::Color { fn from(value: GPUColor) -> Self { match value { GPUColor::Dict(dict) => Self { r: dict.r, g: dict.g, b: dict.b, a: dict.a, }, GPUColor::Sequence((r, g, b, a)) => Self { r, g, b, a }, } } } #[derive(WebIDL)] #[webidl(enum)] pub(crate) enum GPUAutoLayoutMode { Auto, } pub(crate) enum GPUPipelineLayoutOrGPUAutoLayoutMode { PipelineLayout(Ptr), AutoLayoutMode(GPUAutoLayoutMode), } impl From for Option { fn from(value: GPUPipelineLayoutOrGPUAutoLayoutMode) -> Self { match value { GPUPipelineLayoutOrGPUAutoLayoutMode::PipelineLayout(layout) => { Some(layout.id) } GPUPipelineLayoutOrGPUAutoLayoutMode::AutoLayoutMode( GPUAutoLayoutMode::Auto, ) => None, } } } impl<'a> WebIdlConverter<'a> for GPUPipelineLayoutOrGPUAutoLayoutMode { type Options = (); fn convert<'b>( scope: &mut v8::HandleScope<'a>, value: v8::Local<'a, v8::Value>, prefix: Cow<'static, str>, context: ContextFn<'b>, options: &Self::Options, ) -> Result { if value.is_object() { Ok(Self::PipelineLayout(WebIdlConverter::convert( scope, value, prefix, context, options, )?)) } else { Ok(Self::AutoLayoutMode(WebIdlConverter::convert( scope, value, prefix, context, options, )?)) } } } impl<'a> WebIdlConverter<'a> for GPUFeatureName { type Options = (); fn convert<'b>( scope: &mut v8::HandleScope<'a>, value: v8::Local<'a, v8::Value>, prefix: Cow<'static, str>, context: ContextFn<'b>, _options: &Self::Options, ) -> Result { let s = value.to_rust_string_lossy(scope); s.parse().map(Self).map_err(|()| { WebIdlError::new( prefix, context, WebIdlErrorKind::InvalidEnumVariant { converter: "GPUFeatureName", variant: s, }, ) }) } } /// A WebGPU optional feature. /// /// Named after the WebIDL enum, which represents features as strings, but we store the /// feature as bitflag, which must always have exactly one bit set (across both the WebGPU /// and wgpu native features). #[derive(Clone, Copy, Hash, Eq, PartialEq)] pub struct GPUFeatureName(wgpu_types::Features); impl From for wgpu_types::Features { fn from(value: GPUFeatureName) -> wgpu_types::Features { value.0 } } #[derive(Clone, Copy)] pub(crate) struct GPUTextureUsageFlags(pub(crate) wgpu_types::TextureUsages); impl<'a> WebIdlConverter<'a> for GPUTextureUsageFlags { type Options = (); fn convert<'b>( scope: &mut v8::HandleScope<'a>, value: v8::Local<'a, v8::Value>, prefix: Cow<'static, str>, context: ContextFn<'b>, _options: &Self::Options, ) -> Result { let flags_value = u32::convert( scope, value, prefix.clone(), context.borrowed(), &IntOptions { clamp: false, enforce_range: true, }, )?; let flags = wgpu_types::TextureUsages::from_bits(flags_value).ok_or_else(|| { WebIdlError::other( prefix, context, JsErrorBox::type_error("usage is not valid"), ) })?; Ok(GPUTextureUsageFlags(flags)) } } impl From for wgpu_types::TextureUsages { fn from(value: GPUTextureUsageFlags) -> Self { value.0 } } impl GPUTextureUsageFlags { pub fn bits(&self) -> u32 { self.0.bits() } } #[derive(Clone, Copy)] pub(crate) struct GPUShaderStageFlags(pub(crate) wgpu_types::ShaderStages); impl<'a> WebIdlConverter<'a> for GPUShaderStageFlags { type Options = (); fn convert<'b>( scope: &mut v8::HandleScope<'a>, value: v8::Local<'a, v8::Value>, prefix: Cow<'static, str>, context: ContextFn<'b>, _options: &Self::Options, ) -> Result { let flags_value = u32::convert( scope, value, prefix.clone(), context.borrowed(), &IntOptions { clamp: false, enforce_range: true, }, )?; let flags = wgpu_types::ShaderStages::from_bits(flags_value).ok_or_else(|| { WebIdlError::other( prefix, context, JsErrorBox::type_error("shader stage is not valid"), ) })?; Ok(GPUShaderStageFlags(flags)) } } impl From for wgpu_types::ShaderStages { fn from(value: GPUShaderStageFlags) -> Self { value.0 } } #[derive(Clone, Copy)] pub(crate) struct GPUColorWriteFlags(pub(crate) wgpu_types::ColorWrites); impl<'a> WebIdlConverter<'a> for GPUColorWriteFlags { type Options = (); fn convert<'b>( scope: &mut v8::HandleScope<'a>, value: v8::Local<'a, v8::Value>, prefix: Cow<'static, str>, context: ContextFn<'b>, _options: &Self::Options, ) -> Result { let flags_value = u32::convert( scope, value, prefix.clone(), context.borrowed(), &IntOptions { clamp: false, enforce_range: true, }, )?; // WebGPU specifies a validation error for invalid color write mask values. // We propagate invalid bits here; wgpu_core will validate it. Ok(GPUColorWriteFlags( wgpu_types::ColorWrites::from_bits_retain(flags_value), )) } } impl From for wgpu_types::ColorWrites { fn from(value: GPUColorWriteFlags) -> Self { value.0 } } ================================================ FILE: docs/api-specs/cooperative_matrix.md ================================================ # Cooperative Matrix Extensions 🧪Experimental🧪 `wgpu` supports an experimental cooperative matrix feature when `Features::EXPERIMENTAL_COOPERATIVE_MATRIX` is enabled. This exposes hardware-accelerated matrix multiply-accumulate (MMA) operations (for example, NVIDIA tensor cores, Metal SIMD-group matrices, and Vulkan `VK_KHR_cooperative_matrix`). **Note**: The features documented here may have bugs and are subject to breaking changes. The API and shader semantics are expected to evolve. Please refer to the GitHub issue tracker for the latest status and discussions. --- ## Overview Cooperative matrices allow a **workgroup** (or equivalent execution group) to collectively: - load small matrix tiles from memory, - perform matrix multiply-accumulate operations on those tiles, and - store the results back to memory. Conceptually, this is specialized hardware that evaluates: > `C = A * B + C` for relatively small tiles, but at very high throughput compared to composing the same operation from scalar/vector instructions. Cooperative matrix operations are most useful in workloads such as: - machine learning and inference, - dense linear algebra and scientific computing, - image processing, filtering, and transforms. The cooperative nature means that all lanes in the cooperating execution group must participate in the operations; individual invocations cannot diverge. Typical example: - `A` is an M×K matrix. - `B` is a K×N matrix. - `C` is an M×N matrix, acting as the accumulator and result. --- ## Querying hardware support (host side) Before using cooperative matrices in shaders, you must query what configurations your hardware and backend support. On the `Adapter`, `wgpu` exposes: - `Adapter::cooperative_matrix_properties() -> Vec` Each `CooperativeMatrixProperties` describes a single supported configuration. Fields are: - `m_size`: height of matrices A and C (type: `naga::CooperativeSize`) - `n_size`: width of matrices B and C (type: `naga::CooperativeSize`) - `k_size`: shared inner dimension of A and B (type: `naga::CooperativeSize`) - `ab_type`: scalar element type for A and B (type: `naga::Scalar`) - `cr_type`: scalar element type for C and the result (type: `naga::Scalar`) - `saturating_accumulation`: `bool` indicating whether overflow clamping on accumulation is supported for this configuration Example usage: ```/dev/null/cooperative-matrix-host.rs#L1-40 let coop_props = adapter.cooperative_matrix_properties(); for prop in &coop_props { println!( "{:?}x{:?}x{:?} - AB: {:?}, CR: {:?}, saturating: {}", prop.m_size, prop.n_size, prop.k_size, prop.ab_type, prop.cr_type, prop.saturating_accumulation, ); } ``` You **must**: 1. Enable `Features::EXPERIMENTAL_COOPERATIVE_MATRIX` on the `Device`. 2. Query `adapter.cooperative_matrix_properties()` and ensure that the configuration(s) you intend to use in WGSL are actually available on the running adapter/backend. 3. Treat the sizes and types as a contract between your shaders and the underlying hardware implementation. Using unsupported configurations is an error. --- ## Feature and backend requirements ### `wgpu` feature - Using cooperative matrices requires enabling: - `Features::EXPERIMENTAL_COOPERATIVE_MATRIX` This feature may be restricted to certain backends and hardware. ### Hardware / backend notes These are general guidelines, not a complete compatibility matrix: - **Metal**: - Requires Apple7+ (A14) or Mac2+ (M1) GPU with MSL 2.3+. - Strong support for 8×8 `f32`, 8×8 `f16`, and mixed-precision modes (e.g. `f16` A/B and `f32` accumulator C). - Implementation is based on SIMD-group matrix operations. - **Vulkan**: - Requires the `VK_KHR_cooperative_matrix` extension. - Many NVIDIA and AMD GPUs support `f16` at 16×16 tile sizes and similar. - 8×8 `f32` support is hardware-dependent. - Exact configurations are enumerated by `Adapter::cooperative_matrix_properties()`. - **Other backends**: - May not support cooperative matrices at all. In that case the feature will not be exposed, and `adapter.cooperative_matrix_properties()` will return an empty list. > Always treat the properties returned at runtime as the source of truth. --- ## `wgpu` API surface This section summarizes the host-side API elements related to cooperative matrices. (For exact signatures and details, refer to the Rust documentation.) ### Adapter - `Adapter::cooperative_matrix_properties() -> Vec` Returns all cooperative matrix configurations supported by the adapter/backend. ### Structures - `CooperativeMatrixProperties` - `m_size: naga::CooperativeSize` - `n_size: naga::CooperativeSize` - `k_size: naga::CooperativeSize` - `ab_type: naga::Scalar` - `cr_type: naga::Scalar` - `saturating_accumulation: bool` The `naga` types (`CooperativeSize`, `Scalar`) are part of the shader translation layer and determine the legal WGSL/cooperative matrix combinations. There are currently no dedicated `wgpu` buffer or texture types for cooperative matrices; they are expressed in WGSL as special value types accessed via pointers into ordinary `var` / `var` / `var` / etc. --- ## WGSL extension specification Cooperative matrices are enabled and accessed via WGSL extensions. The exact extension spelling may change; the details below describe the intended semantics. ### Enabling cooperative matrices in WGSL Any WGSL program using cooperative matrices must declare an extension at the top of the shader, for example: ```/dev/null/example.wgsl#L1-3 enable wgpu_cooperative_matrix; ``` The shader is invalid if any cooperative matrix types or builtins are used without enabling this extension. ### Cooperative matrix types A cooperative matrix is a value type parameterized by: - tile size (M×N), - scalar element type `T`, and - role `R` indicating how the matrix participates in the multiply-accumulate: - `A`: left operand - `B`: right operand - `C`: accumulator / result Conceptually: ```/dev/null/example.wgsl#L1-8 // A: MxK, B: KxN, C: MxN type coop_matMxN; type coop_matMxN; type coop_matMxN; ``` Concrete examples (sizes and types must match a supported configuration from `Adapter::cooperative_matrix_properties`): ```/dev/null/example.wgsl#L10-20 // 8x8 single-precision tiles alias CoopMatA = coop_mat8x8; alias CoopMatB = coop_mat8x8; alias CoopMatC = coop_mat8x8; // 16x16 half-precision inputs, 16x16 f32 accumulator (mixed precision) alias CoopMat16x16A = coop_mat16x16; alias CoopMat16x16B = coop_mat16x16; alias CoopMat16x16C = coop_mat16x16; ``` The actual set of legal `(M, N, T, R)` combinations is defined by the cooperative matrix properties returned at runtime; shaders must not use arbitrary combinations. ### Roles and semantics - `A` role: - Treated as the left operand in the multiplication. Has shape M×K. - Participates as `A` in `A * B + C`. - `B` role: - Treated as the right operand in the multiplication. Has shape K×N. - Participates as `B` in `A * B + C`. - `C` role: - Treated as accumulator and result. Has shape M×N. - Participates as `C` in `A * B + C`. These roles are part of the type; they are not interchangeable. ### Cooperative matrix operations WGSL provides built-in functions for operating on cooperative matrices. The exact spelling may change; the semantics are: #### `coopLoad` Collectively load a tile from memory into a cooperative matrix. ```/dev/null/example.wgsl#L1-6 fn coopLoad( ptr: ptr, // base pointer to scalar or vector elements stride: u32 // stride (in elements) between rows/columns ) -> coop_matMxN; ``` - Loads an M×N tile (or M×K / K×N, depending on role and operation) from memory pointed to by `ptr`. - `stride` describes the layout in memory; it is usually the number of elements between adjacent rows. - All invocations in the cooperative group must call `coopLoad` in a converged fashion. - Memory address range must be valid and properly aligned for the scalar type. > Implementation note: Each lane contributes to filling the tile based on an implementation-defined mapping from > invocation/lane ID to sub-fragment of the matrix. #### `coopStore` Collectively store a cooperative matrix tile back to memory. ```/dev/null/example.wgsl#L8-13 fn coopStore( value: coop_matMxN, ptr: ptr, stride: u32 ); ``` - Stores `value` into the memory region addressed by `ptr` with given `stride`. - All invocations in the cooperative group must participate. - The store must not alias overlapping tiles in undefined ways. #### `coopMultiplyAdd` Perform a matrix multiply-accumulate operation on cooperative matrices: ```/dev/null/example.wgsl#L15-23 fn coopMultiplyAdd( a: coop_matMAxKA, // A: MAxKA tile b: coop_matKBxNB, // B: KBxNB tile (KB == KA) c: coop_matMAxNB // C: MAxNB accumulator/result ) -> coop_matMAxNB; ``` Semantics: - Computes `C' = A * B + C`. - Returns the resulting accumulator tile `C'`. - Implies: - `KA == KB` (inner dimension must match). - Types `(Tab, Tcr)` must be one of the supported AB/CR combinations given by `CooperativeMatrixProperties`. - Sizes `(MA, NB, KA)` must match a supported `(m_size, n_size, k_size)` triple. For example, with a supported configuration: ```/dev/null/example.wgsl#L25-39 enable wgpu_cooperative_matrix; alias MatA = coop_mat8x8; alias MatB = coop_mat8x8; alias MatC = coop_mat8x8; fn matmul_tile( ptr_a: ptr, ptr_b: ptr, ptr_c: ptr, stride: u32, ) { let a: MatA = coopLoad<_, A>(ptr_a, stride); let b: MatB = coopLoad<_, B>(ptr_b, stride); let c: MatC = coopLoad<_, C>(ptr_c, stride); let result: MatC = coopMultiplyAdd(a, b, c); coopStore(result, ptr_c, stride); } ``` If `saturating_accumulation` is true for the chosen configuration, then overflow during accumulation is clamped (e.g. saturating arithmetic). If false, overflow behavior for the accumulator follows the underlying scalar type semantics (e.g. IEEE-754 for floats). ### Workgroup cooperation and execution model Cooperative matrix operations are **collective**: - All invocations in the relevant execution group must execute each cooperative operation in uniform control flow: - Using `coopLoad`, `coopStore`, or `coopMultiplyAdd` in divergent control flow (e.g. some lanes taking a branch, others not) is undefined behavior. - The exact execution group may be a workgroup, a SIMD-group / subgroup, or another backend-specific granularity; shaders must treat it abstractly. - The workgroup (or cooperating group) size is constrained by both: - the cooperative matrix configuration, and - backend-specific implementation details. For portable code: - Choose a workgroup size that is known to be supported efficiently on your target backends, for example: - `@workgroup_size(8, 8, 1)` to operate on an 8×8 tile, or - a multiple of the tile size where each subgroup handles a tile. - Avoid control-flow divergence around cooperative operations. Example: ```/dev/null/example.wgsl#L1-42 enable wgpu_cooperative_matrix; struct Matrices { // Row-major tiles for A, B, C. data: array, }; @group(0) @binding(0) var buf_a: Matrices; @group(0) @binding(1) var buf_b: Matrices; @group(0) @binding(2) var buf_c: Matrices; alias MatA = coop_mat8x8; alias MatB = coop_mat8x8; alias MatC = coop_mat8x8; @compute @workgroup_size(8, 8, 1) fn main( @builtin(workgroup_id) wg_id: vec3, @builtin(local_invocation_id) lid: vec3, ) { // Compute tile offset; this is one of many possible mappings. let tile_index = wg_id.x; // 1D tiling in this simple example let tile_offset = tile_index * 64u; // 8x8 tile has 64 elements // Base pointers for tiles of A, B, C. let base_a = &buf_a.data[tile_offset]; let base_b = &buf_b.data[tile_offset]; let base_c = &buf_c.data[tile_offset]; let a: MatA = coopLoad(base_a, 8u); let b: MatB = coopLoad(base_b, 8u); let c: MatC = coopLoad(base_c, 8u); let result: MatC = coopMultiplyAdd(a, b, c); coopStore(result, base_c, 8u); } ``` --- ## Validation rules and undefined behavior Implementations must validate the following where possible: - The `wgpu_cooperative_matrix` WGSL extension is enabled if any cooperative matrix types or builtins are used. - Tile sizes `(M, N, K)` and scalar types `(ab_type, cr_type)` match at least one `CooperativeMatrixProperties` entry for the current adapter/backend. - Workgroup size, shader stage, and other pipeline configuration constraints required by the backend are satisfied. The following are examples of **undefined behavior** (non-exhaustive): - Using cooperative matrix operations without enabling the WGSL extension. - Using a cooperative matrix type `(M, N, T, R)` not supported by `Adapter::cooperative_matrix_properties()`. - Mismatching sizes or roles in `coopMultiplyAdd` (e.g. incompatible M/N/K, or incorrect roles). - Executing `coopLoad`, `coopStore`, or `coopMultiplyAdd` in divergent control flow within the cooperating execution group. - Providing invalid, misaligned, or out-of-bounds pointers to `coopLoad` / `coopStore`. - Overlapping `coopStore` targets in a way that creates data races or aliasing that the memory model does not allow. --- ## Example: 64×64 matrix multiply using 8×8 tiles The example in `examples/features/src/cooperative_matrix` demonstrates using cooperative matrices to compute: - `C = A * B + C` where: - `A` is 64×64, - `B` is 64×64, - `C` is 64×64. A high-level tiling strategy: 1. Partition A, B, and C into 8×8 tiles. 2. Launch one workgroup per output tile of C (i.e. 8×8 tiles for a 64×64 matrix = 8×8 = 64 tiles). 3. Within each workgroup: - Loop over K-dimension tiles. - For each `k` tile: - Load an 8×8 tile of A (`MatA`). - Load an 8×8 tile of B (`MatB`). - Maintain an 8×8 accumulator tile (`MatC`) and repeatedly apply `coopMultiplyAdd`. 4. After the K loop, store the final accumulator tile back to C. Key points from the example: - Workgroup size is chosen so that all cooperative operations are well-defined and efficient for 8×8 tiles. - Host-side code: - Enables `Features::EXPERIMENTAL_COOPERATIVE_MATRIX`. - Queries `cooperative_matrix_properties` and verifies that 8×8 `f32` or chosen configuration is supported. - Dispatches the compute pipeline with appropriate grid dimensions. --- ## Notes and best practices - Always query `adapter.cooperative_matrix_properties()` and check that the configuration your shaders use exists. Do not hard-code assumptions about available tile sizes or element types. - Treat the cooperative execution group as an abstract concept; avoid making assumptions about how tiles are mapped to lanes beyond what is guaranteed by the spec. - Avoid divergent control flow around cooperative operations. - Consider providing a fallback non-cooperative implementation for devices that do not support the feature. - This is an experimental extension; API and semantics may change across versions of `wgpu` and `naga`. ================================================ FILE: docs/api-specs/mesh_shading.md ================================================ # Mesh Shader Extensions 🧪Experimental🧪 `wgpu` supports an experimental version of mesh shading when `Features::EXPERIMENTAL_MESH_SHADER` is enabled. The status of the implementation is documented in [the mesh-shading issue](https://github.com/gfx-rs/wgpu/issues/7197). **Note**: The features documented here may have major bugs in them and are expected to be subject to breaking changes. Suggestions for the API exposed by this should be posted on the issue above. ## Mesh shaders overview ### What are mesh shaders? Mesh shaders are a new kind of rasterization pipeline intended to address some of the shortfalls with the vertex shader pipeline. The core idea of mesh shaders is that the GPU decides how to render the many small parts of a scene instead of the CPU issuing a draw call for every small part or issuing an inefficient monolithic draw call for a large part of the scene. Mesh shaders are specifically designed to be used with **meshlet rendering**, a technique where every object is split into many subobjects called meshlets that are each rendered with their own parameters. With the standard vertex pipeline, each draw call specifies an exact number of primitives to render and the same parameters for all vertex shaders on an entire object (or even multiple objects). This doesn't leave room for different LODs for different parts of an object, for example a closer part having more detail, nor does it allow culling smaller sections (or primitives) of objects. With mesh shaders, each task workgroup might get assigned to a single object. It can then analyze the different meshlets(sections) of that object, determine which are visible and should actually be rendered, and for those meshlets determine what LOD to use based on the distance from the camera. It can then dispatch a mesh workgroup for each meshlet, with each mesh workgroup then reading the data for that specific LOD of its meshlet, determining which and how many vertices and primitives to output, determining which remaining primitives need to be culled, and passing the resulting primitives to the rasterizer. Mesh shaders are most effective in scenes with many polygons. They can allow skipping processing of entire groups of primitives that are facing away from the camera or otherwise occluded, which reduces the number of primitives that need to be processed by more than half in most cases, and they can reduce the number of primitives that need to be processed for more distant objects. Scenes that are not bottlenecked by geometry (perhaps instead by fragment processing or post processing) will not see much benefit from using them. Mesh shaders were first shown off in [NVIDIA's asteroids demo](https://www.youtube.com/watch?v=CRfZYJ_sk5E). Now, they form the basis for [Unreal Engine's Nanite](https://www.unrealengine.com/en-US/blog/unreal-engine-5-is-now-available-in-preview#Nanite). ### Mesh shader pipeline With the current pipeline set to a mesh pipeline, a draw command like `render_pass.draw_mesh_tasks(x, y, z)` takes the following steps: * If the pipeline has a task shader stage: * Dispatch a grid of task shader workgroups, where `x`, `y`, and `z` give the number of workgroups along each axis of the grid. Each task shader workgroup produces a mesh shader workgroup grid size `(mx, my, mz)` and a task payload value `mp`. * For each task shader workgroup, dispatch a grid of mesh shader workgroups, where `mx`, `my`, and `mz` give the number of workgroups along each axis of the grid. Pass `mp` to each of these workgroup's mesh shader invocations. * Alternatively, if the pipeline does not have a task shader stage: * Dispatch a single grid of mesh shader workgroups, where `x`, `y`, and `z` give the number of workgroups along each axis of the grid. These mesh shaders receive no task payload value. * Each mesh shader workgroup produces a list of output vertices, and a list of primitives built from those vertices. The workgroup can supply per-primitive values as well, if needed. Each primitive selects its vertices by index, like an indexed draw call, from among the vertices generated by this workgroup. Unlike a grid of ordinary compute shader workgroups collaborating to build vertex and index data in common storage buffers, the vertices and primitives produced by a mesh shader workgroup are entirely private to that workgroup, and are not accessible by other workgroups. * Primitives produced by a mesh shader workgroup can have a culling flag. If a primitive's culling flag is false, it is skipped during rasterization. * The primitives produced by all mesh shader workgroups are then rasterized in the usual way, with each fragment shader invocation handling one pixel. Attributes from the vertices produced by the mesh shader workgroup are provided to the fragment shader with interpolation applied as appropriate. If the mesh shader workgroup supplied per-primitive values, these are available to each primitive's fragment shader invocations. Per-primitive values are never interpolated; fragment shaders simply receive the values the mesh shader workgroup associated with their primitive. ## `wgpu` API ### New `wgpu` functions `Device::create_mesh_pipeline` - Creates a mesh shader pipeline. This is very similar to creating a standard render pipeline, except that it takes a mesh shader state and optional task shader state instead of a vertex state. If the task state is omitted, during rendering the number of workgroups is passed directly from the draw call to the mesh shader state, with an empty payload. `RenderPass::draw_mesh_tasks` - Dispatches the mesh shader pipeline. This ignores render pipeline specific information, such as vertex buffer bindings and index buffer bindings. The dispatch size must adhere to the limits described below. `RenderPass::draw_mesh_tasks_indirect`, `RenderPass::multi_draw_mesh_tasks_indirect` and `RenderPass::multi_draw_mesh_tasks_indirect_count` - Dispatches the mesh shader pipeline with dispatch size taken from a buffer. This ignores render pipeline specific information, such as vertex buffer bindings and index buffer bindings. The dispatch size must adhere to the limits described below. Analogous to `draw_indirect`, `multi_draw_indirect` and `multi_draw_indirect_count`. Requires the corresponding indirect feature to be enabled. An example of using mesh shaders to render a single triangle can be seen [here](../../examples/features/src/mesh_shader). ### Features * Using mesh shaders requires enabling `Features::EXPERIMENTAL_MESH_SHADER`. * Using mesh shaders with multiview requires enabling `Features::EXPERIMENTAL_MESH_SHADER_MULTIVIEW`. * Using mesh shaders with point primitives requires enabling `Features::EXPERIMENTAL_MESH_SHADER_POINTS`. * Queries are unsupported * Primitive index support will be added once support lands in for them in general. ### Limits > **NOTE**: More limits will be added when support is added to `naga`. * `Limits::max_task_workgroup_total_count` - the maximum total number of workgroups from a `draw_mesh_tasks` command or similar. The dimensions passed must be less than or equal to this limit when multiplied together. * `Limits::max_task_workgroups_per_dimension` - the maximum for each of the 3 workgroup dimensions in a `draw_mesh_tasks` command. Each dimension passed must be less than or equal to this limit. * `max_mesh_multiview_count` - The maximum number of views used when multiview rendering with a mesh shader pipeline. * `max_mesh_output_layers` - the maximum number of output layers for a mesh shader pipeline. ## Naga implementation ### Supported frontends * 🛠️ WGSL * ❌ SPIR-V * 🚫 GLSL ### Supported backends * 🛠️ SPIR-V * 🛠️ HLSL * ❌ MSL * 🚫 GLSL * 🚫 WGSL ✔️ = Complete 🛠️ = In progress ❌ = Planned 🚫 = Unplanned/impossible ## `WGSL` extension specification The majority of changes relating to mesh shaders will be in WGSL and `naga`. Using any of these features in a `wgsl` program will require adding the `enable wgpu_mesh_shader;` directive to the top of a program. Two new shader stages will be added to `WGSL`. Fragment shaders are also modified slightly. Both task shaders and mesh shaders are allowed to use any compute-available functionality, including subgroup operations. ### Task shader A function with the `@task` attribute is a **task shader entry point**. A mesh shader pipeline may optionally specify a task shader entry point, and if it does, mesh draw commands using that pipeline dispatch a **task shader grid** of workgroups running the task shader entry point. Like compute shader dispatches, the three-component size passed to `draw_mesh_tasks`, or drawn from the indirect buffer for its indirect variants, specifies the size of the task shader grid as the number of workgroups along each of the grid's three axes. A task shader entry point must have a `@workgroup_size` attribute, meeting the same requirements as one appearing on a compute shader entry point. A task shader entry point must also have a `@payload(G)` property, where `G` is the name of a global variable in the `task_payload` address space. Each task shader workgroup has its own instance of this variable, visible to all invocations in the workgroup. Whatever value the workgroup collectively stores in that global variable becomes the **task payload**, and is provided to all invocations in the mesh shader grid dispatched for the workgroup. A task payload variable must be at least 4 bytes in size. A task shader entry point must return a `vec3` value decorated with `@builtin(mesh_task_size)`. The return value of each workgroup's first invocation (that is, the one whose `local_invocation_index` is `0`) is taken as the size of a **mesh shader grid** to dispatch, measured in workgroups. (If the task shader entry point returns `vec3(0, 0, 0)`, then no mesh shaders are dispatched.) Mesh shader grids are described in the next section. Each task shader workgroup dispatches an independent mesh shader grid: in mesh shader invocations, `@builtin` values like `workgroup_id` and `global_invocation_id` describe the position of the workgroup and invocation within that grid; and `@builtin(num_workgroups)` matches the task shader workgroup's return value. Mesh shaders dispatched for other task shader workgroups are not included in the count. If it is necessary for a mesh shader to know which task shader workgroup dispatched it, the task shader can include its own workgroup id in the task payload. Task shaders can use compute and subgroup builtin inputs, in addition to `view_index` and `draw_id`. ### Mesh shader A function with the `@mesh` attribute is a **mesh shader entry point**. Mesh shaders must not return anything. Like compute shaders, mesh shaders are invoked in a grid of workgroups, called a **mesh shader grid**. If the mesh shader pipeline has a task shader, then each task shader workgroup determines the size of a mesh shader grid to be dispatched, as described above. Otherwise, the three-component size passed to `draw_mesh_tasks`, or drawn from the indirect buffer for its indirect variants, specifies the size of the mesh shader grid directly, as the number of workgroups along each of the grid's three axes. If the mesh shader pipeline has a task shader entry point, then the pipeline's mesh shader entry point must also have a `@payload(G)` attribute, and the sizes of the variables must match. Mesh shader invocations can read from, but not write to, this variable, which is initialized to whatever value was written to it by the task shader workgroup that dispatched this mesh shader grid. If the mesh shader pipeline does not have a task shader entry point, then the mesh shader entry point must not have any `@payload` attribute. A mesh shader entry point must have the following attributes: - `@workgroup_size`: this has the same meaning as when it appears on a compute shader entry point. - `@mesh(VAR)`: Here, `VAR` represents a workgroup variable storing the output information. All mesh shader outputs are per-workgroup, and taken from the workgroup variable specified above. The type must have exactly 4 fields: - A field decorated with `@builtin(vertex_count)`, with type `u32`: this field represents the number of vertices that will be drawn - A field decorated with `@builtin(primitive_count)`, with type `u32`: this field represents the number of primitives that will be drawn - A field decorated with `@builtin(vertices)`, typed as an array of `V`, where `V` is the vertex output type as specified below - A field decorated with `@builtin(primitives)`, typed as an array of `P`, where `P` is the primitive output type as specified below For a vertex count `NV`, the first `NV` elements of the vertex array above are outputted. Therefore, `NV` must be less than or equal to the size of the vertex array. The same is true for primitives with `NP`. The vertex output type `V` must meet the same requirements as a struct type returned by a `@vertex` entry point: all members must have either `@builtin` or `@location` attributes, there must be a `@builtin(position)`, and so on. The primitive output type `P` must be a struct type, every member of which either has a `@location` or `@builtin` attribute. All members decorated with `@location` must also be decorated with `@per_primitive`, as must the corresponding fragment input. The `@per_primitive` decoration may only be applied to members decorated with `@location`. The following `@builtin` attributes are allowed: - `triangle_indices`, `line_indices`, or `point_index`: The annotated member must be of type `vec3`, `vec2`, or `u32`. The member's components are indices (or, its value is an index) into the list of vertices generated by this workgroup, identifying the vertices of the primitive to be drawn. These indices must be less than the value of `numVertices` passed to `setMeshOutputs`. The type `P` must contain exactly one member with one of these attributes, determining what sort of primitives the mesh shader generates. - `cull_primitive`: The annotated member must be of type `bool`. If it is true, then the primitive is skipped during rendering. The `@location` attributes of `P` and `V` must not overlap, since they are merged to produce the user-defined inputs to the fragment shader. Mesh shaders may write to the `primitive_index` builtin. This is treated just like a field decorated with `@location`, so if the mesh shader outputs `primitive_index` the fragment shader must input it, and if the fragment shader inputs it, the mesh shader must write it (unlike vertex shader pipelines). Mesh shaders can use compute and mesh shader builtin inputs, in addition to `view_index`, and if no task shader is present, `draw_id`. ### Fragment shader Fragment shaders can access vertex output data as if it is from a vertex shader. They can also access primitive output data, provided the input is decorated with `@per_primitive`. The `@per_primitive` decoration may only be applied to inputs or struct members decorated with `@location`. The primitive state is part of the fragment input and must match the output of the mesh shader in the pipeline. Using `@per_primitive` also requires enabling the mesh shader extension. Additionally, the locations of vertex and primitive input cannot overlap. ### Full example The following is a full example of WGSL shaders that could be used to create a mesh shader pipeline, showing off many of the features. ```wgsl enable wgpu_mesh_shader; const positions = array( vec4(0., 1., 0., 1.), vec4(-1., -1., 0., 1.), vec4(1., -1., 0., 1.) ); const colors = array( vec4(0., 1., 0., 1.), vec4(0., 0., 1., 1.), vec4(1., 0., 0., 1.) ); struct TaskPayload { colorMask: vec4, visible: bool, } struct VertexOutput { @builtin(position) position: vec4, @location(0) color: vec4, } struct PrimitiveOutput { @builtin(triangle_indices) indices: vec3, @builtin(cull_primitive) cull: bool, @per_primitive @location(1) colorMask: vec4, } struct PrimitiveInput { @per_primitive @location(1) colorMask: vec4, } var taskPayload: TaskPayload; var workgroupData: f32; @task @payload(taskPayload) @workgroup_size(1) fn ts_main() -> @builtin(mesh_task_size) vec3 { workgroupData = 1.0; taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0); taskPayload.visible = true; return vec3(1, 1, 1); } struct MeshOutput { @builtin(vertices) vertices: array, @builtin(primitives) primitives: array, @builtin(vertex_count) vertex_count: u32, @builtin(primitive_count) primitive_count: u32, } var mesh_output: MeshOutput; @mesh(mesh_output) @payload(taskPayload) @workgroup_size(1) fn ms_main() { mesh_output.vertex_count = 3; mesh_output.primitive_count = 1; workgroupData = 2.0; mesh_output.vertices[0].position = positions[0]; mesh_output.vertices[0].color = colors[0] * taskPayload.colorMask; mesh_output.vertices[1].position = positions[1]; mesh_output.vertices[1].color = colors[1] * taskPayload.colorMask; mesh_output.vertices[2].position = positions[2]; mesh_output.vertices[2].color = colors[2] * taskPayload.colorMask; mesh_output.primitives[0].indices = vec3(0, 1, 2); mesh_output.primitives[0].cull = !taskPayload.visible; mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } @fragment fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { return vertex.color * primitive.colorMask; } ``` ================================================ FILE: docs/api-specs/ray_tracing.md ================================================ # Ray Tracing Extensions 🧪Experimental🧪 `wgpu` supports an experimental version of ray tracing which is subject to change. The extensions allow for acceleration structures to be created and built (with `Features::EXPERIMENTAL_RAY_QUERY` enabled) and interacted with in shaders. Currently `naga` only supports ray queries (accessible with `Features::EXPERIMENTAL_RAY_QUERY` enabled in wgpu). **Note**: The features documented here may have major bugs in them and are expected to be subject to breaking changes, suggestions for the API exposed by this should be posted on [the ray-tracing issue](https://github.com/gfx-rs/wgpu/issues/1040). Large changes may mean that this documentation may be out of date. ***This is not*** an introduction to raytracing, and assumes basic prior knowledge, to look at the fundamentals look at an [introduction](https://developer.nvidia.com/blog/introduction-nvidia-rtx-directx-ray-tracing/). ## `wgpu`'s raytracing API: The documentation and specific details of the functions and structures provided can be found with their definitions. Acceleration structures do not have a separate feature, instead they are enabled by `Features::EXPERIMENTAL_RAY_QUERY`, unlike vulkan. When ray tracing pipelines are added, that feature will also enable acceleration structures. A [`Blas`] can be created with [`Device::create_blas`]. A [`Tlas`] can be created with [`Device::create_tlas`]. The [`Tlas`] reference can be placed in a bind group to be used in a shader. A reference to a [`Blas`] can be used to create [`TlasInstance`] alongside a transformation matrix, custom data (this can be any data that should be given to the shader on a hit) which only the first 24 bits may be set, and a mask to filter hits in the shader. A [`Blas`] must be built in either the same build as any [`Tlas`] it is used to build or an earlier build call. Before a [`Tlas`] is used in a shader it must - have been built - have all [`Blas`]es that it was last built with to have last been built in either the same build as this [`Tlas`] or an earlier build call. ### [`Blas`] compaction Once a [`Blas`] has been built, it can be compacted. Acceleration structures are allocated conservatively, without knowing the exact data that is inside them. Once a [`Blas`] has been built, the driver can make data specific optimisations to make the [`BLAS`] smaller. To begin compaction call [`Blas::prepare_compaction_async`] on it. This method waits until all builds operating on the [`Blas`] are finished, prepares the [`Blas`] to be compacted, and runs the given callback. To check whether the [`Blas`] is ready, you can also call [`Blas::ready_for_compaction`] instead of waiting for the callback (useful if you are asynchronously compacting a large number of [`Blas`]es). Submitting a rebuild of a [`Blas`] terminates any [`Blas::prepare_compaction_async`], preventing the callback from being called, and making the [`Blas`] no longer ready to compact. Once a [`Blas`] is ready for compaction, it can be compacted using [`Queue::compact_blas`] this returns the new compacted [`Blas`], which is independent of the [`Blas`] passed in. The other [`Blas`] can be used for other things, including being rebuilt without affecting the new [`Blas`]. The returned [`Blas`] behaves largely like the [`Blas`] it was created from, except that it can be neither rebuilt, nor compacted again. An example of compaction being run when [`Blas`]es are ready, this would be in a situation when memory was not a major problem, otherwise (e.g. if you get an out of memory error) you should compact immediately (and switching all non-compacted [`Blas`]es to compacted ones). ```rust use std::iter; use wgpu::Blas; struct BlasToBeCompacted { blas: Blas, /// The index into the TlasInstance this BLAS is used in. tlas_index: usize, } fn render(/*whatever args you need to render*/) { /* additional code to prepare the renderer */ //An iterator of whatever BLASes you have called `prepare_compaction_async` on. let blas_s_pending_compaction: impl Iterator = iter::empty(); for blas_to_be_compacted in blas_s_pending_compaction { if blas_to_be_compacted.blas.ready_for_compaction() { let compacted_blas = queue.compact_blas(&blas_to_be_compacted.blas); tlas_instance[blas_to_be_compacted.tlas_index].set_blas(&compacted_blas); } } let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); /* do other preparations on the TlasInstance.*/ encoder.build_acceleration_structures(iter::empty(), iter::once(&tlas_package)); /* more render code */ queue.submit([encoder.finish()]); } ``` [`Device::create_blas`]: https://wgpu.rs/doc/wgpu/struct.Device.html#method.create_blas [`Device::create_tlas`]: https://wgpu.rs/doc/wgpu/struct.Device.html#method.create_tlas [`Tlas`]: https://wgpu.rs/doc/wgpu/struct.Tlas.html [`Blas`]: https://wgpu.rs/doc/wgpu/struct.Blas.html [`TlasInstance`]: https://wgpu.rs/doc/wgpu/struct.TlasInstance.html [`Blas::prepare_compaction_async`]: https://wgpu.rs/doc/wgpu/struct.Blas.html#method.prepare_compaction_async [`Blas::ready_for_compaction`]: https://wgpu.rs/doc/wgpu/struct.Blas.html#method.ready_for_compaction [`Queue::compact_blas`]: https://wgpu.rs/doc/wgpu/struct.Queue.html#method.compact_blas ## `naga`'s raytracing API: `naga` supports ray queries (also known as inline raytracing). To enable basic ray query functions you must add `enable wgpu_ray_query` to the shader, ray queries and acceleration structures also support tags which require extra `enable` extensions (see Acceleration structure tags for more info). Ray tracing pipelines are currently in development. Naming is mostly taken from vulkan. ### Ray Queries ```wgsl // - Initializes the `ray_query` to check where (if anywhere) the ray defined by `ray_desc` hits in `acceleration_structure` rayQueryInitialize(rq: ptr, acceleration_structure: acceleration_structure, ray_desc: RayDesc) // Overload. rayQueryInitialize(rq: ptr>, acceleration_structure: acceleration_structure, ray_desc: RayDesc) // - Traces the ray in the initialized ray_query (partially) through the scene. // - Returns true if a triangle that was hit by the ray was in a `Blas` that is not marked as opaque. // - Returns false if all triangles that were hit by the ray were in `Blas`es that were marked as opaque. // - The hit is considered `Candidate` if this function returns true, and the hit is considered `Committed` if // this function returns false. // - A `Candidate` intersection interrupts the ray traversal. // - A `Candidate` intersection may happen anywhere along the ray, it should not be relied on to give the closest hit. A // `Candidate` intersection is to allow the user themselves to decide if that intersection is valid*. If one wants to get // the closest hit a `Committed` intersection should be used. // - Calling this function multiple times will cause the ray traversal to continue if it was interrupted by a `Candidate` // intersection. rayQueryProceed(rq: ptr) -> bool // Overload. rayQueryProceed(rq: ptr>) -> bool // - Generates a hit from procedural geometry at a particular distance. rayQueryGenerateIntersection(hit_t: f32) // - Commits a hit from triangular non-opaque geometry. rayQueryConfirmIntersection() // Aborts the query which is in progress, that is, the next `rayQueryProceed` is guaranteed to return `false` // and any call to `rayQueryGetCommittedIntersection` will return the closest committed result so far. rayQueryTerminate(rq: ptr) // - Returns intersection details about a hit considered `Committed`. rayQueryGetCommittedIntersection(rq: ptr) -> RayIntersection // Overload. rayQueryGetCommittedIntersection(rq: ptr>) -> RayIntersection // - Returns intersection details about a hit considered `Candidate`. rayQueryGetCandidateIntersection(rq: ptr) -> RayIntersection // Overload. rayQueryGetCandidateIntersection(rq: ptr>) -> RayIntersection // - Returns the vertices of the hit triangle considered `Committed`. getCommittedHitVertexPositions(rq: ptr>) -> array, 3> // - Returns the vertices of the hit triangle considered `Candidate`. getCandidateHitVertexPositions(rq: ptr>) -> array, 3> ``` > [!CAUTION] > > #### ⚠️Undefined behavior ⚠️: > - Calling `rayQueryGetCommittedIntersection` or `rayQueryGetCandidateIntersection` when `rayQueryProceed` has not been > called on this ray query since it was initialized (or if the ray query has not been previously initialized). > - Calling `rayQueryGetCommittedIntersection` when `rayQueryProceed`'s latest return on this ray query is considered > `Candidate`. > - Calling `rayQueryGetCandidateIntersection` when `rayQueryProceed`'s latest return on this ray query is considered > `Committed`. > - Calling `getCommittedHitVertexPositions` when `rayQueryProceed`'s latest return on this ray query is considered > `Candidate`. > - Calling `getCandidateHitVertexPositions` when `rayQueryProceed`'s latest return on this ray query is considered > `Committed`. > - Calling `get*HitVertexPositions` when the last `rayQueryProceed` did not hit a triangle > - Calling `rayQueryProceed` when `rayQueryInitialize` has not previously been called on this ray query > - Calling `rayQueryGenerateIntersection` on a query with last intersection kind not being > `RAY_QUERY_INTERSECTION_AABB`, > - Calling `rayQueryGenerateIntersection` with `hit_t` outside of `RayDesc::t_min .. RayDesc::t_max` range. > or when `rayQueryProceed`'s latest return on this ray query is not considered `Candidate`. > - Calling `rayQueryConfirmIntersection` on a query with last intersection kind not being > `RAY_QUERY_INTERSECTION_TRIANGLE`, > or when `rayQueryProceed`'s latest return on this ray query is not considered `Candidate`. > > *this is only known undefined behaviour, and will be worked around in the future. ```wgsl struct RayDesc { // Contains flags to use for this ray (e.g. consider all `Blas`es opaque) flags: u32, // If the bitwise and of this and any `TlasInstance`'s `mask` is not zero then the object inside // the `Blas` contained within that `TlasInstance` may be hit. cull_mask: u32, // Only points on the ray whose t is greater than this may be hit. t_min: f32, // Only points on the ray whose t is less than this may be hit. t_max: f32, // The origin of the ray. origin: vec3, // The direction of the ray, t is calculated as the length down the ray divided by the length of `dir`. dir: vec3, } struct RayIntersection { // the kind of the hit, no other member of this structure is useful if this is equal // to constant `RAY_QUERY_INTERSECTION_NONE`. kind: u32, // Distance from starting point, measured in units of `RayDesc::dir`. t: f32, // Corresponds to `instance.custom_data` where `instance` is the `TlasInstance` // that the intersected object was contained in. instance_custom_data: u32, // The index into the `TlasPackage` to get the `TlasInstance` that the hit object is in instance_index: u32, // The offset into the shader binding table. Currently, this value is always 0. sbt_record_offset: u32, // The index into the `Blas`'s build descriptor (e.g. if `BlasBuildEntry::geometry` is // `BlasGeometries::TriangleGeometries` then it is the index into that contained vector). geometry_index: u32, // The object hit's index into the provided buffer (e.g. if the object is a triangle // then this is the triangle index) primitive_index: u32, // Two of the barycentric coordinates, the third can be calculated (only useful if this is a triangle). barycentrics: vec2, // Whether the hit face is the front (only useful if this is a triangle). front_face: bool, // Matrix for converting from object-space to world-space. // // This matrix needs to be on the left side of the multiplication. Using it the other way round will not work. // Use it this way: `let transformed_vector = intersecion.object_to_world * vec4(x, y, z, transform_multiplier); object_to_world: mat4x3, // Matrix for converting from world-space to object-space // // This matrix needs to be on the left side of the multiplication. Using it the other way round will not work. // Use it this way: `let transformed_vector = intersecion.world_to_object * vec4(x, y, z, transform_multiplier); world_to_object: mat4x3, } /// -- Flags for `RayDesc::flags` -- // All `Blas`es are marked as opaque. const FORCE_OPAQUE = 0x1; // All `Blas`es are marked as non-opaque. const FORCE_NO_OPAQUE = 0x2; // Instead of searching for the closest hit return the first hit. const TERMINATE_ON_FIRST_HIT = 0x4; // Unused: implemented for raytracing pipelines. const SKIP_CLOSEST_HIT_SHADER = 0x8; // If `RayIntersection::front_face` is false do not return a hit. const CULL_BACK_FACING = 0x10; // If `RayIntersection::front_face` is true do not return a hit. const CULL_FRONT_FACING = 0x20; // If the `Blas` a intersection is checking is marked as opaque do not return a hit. const CULL_OPAQUE = 0x40; // If the `Blas` a intersection is checking is not marked as opaque do not return a hit. const CULL_NO_OPAQUE = 0x80; // If the `Blas` a intersection is checking contains triangles do not return a hit. const SKIP_TRIANGLES = 0x100; // If the `Blas` a intersection is checking contains AABBs do not return a hit. const SKIP_AABBS = 0x200; /// -- Constants for `RayIntersection::kind` -- // The ray hit nothing. const RAY_QUERY_INTERSECTION_NONE = 0; // The ray hit a triangle. const RAY_QUERY_INTERSECTION_TRIANGLE = 1; // The ray hit a custom object, this will only happen in a committed intersection // if a ray which intersected a bounding box for a custom object which was then committed. const RAY_QUERY_INTERSECTION_GENERATED = 2; // The ray hit a AABB, this will only happen in a candidate intersection // if the ray intersects the bounding box for a custom object. const RAY_QUERY_INTERSECTION_AABB = 3; ``` ### Ray Tracing Pipelines Functions ```wgsl // Begins to check where (if anywhere) the ray defined by `ray_desc` hits in `acceleration_structure` calling through the `any_hit` shaders and `closest_hit` shader if something was hit or the `miss` shader if no hit was found traceRay(acceleration_structure: acceleration_structure, ray_desc: RayDesc, payload: ptr) ``` > [!CAUTION] > > #### ⚠️Undefined behavior ⚠️: > Calling `traceRay` inside another `traceRay` more than `max_recursion_depth` times > > *this is only known undefined behaviour, and will be worked around in the future. New shader stages ```wgsl // First stage to be called, allowed to call `traceRay` @ray_generation fn rg() {} // Stage called on any hit that is not opaque, not allowed to call `traceRay` @any_hit fn ah() {} // Stage called on the closest hit, allowed to call `traceRay` @closest_hit fn ch() {} // Stage call if there was never a hit, allowed to call `traceRay` @miss fn miss() {} ``` ### Acceleration structure tags These are tags that can be added to a acceleration structure (`acceleration_structure` -> `acceleration_structure<... insert tags here! ...>`) and to a ray query (`ray_query` -> `ray_query<... insert tags here! ...>`). These require more features. | Tag | Requirements | Description | | --- | ------------ | -- | | `vertex_return`| `enable wgpu_ray_query_vertex_return` | Allows getting the vertices of the hit triangle when using ray queries | ================================================ FILE: docs/big-picture.xml ================================================ 7V1be5s4E/41vrQfJHHyZU7tdrftl6dpm93c7IONbNMQSDFOnP76T9jIRgeDjMUh3XjbrREgsGbm1TujkTRAFw/r94n3uPgU+zgcQMNfD9DlAELgQJf8k5W8bEvGrrUtmCeBn1+0L7gJfuG80MhLV4GPl8yFaRyHafDIFk7jKMLTlCnzkiR+Zi+bxSH71EdvjoWCm6kXiqW3gZ8utqWuZezL/8DBfEGfDIz8zMSb3s+TeBXlzxtANNt8tqcfPFpXfv1y4fnxc+Gh6GqALpI4TrffHtYXOMzaljbb9r53B87u3jvBUapyQxgNJy9X7979+udrMPx4he9Wi7shgttqnrxwlTfIX/glf+H0hTbS5mfirCIwQOfPiyDFN4/eNDv7TNSClC3ShzA/HXoTHJ7vWuciDuOEnIriiFx/PoujNNcCYObH9BLShMbmQ8q9MJhHpGxKfh4mJ8+fcJIGRG5n+Yk0zp4rtkLeMNnleM03N1FjHD/gNCG/0cjPWmYuoVyDTSqx570+wLEzyvV6UdQGqiZeroXzXe17SZAvuTAOCObP2/n1nffzj6H/dPfpSwrMu4ehIwrmz5th5KXBEybFt3jy/vpb9uBHYie8uCoE5HvLxU6YsyAMC+1P9BdOpwebW5DKRpz5k7PqlmkS3+NChb49sS1bk6QotrxwzV+QFLANUU5OU2KyXUFMW9EMP1NR6ZYQBr6FHY0SGtsO8jRJyAQKErJalZAhk9Bw4i1JA/fYkHSYi80KA9oSYLMlsNaYMKyxIIwBtMM0a8vgiXydZ1+/rJbpTiLbs5OEnqQl5PmFW7RKzp3iEsk1ISi2A5L0P1KbAeOm5ERJXaFBsU9oU34YJ+kinseRF17tS8/3pR/jrKU2rfMDp+lL3t97qzRmxYDXQfo3+W7k3//JvpOOdnt0uS6cunyhBxH5hX/TCrKDwl3Z4f62zRG9b4t7lOOhMskt41UyxSXtQ4mql8xxWnadub0wa7xSRUhwuO0vGHqrW6zIaUmsVETGCFqMlI6SESxqyMixCkoCSlWkvmCRqmB1y3Vz61mSeC+FCx7jIEqXhZqvs4I9biBKP3LgQAbnAnDXm45Vdj35sn0D+d1wzMIUjz7bxs1v4jR11yRqyitzVURCnGz6CYlCf8w8EFYlK92Jh8D3N/qe4GXwy5ts6suUKRcCqdw6H1iXnAsDKx0eURlLbVPoNXaub/5Og7x/ONibDImtQGPM8rDt0XHqKGgAsEasAg2RMaIYR6uJZ7MlPlkDpE00bhm+uB7Gqepi9mCljlUi5NVHL6jcL/UCvhybBRTkOofhSJcSUXImMtDMrBntsn+uYnpiuNxoyxm5gMhmvT9JGejz/HE1nMYJLhDUbY0HGOpy4T1mX4mgvTDEYTxPvIcMb3ASkB+ZgRR77np/oioEMwvWmMbaJAyXOJCub8p8QRdOkK3LF+TiKsi0RF5rSnit3Zj7YbbNf46jqBQ/wECdDp+AFopggXoBFpDTJmBYpVxHuB6WcB1d4CJyFIl7e46fXhS92A4xYucFCxgxcS3TKlU9dYzgIhRjiecLJQiBNCCE9+3pc3p5++4q/gLCL//++1d082FIw1dtOb6spTuKnm8BSVS8KibYUR8vVH0j6pR07Ry5NgsAqMI54twb7vqTAeNq4fxIJpO7b5/MG/RjOZmMv34eIjE6eSHVwJZdmkOjMse6OqVGdrqrQwwBANbToRh8oqszdNAIuuPCh30KgGyNDXo9Kn2KPg57kL72vcNSILVFLddEcnex8pIeDLXKcTvtwZQ7MGBAlhuPx6iKHWdHBX05kf6ar4v+uojtnVyLU5XS0J3Fk+Ex+37bVtASuytp6wKGjUajPoGHAtvN7qBWlJuDfgIMHBkDlgzR6WDAcvxArwE/WOyAVY51fZSwXhdKcBE1u8JHtnmf2j0CVBDNUdmF71rFFEoi95jy2YuiePXKYEU/hshSLppyouVRkLaGGWuE2w/F9huEEFsRQvoRlLcRF7W1wTGYwDnRaMy9X8OgYAugcIOTp7hXmEATg5pNleP8klZDa3L1PhwcVfVagU281o0vyjmukTf3XsO4i4fdmVT09tTFk1kzLiqyWNfBlHFMU8IxG0tuNYEglVYHcpXHcY/gpvX7B6DqiQK7kR6i0ltENG2kItPjVLZKn6NKV7nrm4nQQjFC668plv0eQdpSG9WSjwJNmwvGb49ODdICwNTKOSDNxWTNtpzkUwEs57UUwzpHMKcXHBeO2R4RWscE02jW3A6GINdP6kuEkyISEEmuZETgaRXee5Fi2P63Ay86aKJhhMkwAJtlOdQEXpbDQiKnRc2hFxDDsbXGj2RMfJMGtfDC18DGjxowgpqCNQKJkWVFNTViJB2ObSursl1SbTXS00g6A1aagKPKB+IdRyczyB/TbKakGGElVXrhMBEnSvXbqdZgtnwuo9FiOEVqtT2eoXOC1bbk4fLmxDu4DUcpJQzORz6Ab5aVxZu7tixKxH4z02rG9aoyLepatWZadBrN3rS85aJPVjWzsv+kVrX5iL7R9tOIte2OOxsWMFvOVzJGABZzDgBx8JqZcJpVoi9dST3G0o/0W+iwET8TtRGsFa1fEhqZh3iJ/quREW25t0NjZFiIDY3oioxAplabraDBwIi4msc8jJ/71Ht0wslkK+K0nFIiTtt7j6f34sB+Zet2MBbvcAHj7sfibdksSB75Iv8sW19rDzGFdmRnrW7YDF3eytgebAwkDKL7sjas7KMKLSRbAYOWnTrgycrHUgzoV9WzM6O2kl+gSu/3v5tDXZ9wqZ+QNk2W/ekqtXSB9iEDrjE44LiIHUo6dfoJrYZ3m5rr9GyRM22C+HStsQN93wKvPcLAuV4vL931d/D4Dk9cGamTycucHwvcrjHbaSlphgVyBudPTrosrqGgMnPxoCAr3SWa49RR1mXl7ELED/dpGiiw+TW/zPJZj5ZZen0zIwtUOAXIucTR4SzNzrCmHX5oG9wkkDaxRupHiyM/ZfRwGnrLZTCVx1vY+R2wmRnO1UBYH0lUpz3T2RA94bQmv6hcXVJrcd4L4FcHbAi5gGFrRSKpostSaVpR9EqF7aDPVJ7gb/RK0xEXrVBcl0xcjoiPjfPwqknRHdtsX9GhGNp6xhOizvhhEmZvL+r96418lpq6lsAnMk7083YZrGM0Apax/3DuJCAPA4XT9Lla/UJpe4kKUxsaa/oKvaEA1EXW5kx0RAH4FLK6FEDIVdSEjLyTRBdXOPheoPT6ZpBUtoSxil0sSSOlhyOq/MSfgu2MYdF6Rlk+ap1Z0pXjlZUWtPkJ74KsvTod4ewX0+bnPJq8daiambBgHm+vupIJEZt5bI5bGJqljVKcmrnwfKI3whLf73GEEyKROBLPldKU7hZ5LcbPHr1pEM0HhxbzLAUVLeTEpq7oycSa4yLOyG4iLC3Xl+OGqKT8Q5Vf1s7wqM85XhvIWYDDDENtTmA1WjYEcsILW1Vcgr3eMtoARShRcmEYjsXJylG77wQGfQY+ux+4a9XTA4cisHXQ1DIdV4sFDbkRJXfkNpHYIm8RWV5UU2AqS2tVI6ztwa9GMqo60aQbMqo6ObGSjAqbwmjCaeiwuKt7aovcHmTBDQFIN7Nd/qsYqnM7AsPhlorSlCcIuezDJmZQyqOpIjnNNpL6KKjFb7/ODJ8p2PnQJZRNbm26q2shvnlCV+aKXVmpVve0KzONml0Z4MKXFp0erj8vT96ubU9xKDIvHROKTtMWvROKaLCqSnji+DJXj6nmwNbJeZFtX1RHB9htV1taZFNM+To6jMIFniv3zVHDNun+tSK0tbg8Z+UWWYCG0HSTZn5zC/PIzTDy99KW5VUim3KO/Z3wHiL2/i8v3tJSnlxqAGqRTkmFqBKGbVMq6ptLlyGVBklZlsNthmZKFl6VbUbMe+T6ZCVGE7/ItsgjvzAdSKZz8I1ZMIC8iLqzIZ6lZc6sTKj7zqzaSS3K0SiRY5yNtaRZ+2szQKgiVqdVsYoeTVYR+Xu++fMm3VOkK3FY25WuQq4t27gViNlnhJQ0drsIqRAHbzzpvMcC2iXUdCYgMTCb7wb+4eExuzuekf9drUnLR1ls1ji7/vCGgCchoEzmrSIgtcGOEJCTeSezzexx78gkFFPjt9tDvRmb+sRvu3dkUpIH3iNja2VxA4mxdc1L6HZMxYUnZmvZcmBv5naUuXXN7iX7O/bH3Fqa3SiYW+csE4lBrR/ek7ecJsHjW7jkNJPrnE4iWdyZE2l1gr50XLh8aMSs4631ZKxV4gryu3+ojrbaoLIqjeugSDVADJjdxsl9lpQNjWsvFZcDfDPxozxGyaKB7Zp43fnpTZq42i7bPbF3Sadc196dauho2t5FV/Vr7Mdvxq6lP+/c2EWP9esCkwLqIRnnwQbYg2m6SsQFi04Xd3XypkaBm2UC1yBh2eJzpgup1IsidRsTaZ0tB7QlwxyTrceAemnfUE9uyss2d7C1jqqILz5OZ+HPn9/v/MnqKU6uz5bW+bCtDZJqp7xx7SSRTuOZbMgc1U1ms6qrqp3PRg6TONvUZX85QbTFp9jPstuv/g8= ================================================ FILE: docs/broadcast_license.nu ================================================ # Quick maintenance script which broadcasts the license files to all crates # that are released on crates.io # Change to the root of the repository cd ($env.FILE_PWD | path dirname) let crates = [ "wgpu", "wgpu-core", "wgpu-core/platform-deps/apple", "wgpu-core/platform-deps/emscripten", "wgpu-core/platform-deps/wasm", "wgpu-core/platform-deps/windows-linux-android", "wgpu-hal", "wgpu-info", "wgpu-naga-bridge", "wgpu-types", "naga", "naga-cli", ] for crate in $crates { cp LICENSE.APACHE LICENSE.MIT $"./($crate)/" } ================================================ FILE: docs/release-checklist.md ================================================ This is an overview of how to run wgpu releases. ## Structure We do a major breaking release every 12 weeks. This happens no matter the status of various in-flight projects. We do a patch releases as needed in the weeks between major releases. Once a new major release is cut, we stop doing patch releases for the previous major release unless there is a critical bug or a compilation issue. ## People Anyone in the @gfx-rs/wgpu team can perform these steps. ## Major Release Process Approx 1 Week Before: - Determine if `glow` (@groves), `rspirv` (@gfx-rs/wgpu) or any other dependant crates will need a release. If so, coordinate with their maintainers. - Go through the changelog: - Re-categorize miscategorized items. - Edit major changes so a user can easily understand what they need to do. - Add missing major changes that users need to know about. - Copy-edit the changelog for clarity. Day of Release: - Update the version number in the root `Cargo.toml` to the new version, this will update all crates to the new version. - Bump the wgpu dependency numbers in the following places: - `Cargo.toml` - `examples/standalone/*` - `examples/bug-repro/*` - Grep for the previous version to ensure various documentation links are updated. - For example, if the previous version was v24.0.0, grep for `v24` and `24.0` - Ensure `glow` and `rspirv` are updated to the latest version if needed. - Add a new header for the changelog with the release version and date. - Create a PR with all of the version changes and changelog updates. - While waiting on the PR, do a dry run of publishing. ```bash cargo publish --dry-run --workspace --all-features --exclude deno_webgpu ``` - Once the PR is CI clean and publish worked, (force) merge it. - Checkout `trunk` with the merged PR. - Publish! These commands can be pasted directly into your terminal in a single command, and they will publish everything. ```bash cargo publish --workspace --all-features --exclude deno_webgpu ``` - If there were any newly published crates, ensure `github:gfx-rs/wgpu` is added as an owner of that crate. - Create a new tag called `vX.Y.Z` and push it to the repo. - For each crate being released (viz., every `publish`-able crate that is not `deno*`), create a new tag of the form `{crate_name}-vX.Y.X`. - Create a new release on the `wgpu` repo with the changelog from this version, targeting that tag - Create a branch with the with the new version `vX` and push it to the repo. - On this branch, remove the [!NOTE] at the top of [wgpu/examples/README.md]. - Complete the release's milestone on GitHub. - Create a new milestone for the next release, in 12 weeks time. - Update the release checklist with any needed changes. - Publish the link to the github release in the following places. - [r/rust](https://www.reddit.com/r/rust/). - Add an AMA comment. - Crosspost to [r/rust_gamedev](https://www.reddit.com/r/rust_gamedev/). - Add an AMA comment. - Include the r/rust post shortlink in the following posts as well: - [wgpu matrix](https://matrix.to/#/#wgpu:matrix.org) - [Rust Gamedev Discord](https://discord.gg/X3MYBNXUMJ) in the #crates and #wgpu channel - [Bevy Discord](https://discord.com/invite/bevy) in the #rendering-dev channel - [Graphics Programming Discord](https://discord.gg/6mgNGk7) in the #webgpu channel - [Rust Community Discord](https://discord.gg/rust-lang-community) in the #games-and-graphics channel ## Patch Release Process - Enumerate all PRs that haven't been backported yet. These use the `PR: needs back-porting` label. [GH Link](https://github.com/gfx-rs/wgpu/pulls?q=sort%3Aupdated-desc+is%3Apr+label%3A%22PR%3A+needs+back-porting%22) - On _your own branch_ based on the latest release branch. Cherry-pick the PRs that need to be backported. When modifying the commits, use --append to retain their original authorship. - Remove the `needs-backport` label from the PRs. - Fix the changelogs items and add a new header for the patch release with the release version and date. - The release section should start with a header saying the following (for example) ```markdown This release includes `crate1`, `crate2` and `crate3` version `X.Y.Z`. All other crates remain at their previous versions. ``` - Once all the PRs are cherry-picked, look at the diff between HEAD and the previous patch release. See what crates changed. - Bump all the versions of the crates that changed. - Create a PR with all of the version changes and changelog updates into the release branch. - Once the PR is CI clean, (force) rebase merge it. - Checkout the release branch with the merged PR. - Publish all relevant crates (see list above). - Create a new release on the `wgpu` repo with the relevant changelog included, based on a new tag called `vX.Y.Z` in the release branch. - For each crate released, also create a tag `{crate_name}-vX.Y.Z`. - Backport the changelog and version bumps to the `trunk` branch. - Ensure that any items in the newly-released changelog don't appear in the "unreleased" section of the trunk changelog. - Update the release checklist with any needed changes. ================================================ FILE: docs/review-checklist.md ================================================ # Review Checklist This is a collection of notes on things to watch out for when reviewing pull requests submitted to wgpu and Naga. Ideally, we want to keep items off this list entirely: - Using Rust effectively can turn some mistakes into compile-time errors. For example, in Naga, using exhaustive matching ensures that changes to the IR will cause compile-time errors in any code that hasn't been updated. - Refactoring can gather together all the code responsible for enforcing some invariant in one place, making it clear whether a change preserves it or not. For example, Naga localizes all handle validation to `naga::valid::Validator::validate_module_handles`, allowing the rest of the validator to assume that all handles are valid. - Offering custom abstractions can help contributors avoid implementing a weaker abstraction by themselves. For example, because `HandleSet` and `HandleVec` are used throughout Naga, contributors are less likely to write code that uses a `BitSet` or `Vec` on handle indices, which would invite bugs by erasing the handle types. This checklist gathers up the concerns that we haven't found a satisfying way to address in a more robust way. ## Naga ### General - [ ] If your change iterates over a collection, did you ensure the order of iteration was deterministic? Using `HashMap` and `HashSet` is fine, as long as you don't iterate over it. - [ ] If you insert elements into a set or map that you expect are not already present, did you make an assertion about `insert`'s return value? ### WGSL Extensions - [ ] If you added a new feature to WGSL that is not covered by the WebGPU specification: - [ ] Did you add a `Capability` flag for it? - [ ] Did you document the feature fully in that flag's doc comment? - [ ] Did you ensure the validator rejects programs that use the feature unless its capability is enabled? ### IR changes If your change adds or removes `Handle`s from the IR: - [ ] Did you update handle validation in `valid::handles`? - [ ] Did you update the compactor in `compact`? - [ ] Did you update `back::pipeline_constants::adjust_expr`? If your change adds a new operation: - [ ] Did you update the typifier in `proc::typifier`? - [ ] Did you update the validator in `valid::expression`? - [ ] If the operation can be used in constant expressions, did you update the constant evaluator in `proc::constant_evaluator`? ### Backend changes - [ ] If your change introduces any new identifiers to generated code, how did you ensure they won't conflict with the users' identifiers? (This is usually not relevant to the SPIR-V backend.) - [ ] Did you use the `Namer` to generate a fresh identifier? - [ ] Did you register the identifier as a reserved word with the `Namer`? - [ ] Did you use a reserved prefix registered with the `Namer`? ================================================ FILE: docs/testing.md ================================================ # Testing in `wgpu` and `naga` There exist a large variety of tests within the `wgpu` repository to make sure we can easily test all the aspects of our libraries. This document serves as a guide to each class of test, and what they are used for. ## Requirements The tests require that the [Vulkan SDK](https://vulkan.lunarg.com/sdk/home) is installed on the system and the `bin` folder of the SDK is in your `PATH`. Without this some tests may fail to run, or report false negatives. Additionally you require you run the tests with `cargo-nextest`. This is what our xtask calls. You can install it with `cargo install cargo-nextest`. ## Run All Tests To run all tests, run `cargo xtask test` from the root of the repository. ## Test Breakdown This is a table of contents, in the form of the repository's directory structure. - benches - [benches](#benchmark-tests) - [cts_runner](#webgpu-cts) - examples - [features](#example-tests) - naga - tests - [example_wgsl](#naga-example-tests) - [snapshot](#naga-snapshot-tests) - [spirv-capabilities](#naga-spirv-capabilities-test) - [validation](#naga-validation) - [wgsl_errors](#naga-wgsl-error-tests) - player - [tests](#player-tests) - tests - [compile](#wgpu-compile-tests) - [dependency](#wgpu-dependency-tests) - [gpu](#wgpu-gpu-tests) - [trace](#wgpu-trace-tests) - [validation](#wgpu-validation-tests) And where applicable [unit-tests](#unit-tests) are scatteredthroughout the codebase. ## Benchmark Tests - Located in: `benches/benches` - Run with `cargo nextest run --bench wgpu-benchmark` - `wgpu` benchmarks for performance testing. These are benchmarks that test the performance of `wgpu` in various scenarios. When run as part of the test suite, they run a single iteration of each benchmark to ensure they continue to function. These tests only run on your system's default GPU. The benchmarks should be very careful to avoid doing any significant work (including connecting to a GPU) outside of the various `benchmark` `criterion` functions. If this is done, the benchmarks will take a long time to list available tests, slowing down the test suite. To run the benchmarks for benchmarking purposes, use `cargo bench`. ## Example Tests - Located in: `examples/features` - Run with `cargo xtask test --bin wgpu-examples` - Uses a custom `#[gpu_test]` harness. - `wgpu` integration tests, with access to `wgpu_test` helpers. These tests validate that the examples are functioning correctly and do not have any regressions. They use the same harness as the [gpu tests](#wgpu-gpu-tests), see that section for more information on the harness. These tests use `nv-flip`'s image comparison through the wgpu example framework to validate that the images outputted by the examples are within tolerance of the expected output. Examples written in `examples/standalone` do not have tests, as they should be easy to copy into a standalone project. ## `naga` Example Tests - Located in: `naga/tests/naga/example_wgsl` - Run with `cargo nextest run --test naga example_wgsl` This simple test ensures that all wgsl files in the `examples` directory can be parsed by `naga`'s `wgsl` parser and validate correctly. ## `naga` Snapshot Tests - Located in: `naga/tests/naga/snapshot`, `naga/tests/in`, and `naga/tests/out` - Run with `cargo nextest run --test naga snapshots` - Data driven snapshot tests for `naga`'s input/output. These tests are snapshot tests for `naga`s parsers and code generators. There are inputs in `wgsl`, `spirv`, and `glsl`. There are outputs for `hlsl`, `spirv`, `wgsl`, `msl`, `glsl`, and naga's internal IR. The tests can be configured by a sidecar toml file of the same name as the input file. This is the goto tool for testing all kinds of codegen and parsing features. To avoid clutter we generally use the following pattern: - `wgsl` tests generate output to all backends. - `spirv`, `glsl` tests generate `wgsl` output This "butterfly" pattern ensures we don't need to test the full matrix of possibilities to get full coverage. While we do not run the results of the code generators, we do test that the generated code is valid. This is done by running `cargo xtask validate ` in the `naga` directory and will use the respective tool to validate the generated code. ## `naga` SPIR-V Capabilities Tests - Located in: `naga/tests/naga/spirv_capabilities` - Run with `cargo nextest run --test naga spirv_capabilities` - Uses the standard `#[test]` harness. These tests convert the given wgsl snippet to spirv and then assert that the spirv has enabled the expected capabilities. ## `naga` Validation Tests - Located in: `naga/tests/naga/validation` - Run with `cargo nextest run --test naga validation` These are hand rolled tests against the naga's validator. If you don't need to test the validator with a custom module, and can use the `wgsl` frontend, you should put the test in the [wgsl errors](#naga-wgsl-error-tests) tests. ## `naga` WGSL Error Tests - Located in: `naga/tests/naga/wgsl_errors` - Run with `cargo nextest run --test naga wgsl_errors` These are tests for the error messages that the `wgsl` frontend produces. Additionally you can check that a given validation error is produced by the validator from a given `wgsl` snippet. ## `player` Tests - Located in: `player/tests` - Run with `cargo nextest run --test player` - Data driven tests using the `player`'s replay system. - `wgpu` integration tests. These are soft-deprecated tests which are another way to write API tests. These use captures of the api calls and replay them to assert on the behavior. They are very difficult to write, and the trace capturing system is currently broken, so these tests exist, but you should not write new ones. These tests only run on your system's default GPU. ## `wgpu` Compile Tests - Located in: `tests/tests/wgpu-compile` - Run with `cargo nextest run --test wgpu-compile` - `trybuild` tests of all rust files in `tests/tests/wgpu-compile/fail` directory. These use the `trybuild` crate to test a few scenarios where the `wgpu` crate is expected to fail to compile. This mainly revolves around ensuring lifetimes are properly handled when dropping passes, etc. ## `wgpu` Dependency Tests - Located in: `tests/tests/wgpu-dependency` - Run with `cargo nextest run --test wgpu-dependency` - Tests against `cargo tree`. These tests ensure that the `wgpu` crate has the correct dependency tree on all platforms. It's super easy to subtly mess up the dependencies which can cause issues or extra dependencies to be pulled in. This provides a way to ensure that our `toml` files are correct. ## `wgpu` GPU Tests - Located in: `tests/tests/wgpu-gpu` - Run with `cargo xtask test --test wgpu-gpu` - Uses a custom `#[gpu_test]` harness. - `wgpu` integration tests, with access to `wgpu_test` helpers. These tests use a custom harness to run each test on all GPUs available on the system. They are general integration tests that write code against the normal `wgpu` API and assert on the behavior. These tests are useful to check the runtime behavior of a program, validate that there are no validation errors coming from the `vulkan`/`dx12`/`metal` validation layers, and ensure behavior is the same across GPUs. If the test does not need to run on a real GPU, it should be in the [validation tests](#wgpu-validation-tests) instead. There is a special parameter system that deals with if a GPU can support the given test, and dealing with expectation management for tests that are expected to fail due to driver or wgpu bugs. Normal `#[test]`s will not be found in this test crate, as we use a custom harness. See also the [example tests](#example-tests) for additional GPU tests. ## `wgpu` Trace Tests - Located in: `tests/tests/wgpu_trace.rs` - Run with `cargo nextest run --test wgpu_trace` - Use the standard `#[test]` harness. These tests are focused on testing the tracing functionality in `wgpu`. They use the a special `noop` backend which does not connect to a real GPU. ## `wgpu` Validation Tests - Located in: `tests/tests/wgpu-validation` - Run with `cargo nextest run --test wgpu-validation` - Use the standard `#[test]` harness. - `wgpu` integration tests, with access to `wgpu_test` helpers. These tests are focused on testing the validation inside of `wgpu-core`. They are written against the `wgpu` API, but are targeting a special `noop` backend which does not connect to a real GPU. This is significantly faster and simpler than running on real hardware, and allows any validation logic to be checked, even if real hardware does not support those features. ## Unit Tests - Located throughout the codebase. - Run with `cargo nextest test -p ` - Standard `#[test]`s. Throughout the codebase we have standard `#[test]`s that test individual functions or small parts of the codebase. These don't run on the gpu. ## WebGPU CTS WebGPU includes a Conformance Test Suite to validate that implementations are working correctly. We run cases from the CTS against wgpu using [Deno](https://deno.com/). A [default list of enabled tests](../cts_runner/test.lst) is automatically run on pull requests in CI. To run the default set of CTS tests locally, run: ``` cargo xtask cts ``` You can also specify a test selector on the command line: ``` cargo xtask cts 'webgpu:api,operation,command_buffer,basic:*' ``` Or supply your own test list in a file: ``` cargo xtask cts -f your_tests.lst ``` To find the full list of tests, go to the [web-based standalone CTS runner](https://gpuweb.github.io/cts/standalone/?runnow=0&worker=0&debug=0&q=webgpu:*). The version of the CTS used by `cargo xtask cts` is specified in [`cts_runner/revision.txt`](../cts_runner/revision.txt). ================================================ FILE: examples/README.md ================================================ > [!NOTE] > These are the examples for the development version of wgpu. If you want to see the examples for the latest crates.io release > of wgpu, go to the [latest release branch](https://github.com/gfx-rs/wgpu/tree/v29/examples#readme). # Examples If you are just starting your graphics programming journey entirely, we recommend going through [Learn-wgpu](https://sotrh.github.io/learn-wgpu/) for a mode guided tutorial, which will also teach you the basics of graphics programming. ## Standalone Examples All the standalone examples are separate crates and include all boilerplate inside the example itself. They can be cloned out of the repository to serve as a starting point for your own projects and are fully commented. | Name | Description | Platforms | |--------|-------------|-----------| | --- | Introductory Examples | --- | | [1. hello compute](standalone/01_hello_compute/) | Simplest example and shows how to run a compute shader on a given set of input data and get the results back. | Native-Only | | [2. hello window](standalone/02_hello_window/) | Shows how to create a window and render into it. | Native-Only | | --- | Special Examples | --- | | [custom backend](standalone/custom_backend/) | Shows how to implement and use custom wgpu context | All | You can also use [`cargo-generate`](https://github.com/cargo-generate/cargo-generate) to easily use these as a basis for your own projects. ```sh cargo generate gfx-rs/wgpu --branch v29 ``` ## Framework Examples These examples use a common framework to handle wgpu init, window creation, and event handling. This allows the example to focus on the unique code in the example itself. Refer to the standalone examples for a more detailed look at the boilerplate code. #### Graphics - `hello_triangle` - Provides an example of a bare-bones wgpu workflow using the Winit crate that simply renders a red triangle on a green background. - `uniform_values` - Demonstrates the basics of enabling shaders and the GPU, in general, to access app state through uniform variables. `uniform_values` also serves as an example of rudimentary app building as the app stores state and takes window-captured keyboard events. The app displays the Mandelbrot Set in grayscale (similar to `storage_texture`) but allows the user to navigate and explore it using their arrow keys and scroll wheel. - `cube` - Introduces the user to slightly more advanced models. The example creates a set of triangles to form a cube on the CPU and then uses a vertex and index buffer to send the generated model to the GPU for usage in rendering. It also uses a texture generated on the CPU to shade the sides of the cube and a uniform variable to apply a transformation matrix to the cube in the shader. - `bunnymark` - Demonstrates many things, but chief among them is performing numerous draw calls with different bind groups in one render pass. The example also uses textures for the icon and uniform buffers to transfer both global and per-particle states. - `skybox` - Shows off too many concepts to list here. The name comes from game development where a "skybox" acts as a background for rendering, usually to add a sky texture for immersion, although they can also be used for backdrops to give the idea of a world beyond the game scene. This example does so much more than this, though, as it uses a car model loaded from a file and uses the user's mouse to rotate the car model in 3d. `skybox` also makes use of depth textures and similar app patterns to `uniform_values`. - `shadow` - Likely by far the most complex example (certainly the largest in lines of code) of the official wgpu examples. `shadow` demonstrates basic scene rendering with the main attraction being lighting and shadows (as the name implies). It is recommended that any user looking into lighting be very familiar with the basic concepts of not only rendering with wgpu but also the primary mathematical ideas of computer graphics. - `multiple-render-targets` - Demonstrates how to render to two texture targets simultaneously from fragment shader. - `render_to_texture` - Renders to an image texture offscreen, demonstrating both off-screen rendering as well as how to add a sort of resolution-agnostic screenshot feature to an engine. This example either outputs an image file of your naming (pass command line arguments after specifying a `--` like `cargo run --bin wgpu-examples -- render_to_texture "test.png"`) or adds an `img` element containing the image to the page in WASM. - `render_with_compute` - Renders an image using compute shaders. - `ray_cube_fragment` - Demonstrates using ray queries with a fragment shader. - `ray_scene` - Demonstrates using ray queries and model loading - `ray_shadows` - Demonstrates a simple use of ray queries - high quality shadows - uses a light set with immediates to raytrace through an untransformed scene and detect whether there is something obstructing the light. - `mesh_shader` - Renders a triangle to a window with mesh shaders, while showcasing most mesh shader related features(task shaders, payloads, per primitive data). #### Compute - `hello_compute` - Demonstrates the basic workflow for getting arrays of numbers to the GPU, executing a shader on them, and getting the results back. The operation it performs is finding the Collatz value (how many iterations of the [Collatz equation](https://en.wikipedia.org/wiki/Collatz_conjecture) it takes for the number to either reach 1 or overflow) of a set of numbers and prints the results. - `repeated_compute` - Mostly for going into detail on subjects `hello-compute` did not. It, too, computes the Collatz conjecture, but this time, it automatically loads large arrays of randomly generated numbers, prints them, runs them, and prints the result. It does this cycle 10 times. - `hello_workgroups` - Teaches the user about the basics of compute workgroups; what they are and what they can do. - `hello_synchronization` - Teaches the user about synchronization in WGSL, the ability to force all invocations in a workgroup to synchronize with each other before continuing via a sort of barrier. - `storage_texture` - Demonstrates the use of storage textures as outputs to compute shaders. The example on the outside seems very similar to `render_to_texture` in that it outputs an image either to the file system or the web page, except displaying a grayscale render of the Mandelbrot Set. However, inside, the example dispatches a grid of compute workgroups, one for each pixel, which calculates the pixel value and stores it to the corresponding pixel of the output storage texture. This example either outputs an image file of your naming (pass command line arguments after specifying a `--` like `cargo run --bin wgpu-examples -- storage_texture "test.png"`) or adds an `img` element containing the image to the page in WASM. - `big_compute_buffers` - Demonstrates how you can split _large_ datasets across multiple buffers, using `binding_array` in your `wgsl` [NOTE: native only, no WASM support]. #### Combined - `boids` - Demonstrates how to combine compute and render workflows by performing a [boid](https://en.wikipedia.org/wiki/Boids) simulation and rendering the boids to the screen as little triangles. - `ray_cube_compute` - Demonstrates using ray queries with a compute shader. - `ray_traced_triangle` - A simpler example demonstrating using ray queries with a compute shader ## Running on the Web To run the examples in a browser, run `cargo xtask run-wasm`. Then open `http://localhost:8000` in your browser, and you can choose an example to run. Naturally, in order to display any of the WebGPU based examples, you need to make sure your browser supports it. ================================================ FILE: examples/bug-repro/01_texture_atomic_bug/Cargo.toml ================================================ [package] name = "wgpu-bug-repro-01-texture-atomic-bug" edition = "2021" rust-version = "1.87" publish = false [dependencies] env_logger = "0.11" pollster = "0.4" wgpu = "29.0.0" winit = "0.30.8" ================================================ FILE: examples/bug-repro/01_texture_atomic_bug/src/main.rs ================================================ //! Repro for Metal driver bug where fragment shader texture atomic writes randomly drop unless //! a compute pass with atomic access to the texture is inserted between the write and the read. //! Both 32-bit and 64-bit atomic textures are affected. //! The bug does not reproduce with `MTL_SHADER_VALIDATION=1`. //! Known to reproduce on Apple M4 Max, macOS 26.3 (Tahoe). //! Dropped writes appear as various tile-shaped black holes that flicker around each frame. use std::sync::Arc; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::window::{Window, WindowId}; fn main() { env_logger::init(); let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll); let mut app = App::default(); event_loop.run_app(&mut app).unwrap(); } #[derive(Default)] struct App { state: Option, } struct State { instance: wgpu::Instance, window: Arc, device: wgpu::Device, queue: wgpu::Queue, surface: wgpu::Surface<'static>, surface_config: wgpu::SurfaceConfiguration, width: u32, height: u32, _storage_texture: wgpu::Texture, _dummy_texture: wgpu::Texture, clear_pipeline: wgpu::ComputePipeline, clear_bg: wgpu::BindGroup, raster_pipeline: wgpu::RenderPipeline, raster_bg: wgpu::BindGroup, dummy_view: wgpu::TextureView, vis_pipeline: wgpu::RenderPipeline, vis_bg: wgpu::BindGroup, } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { if self.state.is_some() { return; } let window = Arc::new( event_loop .create_window( Window::default_attributes() .with_title("Metal Texture Atomic Bug") .with_inner_size(winit::dpi::LogicalSize::new(2560, 1440)), ) .unwrap(), ); self.state = Some(State::new(window)); } fn window_event( &mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent, ) { let Some(state) = &mut self.state else { return }; match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::Resized(size) if size.width > 0 && size.height > 0 => { state.surface_config.width = size.width; state.surface_config.height = size.height; state .surface .configure(&state.device, &state.surface_config); } WindowEvent::RedrawRequested => state.render_frame(), _ => {} } } fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { if let Some(state) = &self.state { state.window.request_redraw(); } } } fn tex_bind_entry(binding: u32, resource: wgpu::BindingResource<'_>) -> wgpu::BindGroupEntry<'_> { wgpu::BindGroupEntry { binding, resource } } impl State { fn new(window: Arc) -> Self { let size = window.inner_size(); let width = size.width.max(1); let height = size.height.max(1); let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env()); let surface = instance.create_surface(window.clone()).unwrap(); let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { compatible_surface: Some(&surface), ..Default::default() })) .expect("No adapter"); println!("Adapter: {:?}", adapter.get_info().name); let required = wgpu::Features::TEXTURE_ATOMIC; assert!( adapter.features().contains(required), "Texture atomics not supported" ); let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor { required_features: required, ..Default::default() })) .unwrap(); let surface_format = surface.get_capabilities(&adapter).formats[0]; let surface_config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: surface_format, width, height, present_mode: wgpu::PresentMode::AutoVsync, alpha_mode: wgpu::CompositeAlphaMode::Auto, view_formats: vec![], desired_maximum_frame_latency: 2, }; surface.configure(&device, &surface_config); let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let tex_size = wgpu::Extent3d { width, height, depth_or_array_layers: 1, }; let storage_texture = device.create_texture(&wgpu::TextureDescriptor { label: None, size: tex_size, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R32Uint, usage: wgpu::TextureUsages::STORAGE_ATOMIC | wgpu::TextureUsages::STORAGE_BINDING, view_formats: &[], }); let storage_view = storage_texture.create_view(&Default::default()); let dummy_texture = device.create_texture(&wgpu::TextureDescriptor { label: None, size: tex_size, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R8Uint, usage: wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }); let dummy_view = dummy_texture.create_view(&Default::default()); // Pipelines use auto-layout; bind groups derived from pipeline layouts. let clear_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: None, module: &shader, entry_point: Some("clear"), compilation_options: Default::default(), cache: None, }); let clear_bg = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &clear_pipeline.get_bind_group_layout(0), entries: &[tex_bind_entry( 0, wgpu::BindingResource::TextureView(&storage_view), )], }); let raster_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: None, vertex: wgpu::VertexState { module: &shader, entry_point: Some("fullscreen"), buffers: &[], compilation_options: Default::default(), }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("write_atomic"), targets: &[Some(wgpu::ColorTargetState { format: wgpu::TextureFormat::R8Uint, blend: None, write_mask: wgpu::ColorWrites::empty(), })], compilation_options: Default::default(), }), primitive: Default::default(), depth_stencil: None, multisample: Default::default(), multiview_mask: None, cache: None, }); let raster_bg = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &raster_pipeline.get_bind_group_layout(0), entries: &[tex_bind_entry( 0, wgpu::BindingResource::TextureView(&storage_view), )], }); let vis_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: None, vertex: wgpu::VertexState { module: &shader, entry_point: Some("fullscreen"), buffers: &[], compilation_options: Default::default(), }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("visualize"), targets: &[Some(surface_format.into())], compilation_options: Default::default(), }), primitive: Default::default(), depth_stencil: None, multisample: Default::default(), multiview_mask: None, cache: None, }); let vis_bg = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &vis_pipeline.get_bind_group_layout(0), entries: &[tex_bind_entry( 0, wgpu::BindingResource::TextureView(&storage_view), )], }); State { instance, window, device, queue, surface, surface_config, width, height, _storage_texture: storage_texture, _dummy_texture: dummy_texture, clear_pipeline, clear_bg, raster_pipeline, raster_bg, dummy_view, vis_pipeline, vis_bg, } } fn render_frame(&mut self) { let frame = match self.surface.get_current_texture() { wgpu::CurrentSurfaceTexture::Success(f) => f, wgpu::CurrentSurfaceTexture::Suboptimal(_) | wgpu::CurrentSurfaceTexture::Outdated => { self.surface.configure(&self.device, &self.surface_config); return; } wgpu::CurrentSurfaceTexture::Lost => { self.surface = self.instance.create_surface(self.window.clone()).unwrap(); self.surface.configure(&self.device, &self.surface_config); return; } _ => return, }; let frame_view = frame.texture.create_view(&Default::default()); let mut enc = self.device.create_command_encoder(&Default::default()); // Clear texture to zero { let mut pass = enc.begin_compute_pass(&Default::default()); pass.set_pipeline(&self.clear_pipeline); pass.set_bind_group(0, &self.clear_bg, &[]); pass.dispatch_workgroups(self.width.div_ceil(8), self.height.div_ceil(8), 1); } // Write via textureAtomicMax in fragment shader { let mut pass = enc.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &self.dummy_view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Discard, }, })], ..Default::default() }); pass.set_pipeline(&self.raster_pipeline); pass.set_bind_group(0, &self.raster_bg, &[]); pass.draw(0..3, 0..1); } // Read texture in fragment shader to visualize { let mut pass = enc.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &frame_view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store, }, })], ..Default::default() }); pass.set_pipeline(&self.vis_pipeline); pass.set_bind_group(0, &self.vis_bg, &[]); pass.draw(0..3, 0..1); } self.queue.submit([enc.finish()]); frame.present(); } } ================================================ FILE: examples/bug-repro/01_texture_atomic_bug/src/shader.wgsl ================================================ @group(0) @binding(0) var tex: texture_storage_2d; @compute @workgroup_size(8, 8, 1) fn clear(@builtin(global_invocation_id) id: vec3) { let dims = textureDimensions(tex); if id.x >= dims.x || id.y >= dims.y { return; } textureStore(tex, id.xy, vec4(0u)); } @vertex fn fullscreen(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4 { let x = f32(i32(vi) / 2) * 4.0 - 1.0; let y = f32(i32(vi) % 2) * 4.0 - 1.0; return vec4(x, y, 0.0, 1.0); } @fragment fn write_atomic(@builtin(position) pos: vec4) { textureAtomicMax(tex, vec2(pos.xy), 1u); } @group(0) @binding(0) var tex_read: texture_storage_2d; @fragment fn visualize(@builtin(position) pos: vec4) -> @location(0) vec4 { let val = textureLoad(tex_read, vec2(pos.xy)).r; if val == 1u { return vec4(0.0, 0.8, 0.0, 1.0); } if val == 0u { return vec4(0.0, 0.0, 0.0, 1.0); } return vec4(1.0, 0.0, 0.0, 1.0); } ================================================ FILE: examples/features/Cargo.toml ================================================ [package] name = "wgpu-examples" version.workspace = true authors.workspace = true edition.workspace = true description = "Common example code" homepage.workspace = true repository.workspace = true keywords.workspace = true license.workspace = true rust-version.workspace = true publish = false [package.metadata.cargo-machete] # Cargo machete struggles with this dev dependency: ignored = ["wasm_bindgen_test"] [lib] path = "src/lib.rs" harness = false bench = false [[bin]] name = "wgpu-examples" path = "src/main.rs" test = false [features] default = [] webgl = ["wgpu/webgl"] webgpu = ["wgpu/webgpu"] [dependencies] bytemuck.workspace = true cfg-if.workspace = true encase = { workspace = true } flume.workspace = true glam = { workspace = true, features = ["bytemuck", "encase"] } half = { workspace = true, features = ["bytemuck"] } ktx2.workspace = true log.workspace = true nanorand = { workspace = true, features = ["getrandom"] } noise.workspace = true obj.workspace = true png.workspace = true pollster.workspace = true web-time.workspace = true winit.workspace = true [dev-dependencies] wgpu-test.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] env_logger.workspace = true wgpu = { workspace = true, features = ["trace"] } [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook.workspace = true console_log.workspace = true fern.workspace = true wasm-bindgen.workspace = true wasm-bindgen-futures.workspace = true wgpu = { workspace = true, default-features = false, features = [ "wgsl", "std", "trace", ] } # We need these features in the framework examples and tests web-sys = { workspace = true, features = [ "Location", "Blob", "RequestInit", "RequestMode", "Request", "ImageData", "Response", "HtmlImageElement", "WebGl2RenderingContext", "CanvasRenderingContext2d", ] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test.workspace = true [lints.clippy] disallowed_types = "allow" ================================================ FILE: examples/features/src/big_compute_buffers/README.md ================================================ # big-compute-buffers *NOTE: `binding_array` is Vulkan only.* This example assumes you're familiar with the other GP-GPU compute examples in this repository, if you're not you should go look at those first. This example also assumes you've specifically come here looking to do this, because you want at least the following: 1. To be working on your 'data' in your shader treating it contiguously, not batching etc. 2. The data you are wanting to work on does **not** fit within a single buffer on your device, see the [hello](https://github.com/gfx-rs/wgpu/tree/trunk/examples/src/hello) example for how to print information about your unique device to explore its maximum supported buffer size. Demonstrates how to split larger datasets (things too big to fit into a single buffer), across multiple buffers. - Creates a set of buffers totalling `1GB`, full of `0.0f32`. - Moves those buffers to the DEVICE. - Increments each element in each set of buffers by `1.0`, on the DEVICE. - Returns those modified buffers full of `1.0` values as a back to the HOST. ## Caution - Large buffers can fail to allocate due to fragmentation issues, you will **always** need not only the appropriate amount of space required for your buffer(s) but, that space will also need to be contiguous within GPU/Device memory for this strategy to work. You can read more about fragmentation [here](https://developer.nvidia.com/docs/drive/drive-os/archives/6.0.4/linux/sdk/common/topics/graphics_content/avoiding_memory_fragmentation.html). ## To Run ```sh # linux/mac RUST_LOG=wgpu_examples::big_compute_buffers=info cargo run -r --bin wgpu-examples -- big_compute_buffers # windows (Powershell) $env:WGPU_BACKEND="Vulkan"; $env:RUST_LOG="wgpu_examples::big_compute_buffers=info"; cargo run -r --bin wgpu-examples -- big_compute_buffers ``` ## Example Output ```txt [2024-09-29T11:47:55Z INFO wgpu_examples::big_compute_buffers] All 0.0s [2024-09-29T11:47:58Z INFO wgpu_examples::big_compute_buffers] GPU RUNTIME: 3228ms [2024-09-29T11:47:58Z INFO wgpu_examples::big_compute_buffers] All 1.0s ``` ================================================ FILE: examples/features/src/big_compute_buffers/mod.rs ================================================ //! This example shows you a potential course for when your 'data' is too large //! for a single Buffer. //! //! A lot of things aren't explained here via comments. See hello-compute and //! repeated-compute for code that is more thoroughly commented. use std::num::{NonZeroU32, NonZeroU64}; use wgpu::{util::DeviceExt, Features}; // These are set by the minimum required defaults for webgpu. const MAX_BUFFER_SIZE: u64 = 1 << 27; // 134_217_728 // 134MB const MAX_DISPATCH_SIZE: u32 = (1 << 16) - 1; pub async fn execute_gpu(numbers: &[f32]) -> Vec { let instance = wgpu::Instance::default(); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions::default()) .await .unwrap(); let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: None, // These features are required to use `binding_array` in your wgsl. // Without them your shader may fail to compile. required_features: Features::BUFFER_BINDING_ARRAY | Features::STORAGE_RESOURCE_BINDING_ARRAY | Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, memory_hints: wgpu::MemoryHints::Performance, required_limits: wgpu::Limits { max_buffer_size: MAX_BUFFER_SIZE, max_binding_array_elements_per_shader_stage: 8, ..Default::default() }, ..Default::default() }) .await .unwrap(); execute_gpu_inner(&device, &queue, numbers).await } pub async fn execute_gpu_inner( device: &wgpu::Device, queue: &wgpu::Queue, numbers: &[f32], ) -> Vec { let (staging_buffers, storage_buffers, bind_group, compute_pipeline) = setup(device, numbers); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: Some("compute pass descriptor"), timestamp_writes: None, }); cpass.set_pipeline(&compute_pipeline); cpass.set_bind_group(0, Some(&bind_group), &[]); cpass.dispatch_workgroups(MAX_DISPATCH_SIZE.min(numbers.len() as u32), 1, 1); } for (storage_buffer, staging_buffer) in storage_buffers.iter().zip(staging_buffers.iter()) { let stg_size = staging_buffer.size(); encoder.copy_buffer_to_buffer( storage_buffer, // Source buffer 0, staging_buffer, // Destination buffer 0, stg_size, ); } queue.submit(Some(encoder.finish())); for staging_buffer in &staging_buffers { let slice = staging_buffer.slice(..); slice.map_async(wgpu::MapMode::Read, |_| {}); } device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); let mut data = Vec::new(); for staging_buffer in &staging_buffers { let slice = staging_buffer.slice(..); let mapped = slice.get_mapped_range(); data.extend_from_slice(bytemuck::cast_slice(&mapped)); drop(mapped); staging_buffer.unmap(); } data } fn setup( device: &wgpu::Device, numbers: &[f32], ) -> ( Vec, Vec, wgpu::BindGroup, wgpu::ComputePipeline, ) { let cs_module = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let staging_buffers = create_staging_buffers(device, numbers); let storage_buffers = create_storage_buffers(device, numbers); let (bind_group_layout, bind_group) = setup_binds(&storage_buffers, device); let compute_pipeline = setup_pipeline(device, bind_group_layout, cs_module); ( staging_buffers, storage_buffers, bind_group, compute_pipeline, ) } fn setup_pipeline( device: &wgpu::Device, bind_group_layout: wgpu::BindGroupLayout, cs_module: wgpu::ShaderModule, ) -> wgpu::ComputePipeline { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Compute Pipeline Layout"), bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some("Compute Pipeline"), layout: Some(&pipeline_layout), module: &cs_module, entry_point: Some("main"), compilation_options: Default::default(), cache: None, }) } fn setup_binds( storage_buffers: &[wgpu::Buffer], device: &wgpu::Device, ) -> (wgpu::BindGroupLayout, wgpu::BindGroup) { let buffers: Vec<_> = storage_buffers .iter() .map(|b| b.as_entire_buffer_binding()) .collect(); let entry = wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::BufferArray(&buffers), }; let bgl_entry = wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: Some(NonZeroU64::new(4).unwrap()), }, count: Some(NonZeroU32::new(buffers.len() as u32).unwrap()), }; let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Custom Storage Bind Group Layout"), entries: &[bgl_entry], }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("Combined Storage Bind Group"), layout: &bind_group_layout, entries: &[entry], }); (bind_group_layout, bind_group) } fn calculate_chunks(numbers: &[f32], max_buffer_size: u64) -> Vec<&[f32]> { let max_elements_per_chunk = max_buffer_size as usize / std::mem::size_of::(); numbers.chunks(max_elements_per_chunk).collect() } fn create_storage_buffers(device: &wgpu::Device, numbers: &[f32]) -> Vec { let chunks = calculate_chunks(numbers, MAX_BUFFER_SIZE); chunks .iter() .enumerate() .map(|(e, seg)| { device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(&format!("Storage Buffer-{e}")), contents: bytemuck::cast_slice(seg), usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC, }) }) .collect() } fn create_staging_buffers(device: &wgpu::Device, numbers: &[f32]) -> Vec { let chunks = calculate_chunks(numbers, MAX_BUFFER_SIZE); (0..chunks.len()) .map(|e| { let size = std::mem::size_of_val(chunks[e]) as u64; device.create_buffer(&wgpu::BufferDescriptor { label: Some(&format!("staging buffer-{e}")), size, usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }) }) .collect() } #[cfg_attr(target_arch = "wasm32", allow(clippy::allow_attributes, dead_code))] async fn run() { let numbers = { const BYTES_PER_GB: usize = 1024 * 1024 * 1024; // 4 bytes per f32 let elements = (BYTES_PER_GB as f32 / 4.0) as usize; vec![0.0; elements] }; assert!(numbers.iter().all(|n| *n == 0.0)); log::info!("All 0.0s"); let t1 = std::time::Instant::now(); let results = execute_gpu(&numbers).await; log::info!("GPU RUNTIME: {}ms", t1.elapsed().as_millis()); assert_eq!(numbers.len(), results.len()); assert!(results.iter().all(|n| *n == 1.0)); log::info!("All 1.0s"); } pub fn main() { #[cfg(not(target_arch = "wasm32"))] { env_logger::init(); pollster::block_on(run()); } } #[cfg(test)] #[cfg(not(target_arch = "wasm32"))] pub mod tests; ================================================ FILE: examples/features/src/big_compute_buffers/shader.wgsl ================================================ const OFFSET: u32 = 1u << 8u; const BUFFER_MAX_ELEMENTS: u32 = 1u << 25u; // Think `buffer.len()` const NUM_BUFFERS: u32 = 8u; const TOTAL_SIZE: u32 = BUFFER_MAX_ELEMENTS * NUM_BUFFERS; // `binding_array` requires a custom struct struct ContiguousArray { inner: array } @group(0) @binding(0) var storage_array: binding_array; @compute @workgroup_size(256, 1, 1) fn main(@builtin(global_invocation_id) global_id: vec3) { let base_index = global_id.x * OFFSET; for (var i = 0u; i < OFFSET; i++) { let index = base_index + i; if index < TOTAL_SIZE { let buffer_index = index / BUFFER_MAX_ELEMENTS; let inner_index = index % BUFFER_MAX_ELEMENTS; storage_array[buffer_index].inner[inner_index] = add_one(storage_array[buffer_index].inner[inner_index]); } } } fn add_one(n: f32) -> f32 { return n + 1.0; } ================================================ FILE: examples/features/src/big_compute_buffers/tests.rs ================================================ use super::*; use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters}; #[gpu_test] pub static TWO_BUFFERS: GpuTestConfiguration = GpuTestConfiguration::new() .parameters( TestParameters::default() .features( Features::BUFFER_BINDING_ARRAY | Features::STORAGE_RESOURCE_BINDING_ARRAY | Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, ) .instance_flags(wgpu::InstanceFlags::GPU_BASED_VALIDATION) .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) .limits(wgpu::Limits { max_buffer_size: MAX_BUFFER_SIZE, max_binding_array_elements_per_shader_stage: 8, ..Default::default() }) // https://github.com/gfx-rs/wgpu/issues/9184 .expect_fail( wgpu_test::FailureCase::molten_vk() .validation_error("Shader library compile failed") .validation_error("could not be compiled into pipeline"), ), ) .run_async(|ctx| { // The test environment's GPU reports 134MB as the max storage buffer size.https://github.com/gfx-rs/wgpu/actions/runs/11001397782/job/30546188996#step:12:1096 const SIZE: usize = (1 << 27) / std::mem::size_of::() * 8; // 2 Buffers worth, of 0.0s. let input = &[0.0; SIZE]; async move { assert_execute_gpu(&ctx.device, &ctx.queue, input).await } }); async fn assert_execute_gpu(device: &wgpu::Device, queue: &wgpu::Queue, input: &[f32]) { let expected_len = input.len(); let produced = execute_gpu_inner(device, queue, input).await; assert_eq!(produced.len(), expected_len); assert!(produced.into_iter().all(|v| v == 1.0)); } ================================================ FILE: examples/features/src/boids/README.md ================================================ # boids Flocking boids example with gpu compute update pass ## To Run ``` cargo run --bin wgpu-examples boids ``` ## Screenshots ![Boids example](./screenshot.png) ================================================ FILE: examples/features/src/boids/compute.wgsl ================================================ struct Particle { pos : vec2, vel : vec2, }; struct SimParams { deltaT : f32, rule1Distance : f32, rule2Distance : f32, rule3Distance : f32, rule1Scale : f32, rule2Scale : f32, rule3Scale : f32, }; @group(0) @binding(0) var params : SimParams; @group(0) @binding(1) var particlesSrc : array; @group(0) @binding(2) var particlesDst : array; // https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp @compute @workgroup_size(64) fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { let total = arrayLength(&particlesSrc); let index = global_invocation_id.x; if (index >= total) { return; } var vPos : vec2 = particlesSrc[index].pos; var vVel : vec2 = particlesSrc[index].vel; var cMass : vec2 = vec2(0.0, 0.0); var cVel : vec2 = vec2(0.0, 0.0); var colVel : vec2 = vec2(0.0, 0.0); var cMassCount : i32 = 0; var cVelCount : i32 = 0; var i : u32 = 0u; loop { if (i >= total) { break; } if (i == index) { continue; } let pos = particlesSrc[i].pos; let vel = particlesSrc[i].vel; if (distance(pos, vPos) < params.rule1Distance) { cMass += pos; cMassCount += 1; } if (distance(pos, vPos) < params.rule2Distance) { colVel -= pos - vPos; } if (distance(pos, vPos) < params.rule3Distance) { cVel += vel; cVelCount += 1; } continuing { i = i + 1u; } } if (cMassCount > 0) { cMass = cMass * (1.0 / f32(cMassCount)) - vPos; } if (cVelCount > 0) { cVel *= 1.0 / f32(cVelCount); } vVel = vVel + (cMass * params.rule1Scale) + (colVel * params.rule2Scale) + (cVel * params.rule3Scale); // clamp velocity for a more pleasing simulation vVel = normalize(vVel) * clamp(length(vVel), 0.0, 0.1); // kinematic update vPos += vVel * params.deltaT; // Wrap around boundary if (vPos.x < -1.0) { vPos.x = 1.0; } if (vPos.x > 1.0) { vPos.x = -1.0; } if (vPos.y < -1.0) { vPos.y = 1.0; } if (vPos.y > 1.0) { vPos.y = -1.0; } // Write back particlesDst[index] = Particle(vPos, vVel); } ================================================ FILE: examples/features/src/boids/draw.wgsl ================================================ @vertex fn main_vs( @location(0) particle_pos: vec2, @location(1) particle_vel: vec2, @location(2) position: vec2, ) -> @builtin(position) vec4 { let angle = -atan2(particle_vel.x, particle_vel.y); let pos = vec2( position.x * cos(angle) - position.y * sin(angle), position.x * sin(angle) + position.y * cos(angle) ); return vec4(pos + particle_pos, 0.0, 1.0); } @fragment fn main_fs() -> @location(0) vec4 { return vec4(1.0, 1.0, 1.0, 1.0); } ================================================ FILE: examples/features/src/boids/mod.rs ================================================ // Flocking boids example with gpu compute update pass // adapted from https://github.com/austinEng/webgpu-samples/blob/master/src/examples/computeBoids.ts use nanorand::{Rng, WyRand}; use wgpu::util::DeviceExt; // number of boid particles to simulate const NUM_PARTICLES: u32 = 1500; // number of single-particle calculations (invocations) in each gpu work group const PARTICLES_PER_GROUP: u32 = 64; /// Example struct holds references to wgpu resources and frame persistent data struct Example { particle_bind_groups: Vec, particle_buffers: Vec, vertices_buffer: wgpu::Buffer, compute_pipeline: wgpu::ComputePipeline, render_pipeline: wgpu::RenderPipeline, work_group_count: u32, frame_num: usize, } impl crate::framework::Example for Example { fn required_limits() -> wgpu::Limits { wgpu::Limits::downlevel_defaults() } fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { wgpu::DownlevelCapabilities { flags: wgpu::DownlevelFlags::COMPUTE_SHADERS, ..Default::default() } } /// constructs initial instance of Example struct fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { let compute_shader = device.create_shader_module(wgpu::include_wgsl!("compute.wgsl")); let draw_shader = device.create_shader_module(wgpu::include_wgsl!("draw.wgsl")); // buffer for simulation parameters uniform let sim_param_data = [ 0.04f32, // deltaT 0.1, // rule1Distance 0.025, // rule2Distance 0.025, // rule3Distance 0.02, // rule1Scale 0.05, // rule2Scale 0.005, // rule3Scale ] .to_vec(); let sim_param_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Simulation Parameter Buffer"), contents: bytemuck::cast_slice(&sim_param_data), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); // create compute bind layout group and compute pipeline layout let compute_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new( (sim_param_data.len() * size_of::()) as _, ), }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true }, has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new((NUM_PARTICLES * 16) as _), }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new((NUM_PARTICLES * 16) as _), }, count: None, }, ], label: None, }); let compute_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("compute"), bind_group_layouts: &[Some(&compute_bind_group_layout)], immediate_size: 0, }); // create render pipeline with empty bind group layout let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("render"), bind_group_layouts: &[], immediate_size: 0, }); let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&render_pipeline_layout), vertex: wgpu::VertexState { module: &draw_shader, entry_point: Some("main_vs"), compilation_options: Default::default(), buffers: &[ wgpu::VertexBufferLayout { array_stride: 4 * 4, step_mode: wgpu::VertexStepMode::Instance, attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2], }, wgpu::VertexBufferLayout { array_stride: 2 * 4, step_mode: wgpu::VertexStepMode::Vertex, attributes: &wgpu::vertex_attr_array![2 => Float32x2], }, ], }, fragment: Some(wgpu::FragmentState { module: &draw_shader, entry_point: Some("main_fs"), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); // create compute pipeline let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some("Compute pipeline"), layout: Some(&compute_pipeline_layout), module: &compute_shader, entry_point: Some("main"), compilation_options: Default::default(), cache: None, }); // buffer for the three 2d triangle vertices of each instance let vertex_buffer_data = [-0.01f32, -0.02, 0.01, -0.02, 0.00, 0.02]; let vertices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::bytes_of(&vertex_buffer_data), usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, }); // buffer for all particles data of type [(posx,posy,velx,vely),...] let mut initial_particle_data = vec![0.0f32; (4 * NUM_PARTICLES) as usize]; let mut rng = WyRand::new_seed(42); let mut unif = || rng.generate::() * 2f32 - 1f32; // Generate a num (-1, 1) for particle_instance_chunk in initial_particle_data.chunks_mut(4) { particle_instance_chunk[0] = unif(); // posx particle_instance_chunk[1] = unif(); // posy particle_instance_chunk[2] = unif() * 0.1; // velx particle_instance_chunk[3] = unif() * 0.1; // vely } // creates two buffers of particle data each of size NUM_PARTICLES // the two buffers alternate as dst and src for each frame let mut particle_buffers = Vec::::new(); let mut particle_bind_groups = Vec::::new(); for i in 0..2 { particle_buffers.push( device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(&format!("Particle Buffer {i}")), contents: bytemuck::cast_slice(&initial_particle_data), usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, }), ); } // create two bind groups, one for each buffer as the src // where the alternate buffer is used as the dst for i in 0..2 { particle_bind_groups.push(device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &compute_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: sim_param_buffer.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: particle_buffers[i].as_entire_binding(), }, wgpu::BindGroupEntry { binding: 2, resource: particle_buffers[(i + 1) % 2].as_entire_binding(), // bind to opposite buffer }, ], label: None, })); } // calculates number of work groups from PARTICLES_PER_GROUP constant let work_group_count = NUM_PARTICLES.div_ceil(PARTICLES_PER_GROUP); // returns Example struct and No encoder commands Example { particle_bind_groups, particle_buffers, vertices_buffer, compute_pipeline, render_pipeline, work_group_count, frame_num: 0, } } /// update is called for any WindowEvent not handled by the framework fn update(&mut self, _event: winit::event::WindowEvent) { //empty } /// resize is called on WindowEvent::Resized events fn resize( &mut self, _sc_desc: &wgpu::SurfaceConfiguration, _device: &wgpu::Device, _queue: &wgpu::Queue, ) { //empty } /// render is called each frame, dispatching compute groups proportional /// a TriangleList draw call for all NUM_PARTICLES at 3 vertices each fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { // create render pass descriptor and its color attachments let color_attachments = [Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store, }, })]; let render_pass_descriptor = wgpu::RenderPassDescriptor { label: None, color_attachments: &color_attachments, depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }; // get command encoder let mut command_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); command_encoder.push_debug_group("compute boid movement"); { // compute pass let mut cpass = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); cpass.set_pipeline(&self.compute_pipeline); cpass.set_bind_group(0, &self.particle_bind_groups[self.frame_num % 2], &[]); cpass.dispatch_workgroups(self.work_group_count, 1, 1); } command_encoder.pop_debug_group(); command_encoder.push_debug_group("render boids"); { // render pass let mut rpass = command_encoder.begin_render_pass(&render_pass_descriptor); rpass.set_pipeline(&self.render_pipeline); // render dst particles rpass.set_vertex_buffer(0, self.particle_buffers[(self.frame_num + 1) % 2].slice(..)); // the three instance-local vertices rpass.set_vertex_buffer(1, self.vertices_buffer.slice(..)); rpass.draw(0..3, 0..NUM_PARTICLES); } command_encoder.pop_debug_group(); // update frame count self.frame_num += 1; // done queue.submit(Some(command_encoder.finish())); } } /// run example pub fn main() { crate::framework::run::("boids"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "boids", // Generated on 1080ti on Vk/Windows image_path: "/examples/features/src/boids/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default() .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) .limits(wgpu::Limits::downlevel_defaults()), comparisons: &[wgpu_test::ComparisonType::Mean(0.005)], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/bunnymark/README.md ================================================ # bunnymark ## To Run ``` cargo run --bin wgpu-examples bunnymark ``` ## Example output ![Example output](./screenshot.png) ================================================ FILE: examples/features/src/bunnymark/mod.rs ================================================ use bytemuck::{Pod, Zeroable}; use nanorand::{Rng, WyRand}; use std::borrow::Cow; use wgpu::util::DeviceExt; use winit::{ event::{ElementState, KeyEvent}, keyboard::{Key, NamedKey}, }; const MAX_BUNNIES: usize = 1 << 20; const BUNNY_SIZE: f32 = 0.15 * 256.0; const GRAVITY: f32 = -9.8 * 100.0; const MAX_VELOCITY: f32 = 750.0; #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Globals { mvp: [[f32; 4]; 4], size: [f32; 2], pad: [f32; 2], } #[repr(C, align(256))] #[derive(Clone, Copy, Pod, Zeroable)] struct Bunny { position: [f32; 2], velocity: [f32; 2], color: u32, _pad: [u32; (256 - 20) / 4], } impl Bunny { fn update_data(&mut self, delta: f32, extent: &[u32; 2]) { self.position[0] += self.velocity[0] * delta; self.position[1] += self.velocity[1] * delta; self.velocity[1] += GRAVITY * delta; if (self.velocity[0] > 0.0 && self.position[0] + 0.5 * BUNNY_SIZE > extent[0] as f32) || (self.velocity[0] < 0.0 && self.position[0] - 0.5 * BUNNY_SIZE < 0.0) { self.velocity[0] *= -1.0; } if self.velocity[1] < 0.0 && self.position[1] < 0.5 * BUNNY_SIZE { self.velocity[1] *= -1.0; } // Top boundary check if self.velocity[1] > 0.0 && self.position[1] + 0.5 * BUNNY_SIZE > extent[1] as f32 { self.velocity[1] *= -1.0; } } } /// Example struct holds references to wgpu resources and frame persistent data struct Example { view: wgpu::TextureView, sampler: wgpu::Sampler, global_bind_group_layout: wgpu::BindGroupLayout, global_group: wgpu::BindGroup, local_group: wgpu::BindGroup, pipeline: wgpu::RenderPipeline, bunnies: Vec, local_buffer: wgpu::Buffer, extent: [u32; 2], rng: WyRand, } impl Example { fn spawn_bunnies(&mut self) { let spawn_count = 64; let color = self.rng.generate::(); println!( "Spawning {} bunnies, total at {}", spawn_count, self.bunnies.len() + spawn_count ); for _ in 0..spawn_count { let speed = self.rng.generate::() * MAX_VELOCITY - (MAX_VELOCITY * 0.5); self.bunnies.push(Bunny { position: [0.0, 0.5 * (self.extent[1] as f32)], velocity: [speed, 0.0], color, _pad: Zeroable::zeroed(), }); } } fn render_inner( &mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue, ) { let delta = 0.01; for bunny in self.bunnies.iter_mut() { bunny.update_data(delta, &self.extent); } let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment; queue.write_buffer(&self.local_buffer, 0, bytemuck::cast_slice(&self.bunnies)); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); { let clear_color = wgpu::Color { r: 0.1, g: 0.2, b: 0.3, a: 1.0, }; let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(clear_color), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, &self.global_group, &[]); for i in 0..self.bunnies.len() { let offset = (i as wgpu::DynamicOffset) * (uniform_alignment as wgpu::DynamicOffset); rpass.set_bind_group(1, &self.local_group, &[offset]); rpass.draw(0..4, 0..1); } } queue.submit(Some(encoder.finish())); } } impl crate::framework::Example for Example { fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!( "../../../../wgpu-hal/examples/halmark/shader.wgsl" ))), }); let global_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new(size_of::() as _), }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, ], label: None, }); let local_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: wgpu::BufferSize::new(size_of::() as _), }, count: None, }], label: None, }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[ Some(&global_bind_group_layout), Some(&local_bind_group_layout), ], immediate_size: 0, }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(wgpu::ColorTargetState { format: config.view_formats[0], blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::default(), })], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleStrip, strip_index_format: Some(wgpu::IndexFormat::Uint16), ..wgpu::PrimitiveState::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let texture = { let img_data = include_bytes!("../../../../logo.png"); let decoder = png::Decoder::new(std::io::Cursor::new(img_data)); let mut reader = decoder.read_info().unwrap(); let buf_len = reader .output_buffer_size() .expect("output buffer would not fit in memory"); let mut buf = vec![0; buf_len]; let info = reader.next_frame(&mut buf).unwrap(); let size = wgpu::Extent3d { width: info.width, height: info.height, depth_or_array_layers: 1, }; let texture = device.create_texture(&wgpu::TextureDescriptor { label: None, size, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); queue.write_texture( texture.as_image_copy(), &buf, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(info.width * 4), rows_per_image: None, }, size, ); texture }; let sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: None, address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::MipmapFilterMode::Nearest, ..Default::default() }); let globals = Globals { mvp: glam::Mat4::orthographic_rh( 0.0, config.width as f32, 0.0, config.height as f32, -1.0, 1.0, ) .to_cols_array_2d(), size: [BUNNY_SIZE; 2], pad: [0.0; 2], }; let global_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("global"), contents: bytemuck::bytes_of(&globals), usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, }); let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment as wgpu::BufferAddress; let local_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("local"), size: (MAX_BUNNIES as wgpu::BufferAddress) * uniform_alignment, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, mapped_at_creation: false, }); let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); let global_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &global_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: global_buffer.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&view), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&sampler), }, ], label: None, }); let local_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &local_bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &local_buffer, offset: 0, size: wgpu::BufferSize::new(size_of::() as _), }), }], label: None, }); let rng = WyRand::new_seed(42); let mut ex = Example { view, sampler, global_bind_group_layout, pipeline, global_group, local_group, bunnies: Vec::new(), local_buffer, extent: [config.width, config.height], rng, }; ex.spawn_bunnies(); ex } fn update(&mut self, event: winit::event::WindowEvent) { if let winit::event::WindowEvent::KeyboardInput { event: KeyEvent { logical_key: Key::Named(NamedKey::Space), state: ElementState::Pressed, .. }, .. } = event { self.spawn_bunnies(); } } fn resize( &mut self, sc_desc: &wgpu::SurfaceConfiguration, device: &wgpu::Device, _queue: &wgpu::Queue, ) { self.extent = [sc_desc.width, sc_desc.height]; let globals = Globals { mvp: glam::Mat4::orthographic_rh( 0.0, sc_desc.width as f32, 0.0, sc_desc.height as f32, -1.0, 1.0, ) .to_cols_array_2d(), size: [BUNNY_SIZE; 2], pad: [0.0; 2], }; let global_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("global"), contents: bytemuck::bytes_of(&globals), usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, }); let global_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &self.global_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: global_buffer.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&self.view), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.sampler), }, ], label: None, }); self.global_group = global_group; } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { self.render_inner(view, device, queue); } } pub fn main() { crate::framework::run::("bunnymark"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "bunnymark", image_path: "/examples/features/src/bunnymark/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default(), // We're looking for very small differences, so look in the high percentiles. comparisons: &[ wgpu_test::ComparisonType::Mean(0.05), wgpu_test::ComparisonType::Percentile { percentile: 0.99, threshold: 0.37, }, ], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/conservative_raster/README.md ================================================ # conservative_raster This example shows how to render with conservative rasterization (native extension with limited support). When enabled, any pixel touched by a triangle primitive is rasterized. This is useful for various advanced techniques, most prominently for implementing realtime voxelization. The demonstration here is implemented by rendering a triangle to a low-resolution target and then upscaling it with nearest-neighbor filtering. The outlines of the triangle are then rendered in the original solution, using the same vertex shader as the triangle. Pixels only drawn with conservative rasterization enabled are depicted red. ## To Run ``` cargo run --bin wgpu-examples conservative_raster ``` ## Screenshots ![Conservative-raster window](./screenshot.png) ================================================ FILE: examples/features/src/conservative_raster/mod.rs ================================================ const RENDER_TARGET_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb; struct Example { low_res_target: wgpu::TextureView, bind_group_upscale: wgpu::BindGroup, pipeline_triangle_conservative: wgpu::RenderPipeline, pipeline_triangle_regular: wgpu::RenderPipeline, pipeline_upscale: wgpu::RenderPipeline, pipeline_lines: Option, bind_group_layout_upscale: wgpu::BindGroupLayout, } impl Example { fn create_low_res_target( config: &wgpu::SurfaceConfiguration, device: &wgpu::Device, bind_group_layout_upscale: &wgpu::BindGroupLayout, ) -> (wgpu::TextureView, wgpu::BindGroup) { let texture_view = device .create_texture(&wgpu::TextureDescriptor { label: Some("Low Resolution Target"), size: wgpu::Extent3d { width: (config.width / 16).max(1), height: (config.height / 16).max(1), depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: RENDER_TARGET_FORMAT, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }) .create_view(&Default::default()); let sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("Nearest Neighbor Sampler"), mag_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest, ..Default::default() }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("upscale bind group"), layout: bind_group_layout_upscale, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&texture_view), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler), }, ], }); (texture_view, bind_group) } } impl crate::framework::Example for Example { fn required_features() -> wgpu::Features { wgpu::Features::CONSERVATIVE_RASTERIZATION } fn optional_features() -> wgpu::Features { wgpu::Features::POLYGON_MODE_LINE } fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { let pipeline_layout_empty = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[], immediate_size: 0, }); let shader_triangle_and_lines = device.create_shader_module(wgpu::include_wgsl!("triangle_and_lines.wgsl")); let pipeline_triangle_conservative = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Conservative Rasterization"), layout: Some(&pipeline_layout_empty), vertex: wgpu::VertexState { module: &shader_triangle_and_lines, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader_triangle_and_lines, entry_point: Some("fs_main_red"), compilation_options: Default::default(), targets: &[Some(RENDER_TARGET_FORMAT.into())], }), primitive: wgpu::PrimitiveState { conservative: true, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let pipeline_triangle_regular = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Regular Rasterization"), layout: Some(&pipeline_layout_empty), vertex: wgpu::VertexState { module: &shader_triangle_and_lines, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader_triangle_and_lines, entry_point: Some("fs_main_blue"), compilation_options: Default::default(), targets: &[Some(RENDER_TARGET_FORMAT.into())], }), primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let pipeline_lines = if device .features() .contains(wgpu::Features::POLYGON_MODE_LINE) { Some( device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Lines"), layout: Some(&pipeline_layout_empty), vertex: wgpu::VertexState { module: &shader_triangle_and_lines, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader_triangle_and_lines, entry_point: Some("fs_main_white"), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), primitive: wgpu::PrimitiveState { polygon_mode: wgpu::PolygonMode::Line, topology: wgpu::PrimitiveTopology::LineStrip, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }), ) } else { None }; let (pipeline_upscale, bind_group_layout_upscale) = { let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("upscale bindgroup"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: false }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), count: None, }, ], }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); let shader = device.create_shader_module(wgpu::include_wgsl!("upscale.wgsl")); ( device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Upscale"), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }), bind_group_layout, ) }; let (low_res_target, bind_group_upscale) = Self::create_low_res_target(config, device, &bind_group_layout_upscale); Self { low_res_target, bind_group_upscale, pipeline_triangle_conservative, pipeline_triangle_regular, pipeline_upscale, pipeline_lines, bind_group_layout_upscale, } } fn resize( &mut self, config: &wgpu::SurfaceConfiguration, device: &wgpu::Device, _queue: &wgpu::Queue, ) { let (low_res_target, bind_group_upscale) = Self::create_low_res_target(config, device, &self.bind_group_layout_upscale); self.low_res_target = low_res_target; self.bind_group_upscale = bind_group_upscale; } fn update(&mut self, _event: winit::event::WindowEvent) {} fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("primary"), }); { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("low resolution"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &self.low_res_target, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.pipeline_triangle_conservative); rpass.draw(0..3, 0..1); rpass.set_pipeline(&self.pipeline_triangle_regular); rpass.draw(0..3, 0..1); } { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("full resolution"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.pipeline_upscale); rpass.set_bind_group(0, &self.bind_group_upscale, &[]); rpass.draw(0..3, 0..1); if let Some(pipeline_lines) = &self.pipeline_lines { rpass.set_pipeline(pipeline_lines); rpass.draw(0..4, 0..1); } } queue.submit(Some(encoder.finish())); } } pub fn main() { crate::framework::run::("conservative-raster"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "conservative-raster", image_path: "/examples/features/src/conservative_raster/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[wgpu_test::ComparisonType::Mean(0.0)], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/conservative_raster/triangle_and_lines.wgsl ================================================ @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4 { let i: i32 = i32(vertex_index % 3u); let x: f32 = f32(i - 1) * 0.75; let y: f32 = f32((i & 1) * 2 - 1) * 0.75 + x * 0.2 + 0.1; return vec4(x, y, 0.0, 1.0); } @fragment fn fs_main_red() -> @location(0) vec4 { return vec4(1.0, 0.0, 0.0, 1.0); } @fragment fn fs_main_blue() -> @location(0) vec4 { return vec4(0.13, 0.31, 0.85, 1.0); // cornflower blue in linear space } @fragment fn fs_main_white() -> @location(0) vec4 { return vec4(1.0, 1.0, 1.0, 1.0); } ================================================ FILE: examples/features/src/conservative_raster/upscale.wgsl ================================================ struct VertexOutput { @builtin(position) position: vec4, @location(0) tex_coords: vec2, }; @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { let x: f32 = f32(i32(vertex_index & 1u) << 2u) - 1.0; let y: f32 = f32(i32(vertex_index & 2u) << 1u) - 1.0; var result: VertexOutput; result.position = vec4(x, -y, 0.0, 1.0); result.tex_coords = vec2(x + 1.0, y + 1.0) * 0.5; return result; } @group(0) @binding(0) var r_color: texture_2d; @group(0) @binding(1) var r_sampler: sampler; @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { return textureSample(r_color, r_sampler, vertex.tex_coords); } ================================================ FILE: examples/features/src/cooperative_matrix/README.md ================================================ # Cooperative Matrix Multiplication This example demonstrates how to use cooperative matrix operations (also known as tensor cores on NVIDIA GPUs) to perform efficient matrix multiplication on the GPU. For the full description of the cooperative matrix feature (supported configurations, WGSL types and operations, validation rules, and backend support), see the central API spec: - `docs/api-specs/cooperative_matrix.md` ## Example specifics This example computes `C = A * B + C` where: - A is a 64×64 matrix - B is a 64×64 matrix - C is a 64×64 matrix (accumulator/result) The example: - Tiles the 64×64 matrices into cooperative matrix tiles (e.g. 8×8) and performs a tiled matmul - Uses a compute shader and compares GPU results against a CPU reference implementation ## Requirements - A GPU and backend that expose `Features::EXPERIMENTAL_COOPERATIVE_MATRIX` - A configuration returned from `adapter.cooperative_matrix_properties()` that matches the tile size and element types used by this example - See `docs/api-specs/cooperative_matrix.md` for details on hardware / backend support ## Running ```bash cargo run --bin wgpu-examples -- cooperative_matrix ``` ## Notes - This is an experimental feature and may not work on all hardware - The shader uses the standard `create_shader_module` with full validation - Results are verified against a CPU reference implementation ================================================ FILE: examples/features/src/cooperative_matrix/mod.rs ================================================ //! Cooperative Matrix Multiplication Example //! //! This example demonstrates how to use cooperative matrix operations //! (also known as tensor cores on NVIDIA GPUs or simdgroup matrix //! operations on Apple GPUs) to perform efficient matrix multiplication. //! //! Cooperative matrices allow a workgroup to collectively load, store, //! and perform matrix operations on small tiles of data, enabling //! hardware-accelerated matrix math. //! //! Note: This feature requires hardware support and is currently //! experimental. Use `adapter.cooperative_matrix_properties()` to query //! supported configurations: //! - Metal (Apple): 8x8 f32, 8x8 f16, mixed precision (f16 inputs, f32 accumulator) //! - Vulkan (AMD): Typically 16x16 f16 //! - Vulkan (NVIDIA): Varies by GPU generation use bytemuck::{Pod, Zeroable}; use half::f16; /// Matrix dimensions for our example (must be divisible by tile size) const M: u32 = 64; // Rows of A and C const N: u32 = 64; // Cols of B and C const K: u32 = 64; // Cols of A, Rows of B #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Dimensions { m: u32, n: u32, k: u32, stride: u32, } async fn run() { // Initialize wgpu let instance = wgpu::Instance::default(); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, ..Default::default() }) .await .expect("Failed to find an appropriate adapter"); log::info!("Using adapter: {:?}", adapter.get_info()); // Query supported cooperative matrix configurations let coop_props = adapter.cooperative_matrix_properties(); if coop_props.is_empty() { log::error!( "Cooperative matrix is not supported on this adapter.\n\ This feature requires:\n\ - Metal: Apple7+ (A14/M1) with MSL 2.3+\n\ - Vulkan: VK_KHR_cooperative_matrix extension" ); return; } // Display supported configurations log::info!("Supported cooperative matrix configurations:"); for (i, prop) in coop_props.iter().enumerate() { log::info!( " [{}] {:?}x{:?}x{:?} - AB: {:?}, CR: {:?}{}", i, prop.m_size, prop.n_size, prop.k_size, prop.ab_type, prop.cr_type, if prop.saturating_accumulation { " (saturating)" } else { "" } ); } // Find a suitable configuration - prefer f32, but accept f16 // Try 16x16 first (AMD), then 8x8 (Apple Metal) let selected_config = coop_props .iter() .find(|prop| { prop.m_size == 16 && prop.n_size == 16 && prop.k_size == 16 && prop.ab_type == wgpu::CooperativeScalarType::F16 && prop.cr_type == wgpu::CooperativeScalarType::F16 }) .or_else(|| { coop_props.iter().find(|prop| { prop.m_size == 8 && prop.n_size == 8 && prop.k_size == 8 && prop.ab_type == wgpu::CooperativeScalarType::F32 && prop.cr_type == wgpu::CooperativeScalarType::F32 }) }); let config = match selected_config { Some(c) => { log::info!( "Selected configuration: {:?}x{:?}x{:?} AB={:?} CR={:?}", c.m_size, c.n_size, c.k_size, c.ab_type, c.cr_type ); c } None => { log::error!( "No suitable cooperative matrix configuration found.\n\ This example supports 16x16 f16 (AMD) or 8x8 f32 (Apple Metal).\n\ Available configurations are listed above." ); return; } }; let tile_size = config.m_size; let use_f16 = config.ab_type == wgpu::CooperativeScalarType::F16; log::info!( "Using {}x{} tiles with {} precision", tile_size, tile_size, if use_f16 { "f16" } else { "f32" } ); // Check if cooperative matrix is supported let adapter_features = adapter.features(); if !adapter_features.contains(wgpu::Features::EXPERIMENTAL_COOPERATIVE_MATRIX) { log::error!("EXPERIMENTAL_COOPERATIVE_MATRIX feature not available"); return; } // Check if f16 is needed and available if use_f16 && !adapter_features.contains(wgpu::Features::SHADER_F16) { log::error!("SHADER_F16 feature not available, but required for f16 cooperative matrices"); return; } // Build required features let mut required_features = wgpu::Features::EXPERIMENTAL_COOPERATIVE_MATRIX; if use_f16 { required_features |= wgpu::Features::SHADER_F16; } // Request device with experimental features enabled let (device, queue) = unsafe { adapter .request_device(&wgpu::DeviceDescriptor { label: Some("Cooperative Matrix Device"), required_features, required_limits: wgpu::Limits::downlevel_defaults(), experimental_features: wgpu::ExperimentalFeatures::enabled(), memory_hints: wgpu::MemoryHints::Performance, trace: wgpu::Trace::Off, }) .await .expect("Failed to create device") }; let results = execute(&device, &queue, config).await; log::info!( "Matrix multiplication {M}x{K}x{N} completed using {} precision!", if use_f16 { "f16" } else { "f32" } ); log::info!("Max error vs CPU reference: {:.6}", results.max_error); if results.max_error < results.tolerance { log::info!( "✓ Results match CPU reference within tolerance ({})", results.tolerance ); } else { log::warn!( "✗ Results differ from CPU reference (tolerance: {})", results.tolerance ); } // Print a small sample of the result log::info!("Sample of result matrix C (top-left 4x4):"); for i in 0..4 { let row: Vec = (0..4) .map(|j| format!("{:6.2}", results.matrix[i * N as usize + j])) .collect(); log::info!(" [{}]", row.join(", ")); } } struct ExecuteResults { max_error: f32, tolerance: f32, matrix: Vec, } async fn execute( device: &wgpu::Device, queue: &wgpu::Queue, config: &wgpu::CooperativeMatrixProperties, ) -> ExecuteResults { let use_f16 = config.ab_type == wgpu::CooperativeScalarType::F16; // Select the appropriate shader based on configuration let shader_source = if use_f16 { include_str!("shader_f16_16x16.wgsl") } else { include_str!("shader.wgsl") }; // Create the shader module using the standard validated path let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Cooperative Matrix Shader"), source: wgpu::ShaderSource::Wgsl(shader_source.into()), }); // Initialize matrices // A is MxK, B is KxN, C is MxN (result) // Use f32 for computation, convert to f16 if needed for GPU let matrix_a_f32: Vec = (0..M * K).map(|i| (i % 7) as f32 * 0.1).collect(); let matrix_b_f32: Vec = (0..K * N).map(|i| (i % 11) as f32 * 0.1).collect(); let matrix_c_f32: Vec = vec![0.0; (M * N) as usize]; // Element size depends on precision let element_size = if use_f16 { 2usize } else { 4usize }; let num_elements_a = (M * K) as usize; let num_elements_b = (K * N) as usize; let num_elements_c = (M * N) as usize; // Create buffers let buffer_a = device.create_buffer(&wgpu::BufferDescriptor { label: Some("Matrix A"), size: (num_elements_a * element_size) as u64, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let buffer_b = device.create_buffer(&wgpu::BufferDescriptor { label: Some("Matrix B"), size: (num_elements_b * element_size) as u64, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let buffer_c = device.create_buffer(&wgpu::BufferDescriptor { label: Some("Matrix C"), size: (num_elements_c * element_size) as u64, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: false, }); let dimensions = Dimensions { m: M, n: N, k: K, stride: N, }; let buffer_dims = device.create_buffer(&wgpu::BufferDescriptor { label: Some("Dimensions"), size: std::mem::size_of::() as u64, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("Staging Buffer"), size: (num_elements_c * element_size) as u64, usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); // Upload data (convert to f16 if needed) if use_f16 { let matrix_a_f16: Vec = matrix_a_f32.iter().map(|&x| f16::from_f32(x)).collect(); let matrix_b_f16: Vec = matrix_b_f32.iter().map(|&x| f16::from_f32(x)).collect(); let matrix_c_f16: Vec = matrix_c_f32.iter().map(|&x| f16::from_f32(x)).collect(); queue.write_buffer(&buffer_a, 0, bytemuck::cast_slice(&matrix_a_f16)); queue.write_buffer(&buffer_b, 0, bytemuck::cast_slice(&matrix_b_f16)); queue.write_buffer(&buffer_c, 0, bytemuck::cast_slice(&matrix_c_f16)); } else { queue.write_buffer(&buffer_a, 0, bytemuck::cast_slice(&matrix_a_f32)); queue.write_buffer(&buffer_b, 0, bytemuck::cast_slice(&matrix_b_f32)); queue.write_buffer(&buffer_c, 0, bytemuck::cast_slice(&matrix_c_f32)); } queue.write_buffer(&buffer_dims, 0, bytemuck::bytes_of(&dimensions)); // Create bind group layout and bind group let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Cooperative Matrix Bind Group Layout"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true }, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true }, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 3, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, ], }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("Cooperative Matrix Bind Group"), layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: buffer_a.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: buffer_b.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 2, resource: buffer_c.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 3, resource: buffer_dims.as_entire_binding(), }, ], }); // Create compute pipeline let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Cooperative Matrix Pipeline Layout"), bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some("Cooperative Matrix Pipeline"), layout: Some(&pipeline_layout), module: &shader, entry_point: Some("main"), compilation_options: Default::default(), cache: None, }); // Dispatch compute let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Cooperative Matrix Encoder"), }); { let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: Some("Cooperative Matrix Pass"), timestamp_writes: None, }); compute_pass.set_pipeline(&pipeline); compute_pass.set_bind_group(0, &bind_group, &[]); // Dispatch one workgroup per tile of the output compute_pass.dispatch_workgroups(M / config.m_size, N / config.m_size, 1); } // Copy result to staging buffer encoder.copy_buffer_to_buffer(&buffer_c, 0, &staging_buffer, 0, staging_buffer.size()); queue.submit(Some(encoder.finish())); // Read back results let buffer_slice = staging_buffer.slice(..); let (sender, receiver) = flume::bounded(1); buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap()); device .poll(wgpu::PollType::wait_indefinitely()) .expect("Poll failed"); receiver .recv_async() .await .expect("Channel receive failed") .expect("Buffer mapping failed"); let data = buffer_slice.get_mapped_range(); // Convert result back to f32 for comparison let result: Vec = if use_f16 { let result_f16: &[f16] = bytemuck::cast_slice(&data); result_f16.iter().map(|x| x.to_f32()).collect() } else { bytemuck::cast_slice::<_, f32>(&data).to_vec() }; // Compute reference result on CPU for verification let mut reference = vec![0.0f32; (M * N) as usize]; for i in 0..M { for j in 0..N { let mut sum = 0.0f32; for k in 0..K { sum += matrix_a_f32[(i * K + k) as usize] * matrix_b_f32[(k * N + j) as usize]; } reference[(i * N + j) as usize] = sum; } } // Verify results (use larger tolerance for f16) let tolerance = if use_f16 { 0.1 } else { 0.01 }; let mut max_error = 0.0f32; for i in 0..(M * N) as usize { let error = (result[i] - reference[i]).abs(); max_error = max_error.max(error); } ExecuteResults { max_error, tolerance, matrix: result, } } pub fn main() { #[cfg(not(target_arch = "wasm32"))] { env_logger::builder() .filter_level(log::LevelFilter::Info) .format_timestamp_nanos() .init(); pollster::block_on(run()); } #[cfg(target_arch = "wasm32")] { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); console_log::init_with_level(log::Level::Info).expect("could not initialize logger"); crate::utils::add_web_nothing_to_see_msg(); wasm_bindgen_futures::spawn_local(run()); } } #[cfg(test)] pub mod tests; ================================================ FILE: examples/features/src/cooperative_matrix/shader.wgsl ================================================ // Cooperative Matrix Multiplication Example // // This shader demonstrates using cooperative matrix operations to perform // matrix multiplication: C = A * B + C // // The matrices are stored in row-major order in storage buffers. // Each workgroup cooperatively loads tiles of A and B, multiplies them, // and accumulates the result into C. enable wgpu_cooperative_matrix; // Matrix dimensions (8x8 tiles) const TILE_SIZE: u32 = 8u; @group(0) @binding(0) var matrix_a: array; @group(0) @binding(1) var matrix_b: array; @group(0) @binding(2) var matrix_c: array; // Dimensions passed as uniforms: M, N, K for C[M,N] = A[M,K] * B[K,N] @group(0) @binding(3) var dimensions: vec4; // x=M, y=N, z=K, w=stride @compute @workgroup_size(8, 8, 1) fn main(@builtin(workgroup_id) workgroup_id: vec3) { let M = dimensions.x; let N = dimensions.y; let K = dimensions.z; let stride = dimensions.w; // Each workgroup handles one 8x8 tile of the output matrix C let tile_row = workgroup_id.x * TILE_SIZE; let tile_col = workgroup_id.y * TILE_SIZE; // Load the C tile (accumulator) let c_offset = tile_row * stride + tile_col; var c_tile = coopLoad>(&matrix_c[c_offset], stride); // Iterate over K dimension in tiles for (var k: u32 = 0u; k < K; k += TILE_SIZE) { // Load A tile: rows [tile_row, tile_row+8), cols [k, k+8) let a_offset = tile_row * K + k; let a_tile = coopLoad>(&matrix_a[a_offset], K); // Load B tile: rows [k, k+8), cols [tile_col, tile_col+8) let b_offset = k * stride + tile_col; let b_tile = coopLoad>(&matrix_b[b_offset], stride); // Multiply and accumulate: C += A * B c_tile = coopMultiplyAdd(a_tile, b_tile, c_tile); } // Store the result back to C coopStore(c_tile, &matrix_c[c_offset], stride); } ================================================ FILE: examples/features/src/cooperative_matrix/shader_f16_16x16.wgsl ================================================ // Cooperative Matrix Multiplication Example (16x16 f16 variant) // // This shader demonstrates using cooperative matrix operations to perform // matrix multiplication: C = A * B + C // // This variant uses 16x16 tiles with f16 precision, which is supported // on AMD GPUs via VK_KHR_cooperative_matrix. // // The matrices are stored in row-major order in storage buffers. // Each workgroup cooperatively loads tiles of A and B, multiplies them, // and accumulates the result into C. enable f16; enable wgpu_cooperative_matrix; // Matrix dimensions (16x16 tiles) const TILE_SIZE: u32 = 16u; @group(0) @binding(0) var matrix_a: array; @group(0) @binding(1) var matrix_b: array; @group(0) @binding(2) var matrix_c: array; // Dimensions passed as uniforms: M, N, K for C[M,N] = A[M,K] * B[K,N] @group(0) @binding(3) var dimensions: vec4; // x=M, y=N, z=K, w=stride // Workgroup size must be a multiple of subgroup size (64 on AMD) // Using 64x1x1 ensures compatibility while still processing 16x16 tiles @compute @workgroup_size(64, 1, 1) fn main(@builtin(workgroup_id) workgroup_id: vec3) { let M = dimensions.x; let N = dimensions.y; let K = dimensions.z; let stride = dimensions.w; // Each workgroup handles one 16x16 tile of the output matrix C let tile_row = workgroup_id.x * TILE_SIZE; let tile_col = workgroup_id.y * TILE_SIZE; // Load the C tile (accumulator) let c_offset = tile_row * stride + tile_col; var c_tile = coopLoad>(&matrix_c[c_offset], stride); // Iterate over K dimension in tiles for (var k: u32 = 0u; k < K; k += TILE_SIZE) { // Load A tile: rows [tile_row, tile_row+16), cols [k, k+16) let a_offset = tile_row * K + k; let a_tile = coopLoad>(&matrix_a[a_offset], K); // Load B tile: rows [k, k+16), cols [tile_col, tile_col+16) let b_offset = k * stride + tile_col; let b_tile = coopLoad>(&matrix_b[b_offset], stride); // Multiply and accumulate: C += A * B c_tile = coopMultiplyAdd(a_tile, b_tile, c_tile); } // Store the result back to C coopStore(c_tile, &matrix_c[c_offset], stride); } ================================================ FILE: examples/features/src/cooperative_matrix/tests.rs ================================================ use super::*; use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters}; #[gpu_test] pub static COOPERATIVE_MATRIX: GpuTestConfiguration = GpuTestConfiguration::new() .parameters( TestParameters::default() .features(wgpu::Features::EXPERIMENTAL_COOPERATIVE_MATRIX) .limits(wgpu::Limits::default()), ) .run_async(|ctx| async move { let coop_props = ctx.adapter.cooperative_matrix_properties(); let config = coop_props .iter() .find(|prop| { prop.m_size == 16 && prop.n_size == 16 && prop.k_size == 16 && prop.ab_type == wgpu::CooperativeScalarType::F16 && prop.cr_type == wgpu::CooperativeScalarType::F16 }) .or_else(|| { coop_props.iter().find(|prop| { prop.m_size == 8 && prop.n_size == 8 && prop.k_size == 8 && prop.ab_type == wgpu::CooperativeScalarType::F32 && prop.cr_type == wgpu::CooperativeScalarType::F32 }) }) .unwrap(); let ExecuteResults { max_error, tolerance, matrix: _, } = execute(&ctx.device, &ctx.queue, config).await; assert!(max_error < tolerance); }); ================================================ FILE: examples/features/src/cube/README.md ================================================ # cube This example renders a textured cube. ## To Run ``` cargo run --bin wgpu-examples cube ``` ## Screenshots ![Cube example](./screenshot.png) ================================================ FILE: examples/features/src/cube/mod.rs ================================================ use bytemuck::{Pod, Zeroable}; use std::f32::consts; use wgpu::util::DeviceExt; #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Vertex { _pos: [f32; 4], _tex_coord: [f32; 2], } fn vertex(pos: [i8; 3], tc: [i8; 2]) -> Vertex { Vertex { _pos: [pos[0] as f32, pos[1] as f32, pos[2] as f32, 1.0], _tex_coord: [tc[0] as f32, tc[1] as f32], } } fn create_vertices() -> (Vec, Vec) { let vertex_data = [ // top (0, 0, 1) vertex([-1, -1, 1], [0, 0]), vertex([1, -1, 1], [1, 0]), vertex([1, 1, 1], [1, 1]), vertex([-1, 1, 1], [0, 1]), // bottom (0, 0, -1) vertex([-1, 1, -1], [1, 0]), vertex([1, 1, -1], [0, 0]), vertex([1, -1, -1], [0, 1]), vertex([-1, -1, -1], [1, 1]), // right (1, 0, 0) vertex([1, -1, -1], [0, 0]), vertex([1, 1, -1], [1, 0]), vertex([1, 1, 1], [1, 1]), vertex([1, -1, 1], [0, 1]), // left (-1, 0, 0) vertex([-1, -1, 1], [1, 0]), vertex([-1, 1, 1], [0, 0]), vertex([-1, 1, -1], [0, 1]), vertex([-1, -1, -1], [1, 1]), // front (0, 1, 0) vertex([1, 1, -1], [1, 0]), vertex([-1, 1, -1], [0, 0]), vertex([-1, 1, 1], [0, 1]), vertex([1, 1, 1], [1, 1]), // back (0, -1, 0) vertex([1, -1, 1], [0, 0]), vertex([-1, -1, 1], [1, 0]), vertex([-1, -1, -1], [1, 1]), vertex([1, -1, -1], [0, 1]), ]; let index_data: &[u16] = &[ 0, 1, 2, 2, 3, 0, // top 4, 5, 6, 6, 7, 4, // bottom 8, 9, 10, 10, 11, 8, // right 12, 13, 14, 14, 15, 12, // left 16, 17, 18, 18, 19, 16, // front 20, 21, 22, 22, 23, 20, // back ]; (vertex_data.to_vec(), index_data.to_vec()) } fn create_texels(size: usize) -> Vec { (0..size * size) .map(|id| { // get high five for recognizing this ;) let cx = 3.0 * (id % size) as f32 / (size - 1) as f32 - 2.0; let cy = 2.0 * (id / size) as f32 / (size - 1) as f32 - 1.0; let (mut x, mut y, mut count) = (cx, cy, 0); while count < 0xFF && x * x + y * y < 4.0 { let old_x = x; x = x * x - y * y + cx; y = 2.0 * old_x * y + cy; count += 1; } count }) .collect() } struct Example { vertex_buf: wgpu::Buffer, index_buf: wgpu::Buffer, index_count: usize, bind_group: wgpu::BindGroup, uniform_buf: wgpu::Buffer, pipeline: wgpu::RenderPipeline, pipeline_wire: Option, } impl Example { fn generate_matrix(aspect_ratio: f32) -> glam::Mat4 { let projection = glam::Mat4::perspective_rh(consts::FRAC_PI_4, aspect_ratio, 1.0, 10.0); let view = glam::Mat4::look_at_rh( glam::Vec3::new(1.5f32, -5.0, 3.0), glam::Vec3::ZERO, glam::Vec3::Z, ); projection * view } } impl crate::framework::Example for Example { fn optional_features() -> wgpu::Features { wgpu::Features::POLYGON_MODE_LINE } fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { // Create the vertex and index buffers let vertex_size = size_of::(); let (vertex_data, index_data) = create_vertices(); let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::cast_slice(&vertex_data), usage: wgpu::BufferUsages::VERTEX, }); let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Index Buffer"), contents: bytemuck::cast_slice(&index_data), usage: wgpu::BufferUsages::INDEX, }); // Create pipeline layout let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new(64), }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { multisampled: false, sample_type: wgpu::TextureSampleType::Uint, view_dimension: wgpu::TextureViewDimension::D2, }, count: None, }, ], }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); // Create the texture let size = 256u32; let texels = create_texels(size as usize); let texture_extent = wgpu::Extent3d { width: size, height: size, depth_or_array_layers: 1, }; let texture = device.create_texture(&wgpu::TextureDescriptor { label: None, size: texture_extent, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R8Uint, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, view_formats: &[], }); let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); queue.write_texture( texture.as_image_copy(), &texels, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(size), rows_per_image: None, }, texture_extent, ); // Create other resources let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32); let mx_ref: &[f32; 16] = mx_total.as_ref(); let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Uniform Buffer"), contents: bytemuck::cast_slice(mx_ref), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); // Create bind group let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: uniform_buf.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&texture_view), }, ], label: None, }); let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let vertex_buffers = [wgpu::VertexBufferLayout { array_stride: vertex_size as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &[ wgpu::VertexAttribute { format: wgpu::VertexFormat::Float32x4, offset: 0, shader_location: 0, }, wgpu::VertexAttribute { format: wgpu::VertexFormat::Float32x2, offset: 4 * 4, shader_location: 1, }, ], }]; let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &vertex_buffers, }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), primitive: wgpu::PrimitiveState { cull_mode: Some(wgpu::Face::Back), ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let pipeline_wire = if device .features() .contains(wgpu::Features::POLYGON_MODE_LINE) { let pipeline_wire = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &vertex_buffers, }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_wire"), compilation_options: Default::default(), targets: &[Some(wgpu::ColorTargetState { format: config.view_formats[0], blend: Some(wgpu::BlendState { color: wgpu::BlendComponent { operation: wgpu::BlendOperation::Add, src_factor: wgpu::BlendFactor::SrcAlpha, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, }, alpha: wgpu::BlendComponent::REPLACE, }), write_mask: wgpu::ColorWrites::ALL, })], }), primitive: wgpu::PrimitiveState { front_face: wgpu::FrontFace::Ccw, cull_mode: Some(wgpu::Face::Back), polygon_mode: wgpu::PolygonMode::Line, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); Some(pipeline_wire) } else { None }; // Done Example { vertex_buf, index_buf, index_count: index_data.len(), bind_group, uniform_buf, pipeline, pipeline_wire, } } fn update(&mut self, _event: winit::event::WindowEvent) { //empty } fn resize( &mut self, config: &wgpu::SurfaceConfiguration, _device: &wgpu::Device, queue: &wgpu::Queue, ) { let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32); let mx_ref: &[f32; 16] = mx_total.as_ref(); queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(mx_ref)); } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.1, g: 0.2, b: 0.3, a: 1.0, }), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.push_debug_group("Prepare data for draw."); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, &self.bind_group, &[]); rpass.set_index_buffer(self.index_buf.slice(..), wgpu::IndexFormat::Uint16); rpass.set_vertex_buffer(0, self.vertex_buf.slice(..)); rpass.pop_debug_group(); rpass.insert_debug_marker("Draw!"); rpass.draw_indexed(0..self.index_count as u32, 0, 0..1); if let Some(ref pipe) = self.pipeline_wire { rpass.set_pipeline(pipe); rpass.draw_indexed(0..self.index_count as u32, 0, 0..1); } } queue.submit(Some(encoder.finish())); } } pub fn main() { crate::framework::run::("cube"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "cube", // Generated on 1080ti on Vk/Windows image_path: "/examples/features/src/cube/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[ wgpu_test::ComparisonType::Mean(0.041), // Bounded by Apple A9 ], _phantom: std::marker::PhantomData::, }; #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST_LINES: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "cube-lines", // Generated on 1080ti on Vk/Windows image_path: "/examples/features/src/cube/screenshot-lines.png", width: 1024, height: 768, optional_features: wgpu::Features::POLYGON_MODE_LINE, base_test_parameters: wgpu_test::TestParameters::default(), // We're looking for tiny changes here, so we focus on a spike in the 95th percentile. comparisons: &[ wgpu_test::ComparisonType::Mean(0.05), // Bounded by Intel 630 on Vk/Windows wgpu_test::ComparisonType::Percentile { percentile: 0.95, threshold: 0.36, }, // Bounded by 1080ti on DX12 ], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/cube/shader.wgsl ================================================ struct VertexOutput { @location(0) tex_coord: vec2, @builtin(position) position: vec4, }; @group(0) @binding(0) var transform: mat4x4; @vertex fn vs_main( @location(0) position: vec4, @location(1) tex_coord: vec2, ) -> VertexOutput { var result: VertexOutput; result.tex_coord = tex_coord; result.position = transform * position; return result; } @group(0) @binding(1) var r_color: texture_2d; @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { let tex = textureLoad(r_color, vec2(vertex.tex_coord * 256.0), 0); let v = f32(tex.x) / 255.0; return vec4(1.0 - (v * 5.0), 1.0 - (v * 15.0), 1.0 - (v * 50.0), 1.0); } @fragment fn fs_wire(vertex: VertexOutput) -> @location(0) vec4 { return vec4(0.0, 0.5, 0.0, 0.5); } ================================================ FILE: examples/features/src/framework.rs ================================================ use std::future::Future; use std::sync::Arc; use wgpu::{Instance, Surface}; use winit::{ application::ApplicationHandler, dpi::PhysicalSize, event::{KeyEvent, WindowEvent}, event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy}, keyboard::{Key, NamedKey}, window::Window, }; pub trait Example: 'static + Sized { const SRGB: bool = true; fn optional_features() -> wgpu::Features { wgpu::Features::empty() } fn required_features() -> wgpu::Features { wgpu::Features::empty() } fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { wgpu::DownlevelCapabilities { flags: wgpu::DownlevelFlags::empty(), shader_model: wgpu::ShaderModel::Sm5, ..wgpu::DownlevelCapabilities::default() } } fn required_limits() -> wgpu::Limits { wgpu::Limits::downlevel_webgl2_defaults() // These downlevel limits will allow the code to run on all possible hardware } fn init( config: &wgpu::SurfaceConfiguration, adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self; fn resize( &mut self, config: &wgpu::SurfaceConfiguration, device: &wgpu::Device, queue: &wgpu::Queue, ); fn update(&mut self, event: WindowEvent); fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue); } // Initialize logging in platform dependant ways. fn init_logger() { cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { // As we don't have an environment to pull logging level from, we use the query string. let query_string = web_sys::window().unwrap().location().search().unwrap(); let query_level: Option = parse_url_query_string(&query_string, "RUST_LOG") .and_then(|x| x.parse().ok()); let base_level = query_level.unwrap_or(log::LevelFilter::Info); // On web, we use fern, as console_log doesn't have filtering on a per-module level. fern::Dispatch::new() .level(base_level) .chain(fern::Output::call(console_log::log)) .apply() .unwrap(); std::panic::set_hook(Box::new(console_error_panic_hook::hook)); } else { // parse_default_env will read the RUST_LOG environment variable and apply it on top // of these default filters. env_logger::builder() .filter_level(log::LevelFilter::Info) .parse_default_env() .init(); } } } /// Runs a future to completion. On native this blocks via pollster, on wasm this spawns /// a local task. This allows the same async wgpu initialization code to work on both platforms. #[cfg(not(target_arch = "wasm32"))] fn spawn(f: impl Future + 'static) { pollster::block_on(f); } /// Runs a future to completion. On native this blocks via pollster, on wasm this spawns /// a local task. This allows the same async wgpu initialization code to work on both platforms. #[cfg(target_arch = "wasm32")] fn spawn(f: impl Future + 'static) { wasm_bindgen_futures::spawn_local(f); } /// Wrapper type which manages the surface and surface configuration. /// /// As surface usage varies per platform, wrapping this up cleans up the event loop code. struct SurfaceWrapper { surface: Option>, config: Option, } impl SurfaceWrapper { /// Create a new surface wrapper with no surface or configuration. fn new() -> Self { Self { surface: None, config: None, } } /// Called after the instance is created, but before we request an adapter. /// /// On wasm, we need to create the surface here, as the WebGL backend needs /// a surface (and hence a canvas) to be present to create the adapter. /// /// We cannot unconditionally create a surface here, as Android requires /// us to wait until we receive the `Resumed` event to do so. fn pre_adapter(&mut self, instance: &Instance, window: Arc) { if cfg!(target_arch = "wasm32") { self.surface = Some(instance.create_surface(window).unwrap()); } } /// Called on resume to create (on native) and configure the surface. /// /// On all native platforms, this is where we create the surface. /// On wasm, the surface was already created in [`Self::pre_adapter`]. /// /// Additionally, we configure the surface based on the (now valid) window size. fn resume(&mut self, context: &ExampleContext, window: Arc, srgb: bool) { // Window size is only actually valid after we enter the event loop. let window_size = window.inner_size(); let width = window_size.width.max(1); let height = window_size.height.max(1); log::info!("Surface resume {window_size:?}"); // We didn't create the surface in pre_adapter, so we need to do so now. if !cfg!(target_arch = "wasm32") { self.surface = Some(context.instance.create_surface(window).unwrap()); } // From here on, self.surface should be Some. let surface = self.surface.as_ref().unwrap(); // Get the default configuration, let mut config = surface .get_default_config(&context.adapter, width, height) .expect("Surface isn't supported by the adapter."); if srgb { // Not all platforms (WebGPU) support sRGB swapchains, so we need to use view formats let view_format = config.format.add_srgb_suffix(); config.view_formats.push(view_format); } else { // All platforms support non-sRGB swapchains, so we can just use the format directly. let format = config.format.remove_srgb_suffix(); config.format = format; config.view_formats.push(format); }; config.desired_maximum_frame_latency = 3; surface.configure(&context.device, &config); self.config = Some(config); } /// Resize the surface, making sure to not resize to zero. fn resize(&mut self, context: &ExampleContext, size: PhysicalSize) { log::info!("Surface resize {size:?}"); let config = self.config.as_mut().unwrap(); config.width = size.width.max(1); config.height = size.height.max(1); let surface = self.surface.as_ref().unwrap(); surface.configure(&context.device, config); } /// Acquire the next surface texture. /// /// Returns `None` on failure. fn acquire( &mut self, context: &ExampleContext, window: Arc, ) -> Option { use wgpu::CurrentSurfaceTexture; let surface = self.surface.as_ref().unwrap(); match surface.get_current_texture() { CurrentSurfaceTexture::Success(frame) => Some(frame), // If we timed out or the window is occluded, skip this frame: CurrentSurfaceTexture::Timeout | CurrentSurfaceTexture::Occluded => None, // If the surface is outdated or suboptimal, reconfigure and retry. CurrentSurfaceTexture::Suboptimal(_) | CurrentSurfaceTexture::Outdated => { surface.configure(&context.device, self.config()); match surface.get_current_texture() { CurrentSurfaceTexture::Success(frame) | CurrentSurfaceTexture::Suboptimal(frame) => Some(frame), other => panic!("Failed to acquire next surface texture: {other:?}"), } } CurrentSurfaceTexture::Validation => { unreachable!("No error scope registered, so validation errors will panic") } // If the surface is lost, recreate and reconfigure it. CurrentSurfaceTexture::Lost => { self.surface = Some(context.instance.create_surface(window).unwrap()); self.surface .as_ref() .unwrap() .configure(&context.device, self.config()); match self.surface.as_ref().unwrap().get_current_texture() { CurrentSurfaceTexture::Success(frame) | CurrentSurfaceTexture::Suboptimal(frame) => Some(frame), other => panic!("Failed to acquire next surface texture: {other:?}"), } } } } /// On suspend on android, we drop the surface, as it's no longer valid. /// /// A suspend event is always followed by at least one resume event. fn suspend(&mut self) { if cfg!(target_os = "android") { self.surface = None; } } fn get(&self) -> Option<&'_ Surface<'static>> { self.surface.as_ref() } fn config(&self) -> &wgpu::SurfaceConfiguration { self.config.as_ref().unwrap() } } /// Context containing global wgpu resources. struct ExampleContext { instance: wgpu::Instance, adapter: wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue, } impl ExampleContext { /// Initializes the example context. async fn init_async( surface: &mut SurfaceWrapper, window: Arc, display_handle: winit::event_loop::OwnedDisplayHandle, ) -> Self { log::info!("Initializing wgpu..."); let instance_descriptor = wgpu::InstanceDescriptor::new_with_display_handle_from_env(Box::new(display_handle)); let instance = wgpu::Instance::new(instance_descriptor); surface.pre_adapter(&instance, window); let adapter = get_adapter_with_capabilities_or_from_env( &instance, &E::required_features(), &E::required_downlevel_capabilities(), &surface.get(), ) .await; // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the surface. let needed_limits = E::required_limits().using_resolution(adapter.limits()); let info = adapter.get_info(); log::info!("Selected adapter: {} ({:?})", info.name, info.backend); let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: None, required_features: (E::optional_features() & adapter.features()) | E::required_features(), required_limits: needed_limits, experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() }, memory_hints: wgpu::MemoryHints::MemoryUsage, trace: match std::env::var_os("WGPU_TRACE") { Some(path) => wgpu::Trace::Directory(path.into()), None => wgpu::Trace::Off, }, }) .await .expect("Unable to find a suitable GPU adapter!"); Self { instance, adapter, device, queue, } } } struct FrameCounter { // Instant of the last time we printed the frame time. last_printed_instant: web_time::Instant, // Number of frames since the last time we printed the frame time. frame_count: u32, } impl FrameCounter { fn new() -> Self { Self { last_printed_instant: web_time::Instant::now(), frame_count: 0, } } fn update(&mut self) { self.frame_count += 1; let new_instant = web_time::Instant::now(); let elapsed_secs = (new_instant - self.last_printed_instant).as_secs_f32(); if elapsed_secs > 1.0 { let elapsed_ms = elapsed_secs * 1000.0; let frame_time = elapsed_ms / self.frame_count as f32; let fps = self.frame_count as f32 / elapsed_secs; log::info!("Frame time {frame_time:.2}ms ({fps:.1} FPS)"); self.last_printed_instant = new_instant; self.frame_count = 0; } } } /// User event sent via [`EventLoopProxy`] to deliver async initialization results /// back to the main event loop. enum AppAction { /// The async wgpu initialization has completed. WgpuInitialized { context: ExampleContext, surface: SurfaceWrapper, }, } #[expect(clippy::large_enum_variant)] enum AppState { /// Waiting for the first `resumed()` call. Uninitialized, /// Window created, async wgpu initialization in progress. Loading, /// Fully initialized and rendering. Running { context: ExampleContext, surface: SurfaceWrapper, example: E, }, } /// The main application struct, implementing winit's [`ApplicationHandler`]. /// /// Winit 0.30 requires that windows are not created until the `resumed()` callback, /// and that all wgpu resources (instance, adapter, device) are initialized after the /// window exists. On native, this init happens synchronously via `pollster::block_on`. /// On wasm, it happens asynchronously via `wasm_bindgen_futures::spawn_local`, with /// the results delivered back through an [`EventLoopProxy`] user event. struct App { title: &'static str, proxy: EventLoopProxy, window: Option>, frame_counter: FrameCounter, occluded: bool, state: AppState, } impl App { fn new(title: &'static str, event_loop: &EventLoop) -> Self { Self { title, proxy: event_loop.create_proxy(), window: None, frame_counter: FrameCounter::new(), occluded: false, state: AppState::Uninitialized, } } } impl ApplicationHandler for App { /// Called when the application is (re)started. On the first call, the window and wgpu /// resources are created. On Android, this may be called again after each suspend — /// in that case we only need to re-create the surface. fn resumed(&mut self, event_loop: &ActiveEventLoop) { // On Android, re-create the surface after a suspend/resume cycle. if let AppState::Running { ref context, ref mut surface, .. } = self.state { if let Some(window) = &self.window { surface.resume(context, window.clone(), E::SRGB); window.request_redraw(); } return; } if !matches!(self.state, AppState::Uninitialized) { return; } self.state = AppState::Loading; #[cfg_attr( not(target_arch = "wasm32"), expect(unused_mut, reason = "wasm32 re-assigns to specify canvas") )] let mut attributes = Window::default_attributes().with_title(self.title); #[cfg(target_arch = "wasm32")] { use wasm_bindgen::JsCast; use winit::platform::web::WindowAttributesExtWebSys; let canvas = web_sys::window() .unwrap() .document() .unwrap() .get_element_by_id("canvas") .unwrap() .dyn_into::() .unwrap(); attributes = attributes.with_canvas(Some(canvas)); } let window = Arc::new( event_loop .create_window(attributes) .expect("Failed to create window"), ); self.window = Some(window.clone()); let display_handle = event_loop.owned_display_handle(); let proxy = self.proxy.clone(); // Spawn the async wgpu initialization. On native, `spawn` uses `pollster::block_on` // so this completes synchronously before `resumed()` returns. On wasm, `spawn` uses // `wasm_bindgen_futures::spawn_local` so the result arrives later via `user_event()`. spawn(async move { let mut surface = SurfaceWrapper::new(); let context = ExampleContext::init_async::(&mut surface, window.clone(), display_handle).await; surface.resume(&context, window, E::SRGB); let _ = proxy.send_event(AppAction::WgpuInitialized { context, surface }); }); } /// Receives the result of the async wgpu initialization. Creates the [`Example`] and /// transitions to the running state. fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: AppAction) { match event { AppAction::WgpuInitialized { context, surface } => { let example = E::init( surface.config(), &context.adapter, &context.device, &context.queue, ); self.state = AppState::Running { context, surface, example, }; if let Some(window) = &self.window { window.request_redraw(); } } } } fn suspended(&mut self, _event_loop: &ActiveEventLoop) { if let AppState::Running { surface, .. } = &mut self.state { surface.suspend(); } } fn window_event( &mut self, event_loop: &ActiveEventLoop, _window_id: winit::window::WindowId, event: WindowEvent, ) { let AppState::Running { ref mut context, ref mut surface, ref mut example, } = self.state else { return; }; match event { WindowEvent::Resized(size) => { surface.resize(context, size); example.resize(surface.config(), &context.device, &context.queue); if let Some(window) = &self.window { window.request_redraw(); } } WindowEvent::KeyboardInput { event: KeyEvent { logical_key: Key::Named(NamedKey::Escape), .. }, .. } | WindowEvent::CloseRequested => { event_loop.exit(); } #[cfg(not(target_arch = "wasm32"))] WindowEvent::KeyboardInput { event: KeyEvent { logical_key: Key::Character(s), .. }, .. } if s == "r" => { println!("{:#?}", context.instance.generate_report()); } WindowEvent::RedrawRequested => { // Don't render while occluded, this may leak on apple platforms. if self.occluded { return; } self.frame_counter.update(); let window_arc = self.window.clone().unwrap(); if let Some(frame) = surface.acquire(context, window_arc) { let view = frame.texture.create_view(&wgpu::TextureViewDescriptor { format: Some(surface.config().view_formats[0]), ..wgpu::TextureViewDescriptor::default() }); example.render(&view, &context.device, &context.queue); if let Some(window) = &self.window { window.pre_present_notify(); } frame.present(); } if let Some(window) = &self.window { window.request_redraw(); } } WindowEvent::Occluded(is_occluded) => { self.occluded = is_occluded; // Resume rendering when un-occluded. if !is_occluded { if let Some(window) = &self.window { window.request_redraw(); } } } _ => example.update(event), } } } fn start(title: &'static str) { init_logger(); log::debug!( "Enabled backends: {:?}", wgpu::Instance::enabled_backend_features() ); let event_loop = EventLoop::with_user_event().build().unwrap(); #[cfg_attr(target_arch = "wasm32", expect(unused_mut))] let mut app = App::::new(title, &event_loop); log::info!("Entering event loop..."); cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { use winit::platform::web::EventLoopExtWebSys; event_loop.spawn_app(app); } else { event_loop.run_app(&mut app).unwrap(); } } } pub fn run(title: &'static str) { start::(title); } #[cfg(target_arch = "wasm32")] /// Parse the query string as returned by `web_sys::window()?.location().search()?` and get a /// specific key out of it. pub fn parse_url_query_string<'a>(query: &'a str, search_key: &str) -> Option<&'a str> { let query_string = query.strip_prefix('?')?; for pair in query_string.split('&') { let mut pair = pair.split('='); let key = pair.next()?; let value = pair.next()?; if key == search_key { return Some(value); } } None } #[cfg(test)] pub use wgpu_test::image::ComparisonType; use crate::utils::get_adapter_with_capabilities_or_from_env; #[cfg(test)] #[derive(Clone)] pub struct ExampleTestParams { pub name: &'static str, // Path to the reference image, relative to the root of the repo. pub image_path: &'static str, pub width: u32, pub height: u32, pub optional_features: wgpu::Features, pub base_test_parameters: wgpu_test::TestParameters, /// Comparisons against FLIP statistics that determine if the test passes or fails. pub comparisons: &'static [ComparisonType], pub _phantom: std::marker::PhantomData, } #[cfg(test)] impl From> for wgpu_test::GpuTestConfiguration { fn from(params: ExampleTestParams) -> Self { wgpu_test::GpuTestConfiguration::new() .name(params.name) .parameters({ assert_eq!(params.width % 64, 0, "width needs to be aligned 64"); let features = E::required_features() | params.optional_features; params .base_test_parameters .clone() .features(features) .limits(E::required_limits()) }) .run_async(move |ctx| async move { let format = if E::SRGB { wgpu::TextureFormat::Rgba8UnormSrgb } else { wgpu::TextureFormat::Rgba8Unorm }; let dst_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { label: Some("destination"), size: wgpu::Extent3d { width: params.width, height: params.height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, view_formats: &[], }); let dst_view = dst_texture.create_view(&wgpu::TextureViewDescriptor::default()); let dst_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { label: Some("image map buffer"), size: params.width as u64 * params.height as u64 * 4, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false, }); let mut example = E::init( &wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format, width: params.width, height: params.height, desired_maximum_frame_latency: 2, present_mode: wgpu::PresentMode::Fifo, alpha_mode: wgpu::CompositeAlphaMode::Auto, view_formats: vec![format], }, &ctx.adapter, &ctx.device, &ctx.queue, ); example.render(&dst_view, &ctx.device, &ctx.queue); let mut cmd_buf = ctx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); cmd_buf.copy_texture_to_buffer( wgpu::TexelCopyTextureInfo { texture: &dst_texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All, }, wgpu::TexelCopyBufferInfo { buffer: &dst_buffer, layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(params.width * 4), rows_per_image: None, }, }, wgpu::Extent3d { width: params.width, height: params.height, depth_or_array_layers: 1, }, ); ctx.queue.submit(Some(cmd_buf.finish())); let dst_buffer_slice = dst_buffer.slice(..); dst_buffer_slice.map_async(wgpu::MapMode::Read, |_| ()); ctx.async_poll(wgpu::PollType::wait_indefinitely()) .await .unwrap(); let bytes = dst_buffer_slice.get_mapped_range().to_vec(); wgpu_test::image::compare_image_output( dbg!(env!("CARGO_MANIFEST_DIR").to_string() + "/../../" + params.image_path), &ctx.adapter_info, params.width, params.height, &bytes, params.comparisons, ) .await; }) } } ================================================ FILE: examples/features/src/hello_synchronization/README.md ================================================ # hello_synchronization This example is 1. A small demonstration of the importance of synchronization. 2. How basic synchronization you can understand from the CPU is performed on the GPU. ## To Run ``` cargo run --bin wgpu-examples hello_synchronization ``` ## A Primer on WGSL Synchronization Functions The official documentation is a little scattered and sparse. The meat of the subject is found [here](https://www.w3.org/TR/2023/WD-WGSL-20230629/#sync-builtin-functions) but there's also a bit on control barriers [here](https://www.w3.org/TR/2023/WD-WGSL-20230629/#control-barrier). The most important part comes from that first link though, where the spec says "the affected memory and atomic operations program-ordered before the synchronization function must be visible to all other threads in the workgroup before any affected memory or atomic operation program-ordered after the synchronization function is executed by a member of the workgroup." And at the second, we also get "a control barrier is executed by all invocations in the same workgroup as if it were executed concurrently." That's rather vague (and it is by design) so let's break it down and make a comparison that should make that sentence come a bit more into focus. [Barriers in Rust](https://doc.rust-lang.org/std/sync/struct.Barrier.html#) fit both bills rather nicely. Firstly, Rust barriers are executed as if they were executed concurrently because they are - at least as long as you define the execution by when it finishes, when [`Barrier::wait`](https://doc.rust-lang.org/std/sync/struct.Barrier.html#method.wait) finally unblocks the thread and execution continues concurrently from there. Rust barriers also fit the first bill; because all affected threads must execute `Barrier::wait` in order for execution to continue, we can guarantee that _all (synchronous)_ operations ordered before the wait call are executed before any operations ordered after the wait call begin execution. Applying this to WGSL barriers, we can think of a barrier in WGSL as a checkpoint all invocations within each workgroup must reach before the entire workgroup continues with the program together. There are two key differences though and one is that although Rust barriers don't enforce that atomic operations called before the barrier are visible after the barrier, WGSL barriers do. This is incredibly useful and important though and is demonstrated in this example. Another is that WGSL's synchronous functions only affect memory and atomic operations in a certain address space. This applies to the whole 'all atomic operations called before the function are visible after the function' thing. There are currently three different synchronization functions: - `storageBarrier` which works in the storage address space and is a simple barrier. - `workgroupBarrier` which works in the workgroup address space and is a simple barrier. - `workgroupUniformLoad` which also works in the workgroup address space and is more than just a barrier. Read up on all three [here](https://www.w3.org/TR/2023/WD-WGSL-20230629/#sync-builtin-functions). ================================================ FILE: examples/features/src/hello_synchronization/mod.rs ================================================ const ARR_SIZE: usize = 128; struct ExecuteResults { patient_workgroup_results: Vec, hasty_workgroup_results: Vec, } async fn run() { let instance = wgpu::Instance::default(); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions::default()) .await .unwrap(); let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::Performance, trace: wgpu::Trace::Off, }) .await .unwrap(); let ExecuteResults { patient_workgroup_results, hasty_workgroup_results, } = execute(&device, &queue, ARR_SIZE).await; // Print data log::info!("Patient results: {patient_workgroup_results:?}"); if !patient_workgroup_results.iter().any(|e| *e != 16) { log::info!("patient_main was patient."); } else { log::error!("patient_main was not patient!"); } log::info!("Hasty results: {hasty_workgroup_results:?}"); if hasty_workgroup_results.iter().any(|e| *e != 16) { log::info!("hasty_main was not patient."); } else { log::info!("hasty_main got lucky."); } } async fn execute( device: &wgpu::Device, queue: &wgpu::Queue, result_vec_size: usize, ) -> ExecuteResults { let mut local_patient_workgroup_results = vec![0u32; result_vec_size]; let mut local_hasty_workgroup_results = local_patient_workgroup_results.clone(); let shaders_module = device.create_shader_module(wgpu::include_wgsl!("shaders.wgsl")); let storage_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: size_of_val(local_patient_workgroup_results.as_slice()) as u64, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: false, }); let output_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: size_of_val(local_patient_workgroup_results.as_slice()) as u64, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false, }); let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: None, }, count: None, }], }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: storage_buffer.as_entire_binding(), }], }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); let patient_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: Some(&pipeline_layout), module: &shaders_module, entry_point: Some("patient_main"), compilation_options: Default::default(), cache: None, }); let hasty_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: Some(&pipeline_layout), module: &shaders_module, entry_point: Some("hasty_main"), compilation_options: Default::default(), cache: None, }); //---------------------------------------------------------- let mut command_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut compute_pass = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); compute_pass.set_pipeline(&patient_pipeline); compute_pass.set_bind_group(0, &bind_group, &[]); compute_pass.dispatch_workgroups(local_patient_workgroup_results.len() as u32, 1, 1); } queue.submit(Some(command_encoder.finish())); get_data( local_patient_workgroup_results.as_mut_slice(), &storage_buffer, &output_staging_buffer, device, queue, ) .await; let mut command_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut compute_pass = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); compute_pass.set_pipeline(&hasty_pipeline); compute_pass.set_bind_group(0, &bind_group, &[]); compute_pass.dispatch_workgroups(local_patient_workgroup_results.len() as u32, 1, 1); } queue.submit(Some(command_encoder.finish())); get_data( local_hasty_workgroup_results.as_mut_slice(), &storage_buffer, &output_staging_buffer, device, queue, ) .await; ExecuteResults { patient_workgroup_results: local_patient_workgroup_results, hasty_workgroup_results: local_hasty_workgroup_results, } } async fn get_data( output: &mut [T], storage_buffer: &wgpu::Buffer, staging_buffer: &wgpu::Buffer, device: &wgpu::Device, queue: &wgpu::Queue, ) { let mut command_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); command_encoder.copy_buffer_to_buffer( storage_buffer, 0, staging_buffer, 0, size_of_val(output) as u64, ); queue.submit(Some(command_encoder.finish())); let buffer_slice = staging_buffer.slice(..); let (sender, receiver) = flume::bounded(1); buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap()); device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); receiver.recv_async().await.unwrap().unwrap(); output.copy_from_slice(bytemuck::cast_slice(&buffer_slice.get_mapped_range()[..])); staging_buffer.unmap(); } pub fn main() { #[cfg(not(target_arch = "wasm32"))] { env_logger::builder() .filter_level(log::LevelFilter::Info) .format_timestamp_nanos() .init(); pollster::block_on(run()); } #[cfg(target_arch = "wasm32")] { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); console_log::init_with_level(log::Level::Info).expect("could not initialize logger"); crate::utils::add_web_nothing_to_see_msg(); wasm_bindgen_futures::spawn_local(run()); } } #[cfg(test)] pub mod tests; ================================================ FILE: examples/features/src/hello_synchronization/shaders.wgsl ================================================ @group(0) @binding(0) var output: array; var count: atomic; @compute @workgroup_size(16) fn patient_main( @builtin(local_invocation_id) local_id: vec3, @builtin(workgroup_id) workgroup_id: vec3 ) { atomicAdd(&count, 1u); workgroupBarrier(); if (local_id.x == 0u) { output[workgroup_id.x] = atomicLoad(&count); } } @compute @workgroup_size(16) fn hasty_main( @builtin(local_invocation_id) local_id: vec3, @builtin(workgroup_id) workgroup_id: vec3 ) { atomicAdd(&count, 1u); if (local_id.x == 0u) { output[workgroup_id.x] = atomicLoad(&count); } } ================================================ FILE: examples/features/src/hello_synchronization/tests.rs ================================================ use super::*; use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters}; #[gpu_test] pub static SYNC: GpuTestConfiguration = GpuTestConfiguration::new() .parameters( // Taken from hello-compute tests. TestParameters::default() .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) .limits(wgpu::Limits::downlevel_defaults()), ) .run_async(|ctx| async move { let ExecuteResults { patient_workgroup_results, hasty_workgroup_results: _, } = execute(&ctx.device, &ctx.queue, ARR_SIZE).await; assert_eq!(patient_workgroup_results, [16_u32; ARR_SIZE]); }); ================================================ FILE: examples/features/src/hello_triangle/README.md ================================================ # hello_triangle This example renders a triangle to a window. ## To Run ``` cargo run --bin wgpu-examples hello_triangle ``` ## Screenshots ![Triangle window](./screenshot.png) ================================================ FILE: examples/features/src/hello_triangle/mod.rs ================================================ use std::{borrow::Cow, future::Future, sync::Arc}; use wgpu::CurrentSurfaceTexture; use winit::{ application::ApplicationHandler, event::WindowEvent, event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy}, window::Window, }; /// Runs a future to completion. On native this blocks synchronously via pollster. /// On wasm this spawns a local task so control returns to the browser immediately. #[cfg(not(target_arch = "wasm32"))] fn spawn(f: impl Future + 'static) { pollster::block_on(f); } /// Runs a future to completion. On native this blocks synchronously via pollster. /// On wasm this spawns a local task so control returns to the browser immediately. #[cfg(target_arch = "wasm32")] fn spawn(f: impl Future + 'static) { wasm_bindgen_futures::spawn_local(f); } struct WgpuState { instance: wgpu::Instance, window: Arc, device: wgpu::Device, queue: wgpu::Queue, surface: wgpu::Surface<'static>, config: wgpu::SurfaceConfiguration, render_pipeline: wgpu::RenderPipeline, } enum TriangleAction { Initialized(WgpuState), } #[expect(clippy::large_enum_variant)] enum AppState { Uninitialized, Loading, Running(WgpuState), } struct App { proxy: EventLoopProxy, window: Option>, state: AppState, } impl App { fn new(event_loop: &EventLoop) -> Self { Self { proxy: event_loop.create_proxy(), window: None, state: AppState::Uninitialized, } } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { if !matches!(self.state, AppState::Uninitialized) { return; } self.state = AppState::Loading; #[cfg_attr( not(target_arch = "wasm32"), expect(unused_mut, reason = "wasm32 re-assigns to specify canvas") )] let mut attributes = Window::default_attributes(); #[cfg(target_arch = "wasm32")] { use wasm_bindgen::JsCast; use winit::platform::web::WindowAttributesExtWebSys; let canvas = web_sys::window() .unwrap() .document() .unwrap() .get_element_by_id("canvas") .unwrap() .dyn_into::() .unwrap(); attributes = attributes.with_canvas(Some(canvas)); } let window = Arc::new( event_loop .create_window(attributes) .expect("Failed to create window"), ); self.window = Some(window.clone()); let display_handle = event_loop.owned_display_handle(); let proxy = self.proxy.clone(); spawn(async move { let mut size = window.inner_size(); size.width = size.width.max(1); size.height = size.height.max(1); let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_with_display_handle_from_env( Box::new(display_handle), )); let surface = instance.create_surface(window.clone()).unwrap(); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::default(), force_fallback_adapter: false, // Request an adapter which can render to our surface compatible_surface: Some(&surface), }) .await .expect("Failed to find an appropriate adapter"); // Create the logical device and command queue let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: None, required_features: wgpu::Features::empty(), // Make sure we use the texture resolution limits from the adapter, // so we can support images the size of the swapchain. required_limits: wgpu::Limits::downlevel_webgl2_defaults() .using_resolution(adapter.limits()), experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) .await .expect("Failed to create device"); // Load the shaders from disk let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[], immediate_size: 0, }); let swapchain_capabilities = surface.get_capabilities(&adapter); let swapchain_format = swapchain_capabilities.formats[0]; let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), buffers: &[], compilation_options: Default::default(), }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(swapchain_format.into())], }), primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let config = surface .get_default_config(&adapter, size.width, size.height) .unwrap(); surface.configure(&device, &config); let _ = proxy.send_event(TriangleAction::Initialized(WgpuState { instance, window, device, queue, surface, config, render_pipeline, })); }); } fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: TriangleAction) { match event { TriangleAction::Initialized(wgpu_state) => { self.state = AppState::Running(wgpu_state); if let Some(window) = &self.window { window.request_redraw(); } } } } fn window_event( &mut self, event_loop: &ActiveEventLoop, _window_id: winit::window::WindowId, event: WindowEvent, ) { let AppState::Running(wgpu_state) = &mut self.state else { return; }; match event { WindowEvent::Resized(new_size) => { // Reconfigure the surface with the new size wgpu_state.config.width = new_size.width.max(1); wgpu_state.config.height = new_size.height.max(1); wgpu_state .surface .configure(&wgpu_state.device, &wgpu_state.config); // On macos the window needs to be redrawn manually after resizing if let Some(window) = &self.window { window.request_redraw(); } } WindowEvent::RedrawRequested => { let frame = match wgpu_state.surface.get_current_texture() { CurrentSurfaceTexture::Success(frame) => frame, CurrentSurfaceTexture::Timeout | CurrentSurfaceTexture::Occluded => { // Try again later if let Some(window) = &self.window { window.request_redraw(); } return; } CurrentSurfaceTexture::Suboptimal(_) | CurrentSurfaceTexture::Outdated => { wgpu_state .surface .configure(&wgpu_state.device, &wgpu_state.config); if let Some(window) = &self.window { window.request_redraw(); } return; } CurrentSurfaceTexture::Validation => { unreachable!("No error scope registered, so validation errors will panic") } CurrentSurfaceTexture::Lost => { wgpu_state.surface = wgpu_state .instance .create_surface(wgpu_state.window.clone()) .unwrap(); wgpu_state .surface .configure(&wgpu_state.device, &wgpu_state.config); if let Some(window) = &self.window { window.request_redraw(); } return; } }; let view = frame .texture .create_view(&wgpu::TextureViewDescriptor::default()); let mut encoder = wgpu_state .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&wgpu_state.render_pipeline); rpass.draw(0..3, 0..1); } wgpu_state.queue.submit(Some(encoder.finish())); if let Some(window) = &self.window { window.pre_present_notify(); } frame.present(); } WindowEvent::Occluded(is_occluded) => { if !is_occluded { if let Some(window) = &self.window { window.request_redraw(); } } } WindowEvent::CloseRequested => event_loop.exit(), _ => {} } } } pub fn main() { cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); console_log::init().expect("could not initialize logger"); } else { env_logger::init(); } } let event_loop = EventLoop::with_user_event().build().unwrap(); #[cfg_attr(target_arch = "wasm32", expect(unused_mut))] let mut app = App::new(&event_loop); cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { use winit::platform::web::EventLoopExtWebSys; event_loop.spawn_app(app); } else { event_loop.run_app(&mut app).unwrap(); } } } ================================================ FILE: examples/features/src/hello_triangle/shader.wgsl ================================================ @vertex fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { let x = f32(i32(in_vertex_index) - 1); let y = f32(i32(in_vertex_index & 1u) * 2 - 1); return vec4(x, y, 0.0, 1.0); } @fragment fn fs_main() -> @location(0) vec4 { return vec4(1.0, 0.0, 0.0, 1.0); } ================================================ FILE: examples/features/src/hello_windows/README.md ================================================ # hello_windows This example renders a set of 16 windows, with a differently colored background ## To Run ``` cargo run --bin wgpu-examples hello_windows ``` ## Screenshots ![16 windows](./screenshot.png) ================================================ FILE: examples/features/src/hello_windows/mod.rs ================================================ #![cfg_attr(target_arch = "wasm32", allow(dead_code, unused_imports))] use std::{collections::HashMap, sync::Arc}; use wgpu::CurrentSurfaceTexture; use winit::{ application::ApplicationHandler, event::WindowEvent, event_loop::ActiveEventLoop, window::{Window, WindowId}, }; struct ViewportDesc { window: Arc, background: wgpu::Color, surface: wgpu::Surface<'static>, } struct Viewport { desc: ViewportDesc, config: wgpu::SurfaceConfiguration, } impl ViewportDesc { fn new(window: Arc, background: wgpu::Color, instance: &wgpu::Instance) -> Self { let surface = instance.create_surface(window.clone()).unwrap(); Self { window, background, surface, } } fn build(self, adapter: &wgpu::Adapter, device: &wgpu::Device) -> Viewport { let size = self.window.inner_size(); let config = self .surface .get_default_config(adapter, size.width, size.height) .unwrap(); self.surface.configure(device, &config); Viewport { desc: self, config } } } impl Viewport { fn resize(&mut self, device: &wgpu::Device, size: winit::dpi::PhysicalSize) { self.config.width = size.width; self.config.height = size.height; self.desc.surface.configure(device, &self.config); } fn get_current_texture(&mut self) -> CurrentSurfaceTexture { self.desc.surface.get_current_texture() } } const WINDOW_SIZE: u32 = 128; const WINDOW_PADDING: u32 = 16; const WINDOW_TITLEBAR: u32 = 32; const WINDOW_OFFSET: u32 = WINDOW_SIZE + WINDOW_PADDING; const ROWS: u32 = 4; const COLUMNS: u32 = 4; enum AppState { Uninitialized, Running { instance: wgpu::Instance, device: wgpu::Device, queue: wgpu::Queue, viewports: HashMap, }, } struct App { state: AppState, } impl App { fn new() -> Self { Self { state: AppState::Uninitialized, } } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { if !matches!(self.state, AppState::Uninitialized) { return; } // Create all 16 windows. let mut windows: Vec<(Arc, wgpu::Color)> = Vec::with_capacity((ROWS * COLUMNS) as usize); for row in 0..ROWS { for column in 0..COLUMNS { let window = Arc::new( event_loop .create_window( Window::default_attributes() .with_title(format!("x{column}y{row}")) .with_inner_size(winit::dpi::PhysicalSize::new( WINDOW_SIZE, WINDOW_SIZE, )), ) .unwrap(), ); window.set_outer_position(winit::dpi::PhysicalPosition::new( WINDOW_PADDING + column * WINDOW_OFFSET, WINDOW_PADDING + row * (WINDOW_OFFSET + WINDOW_TITLEBAR), )); fn frac(index: u32, max: u32) -> f64 { index as f64 / max as f64 } windows.push(( window, wgpu::Color { r: frac(row, ROWS), g: 0.5 - frac(row * column, ROWS * COLUMNS) * 0.5, b: frac(column, COLUMNS), a: 1.0, }, )); } } // Initialize wgpu synchronously (native-only). let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_with_display_handle_from_env( Box::new(event_loop.owned_display_handle()), )); let viewport_descs: Vec<_> = windows .into_iter() .map(|(window, color)| ViewportDesc::new(window, color, &instance)) .collect(); let (adapter, device, queue) = pollster::block_on(async { let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { compatible_surface: viewport_descs.first().map(|desc| &desc.surface), ..Default::default() }) .await .expect("Failed to find an appropriate adapter"); let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) .await .expect("Failed to create device"); (adapter, device, queue) }); let viewports: HashMap = viewport_descs .into_iter() .map(|desc| (desc.window.id(), desc.build(&adapter, &device))) .collect(); self.state = AppState::Running { instance, device, queue, viewports, }; } fn window_event( &mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent, ) { let AppState::Running { instance, device, queue, viewports, } = &mut self.state else { return; }; match event { WindowEvent::Resized(new_size) => { // Recreate the swap chain with the new size if let Some(viewport) = viewports.get_mut(&window_id) { viewport.resize(device, new_size); // On macos the window needs to be redrawn manually after resizing viewport.desc.window.request_redraw(); } } WindowEvent::RedrawRequested => { if let Some(viewport) = viewports.get_mut(&window_id) { let frame = match viewport.get_current_texture() { CurrentSurfaceTexture::Success(frame) => frame, CurrentSurfaceTexture::Timeout | CurrentSurfaceTexture::Occluded => { viewport.desc.window.request_redraw(); return; } CurrentSurfaceTexture::Suboptimal(_) | CurrentSurfaceTexture::Outdated => { viewport.desc.surface.configure(device, &viewport.config); viewport.desc.window.request_redraw(); return; } CurrentSurfaceTexture::Validation => { unreachable!( "No error scope registered, so validation errors will panic" ) } CurrentSurfaceTexture::Lost => { viewport.desc.surface = instance .create_surface(viewport.desc.window.clone()) .unwrap(); viewport.desc.surface.configure(device, &viewport.config); viewport.desc.window.request_redraw(); return; } }; let view = frame .texture .create_view(&wgpu::TextureViewDescriptor::default()); let mut encoder = device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let _rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(viewport.desc.background), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); } queue.submit(Some(encoder.finish())); viewport.desc.window.pre_present_notify(); frame.present(); } } WindowEvent::Occluded(is_occluded) => { if !is_occluded { if let Some(viewport) = viewports.get(&window_id) { viewport.desc.window.request_redraw(); } } } WindowEvent::CloseRequested => { viewports.remove(&window_id); if viewports.is_empty() { event_loop.exit(); } } _ => {} } } } pub fn main() { #[cfg(not(target_arch = "wasm32"))] { env_logger::init(); let event_loop = winit::event_loop::EventLoop::new().unwrap(); let mut app = App::new(); event_loop.run_app(&mut app).unwrap(); } #[cfg(target_arch = "wasm32")] { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); panic!("wasm32 is not supported") } } ================================================ FILE: examples/features/src/hello_workgroups/README.md ================================================ # hello_workgroups Now you finally know what that silly little `@workgroup_size(1)` means! This example is an extremely bare-bones and arguably somewhat unreasonable demonstration of what workgroup sizes mean in an attempt to explain workgroups in general. The example starts with two arrays of numbers. One where `a[i] = i` and the other where `b[i] = 2i`. Both are bound to the shader. The program dispatches a workgroup for each index, each workgroup representing both elements at that index in both arrays. Each invocation in each workgroup works on its respective array and adds 1 to the element there. ## To Run ``` cargo run --bin wgpu-examples hello_workgroups ``` ## What are Workgroups? ### TLDR / Key Takeaways - Workgroups fit in a 3d grid of workgroups executed in a single dispatch. - All invocations in a workgroup are guaranteed to execute concurrently. - Workgroups carry no other guarantees for concurrency outside of those individual workgroups, meaning... - No two workgroups can be guaranteed to be executed in parallel. - No two workgroups can be guaranteed NOT to be executed in parallel. - No set of workgroups can be guaranteed to execute in any predictable or reliable order in relation to each other. - The size of a workgroup is defined with the `@workgroup_size` attribute on a compute shader main function. - The location of an invocation within its workgroup grid can be got with `@builtin(local_invocation_id)`. - The location of an invocation within the entire compute shader grid can be gotten with `@builtin(global_invocation_id)`. - The location of an invocation's workgroup within the dispatch grid can be gotten with `@builtin(workgroup_id)`. - Workgroups share memory within the `workgroup` address space. Workgroup memory is similar to private memory but it is shared within a workgroup. Invocations within a workgroup will see the same memory but invocations across workgroups will be accessing different memory. ### Introduction When you call `ComputePass::dispatch_workgroups`, the function dispatches multiple workgroups in a 3d grid defined by the `x`, `y`, and `z` parameters you pass to the function. For example, `dispatch_workgroups(5, 2, 1)` would create a dispatch grid like |||||| |---|---|---|---|---| | W | W | W | W | W | | W | W | W | W | W | Where each W is a workgroup. If you want your shader to consider what workgroup within the dispatch the current invocation is in, add a function argument with type `vec3` and with the attribute `@builtin(workgroup_id)`. Note here that in this example, the term "dispatch grid" is used throughout to mean the grid of workgroups within the dispatch but is not a proper term within WGSL. Other terms to know though that are proper are "workgroup grid" which refers to the invocations in a single _workgroup_ and "compute shader grid" which refers to the grid of _all_ the invocations in the _entire dispatch_. ### Within the Workgroup Although with hello-compute and repeated-compute, we used a workgroup size of `(1)`, or rather, (1, 1, 1), and then each workgroup called from `dispatch_workgroups` made _an_ invocation, this isn't always the case. Each workgroup represents its own little grid of individual invocations tied together. This could be just one or practically any number in a 3d grid of invocations. The grid size of each workgroup and thus the number of invocations called per workgroup is determined by the `@workgroup_size` attribute you've seen in other compute shaders. To get the current invocation's location within a workgroup, add a `vec3` argument to the main function with the attribute `@builtin(local_invocation_id)`. We'll look at the compute shader grid of a dispatch of size (2, 2, 1) with workgroup sizes of (2, 2, 1) as well. Let `w` be the `workgroup_id` and `i` be the `local_invocation_id`. ||||| |------------------------|------------------------|------------------------|------------------------| | w(0, 0, 0), i(0, 0, 0) | w(0, 0, 0), i(1, 0, 0) | w(1, 0, 0), i(0, 0, 0) | w(1, 0, 0), i(1, 0, 0) | | w(0, 0, 0), i(0, 1, 0) | w(0, 0, 0), i(1, 1, 0) | w(1, 0, 0), i(0, 1, 0) | w(1, 0, 0), i(1, 1, 0) | | w(0, 1, 0), i(0, 0, 0) | w(0, 1, 0), i(1, 0, 0) | w(1, 1, 0), i(0, 0, 0) | w(1, 1, 0), i(1, 0, 0) | | w(0, 1, 0), i(0, 1, 0) | w(0, 1, 0), i(1, 1, 0) | w(1, 1, 0), i(0, 1, 0) | w(1, 1, 0), i(1, 1, 0) | ### Execution of Workgroups As stated before, workgroups are groups of invocations. The invocations within a workgroup are always guaranteed to execute in parallel. That said, the guarantees basically stop there. You cannot get any guarantee as to when any given workgroup will execute, including in relation to other workgroups. You can't guarantee that any two workgroups will execute together nor can you guarantee that they will _not_ execute together. Of the workgroups that don't execute together, you additionally cannot guarantee that they will execute in any particular order. When your function runs in an invocation, you know that it will be working together with its workgroup buddies and that's basically it. See [the WGSL spec on compute shader execution](https://www.w3.org/TR/2023/WD-WGSL-20230629/#compute-shader-workgroups) for more details. ### Workgroups and their Invocations in a Global Scope As mentioned above, invocations exist both within the context of a workgroup grid as well as a compute shader grid which is a grid, divided into workgroup sections, of invocations that represents the whole of the dispatch. Similar to how `@builtin(local_invocation_id)` gets you the place of the invocation within the workgroup grid, `@builtin(global_invocation_id)` gets you the place of the invocation within the entire compute shader grid. Slight trivia: you might imagine that this is computed from `local_invocation_id` and `workgroup_id` but it's actually the opposite. Everything operates on the compute shader grid, the workgroups are imagined sectors within the compute shader grid, and `local_invocation_id` and `workgroup_id` are calculated based on global id and known workgroup size. Yes, we live in a matrix... of compute shader invocations. This isn't super useful information but it can help fit things into a larger picture. ## Barriers and Workgroups Arguably, workgroups are at their most useful when being used alongside barriers. Since barriers are already explained more thoroughly in the hello-synchronization example, this section will be short. Despite affecting different memory address spaces, all synchronization functions affect invocations on a workgroup level, synchronizing the workgroup. See [hello-synchronization/README.md](../hello-synchronization/README.md) for more. ## Links to Technical Resources For a rather long explainer, this README may still leave the more technically minded person with questions. The specifications for both WebGPU and WGSL ("WebGPU Shading Language") are long and it's rather unintuitive that by far the vast majority of specification on how workgroups and compute shaders more generally work, is all in the WGSL spec. Below are some links into the specifications at a couple interesting points: - [Here](https://www.w3.org/TR/WGSL/#compute-shader-workgroups) is the main section on workgroups and outlines important terminology in technical terms. It is recommended that everyone looking for something in this section of this README start by reading this. - [Here](https://www.w3.org/TR/webgpu/#computing-operations) is a section on compute shaders from a WebGPU perspective (instead of WGSL). It's still a stub but hopefully it will grow in the future. - Don't forget your [`@builtin()`'s](https://www.w3.org/TR/WGSL/#builtin-inputs-outputs)! ================================================ FILE: examples/features/src/hello_workgroups/mod.rs ================================================ //! This example assumes that you've seen hello-compute and or repeated-compute //! and thus have a general understanding of what's going on here. //! //! There's an explainer on what this example does exactly and what workgroups //! are and the meaning of `@workgroup(size_x, size_y, size_z)` in the //! README. Also see commenting in shader.wgsl as well. //! //! Only parts specific to this example will be commented. use wgpu::util::DeviceExt; async fn run() { let mut local_a = [0i32; 100]; for (i, e) in local_a.iter_mut().enumerate() { *e = i as i32; } log::info!("Input a: {local_a:?}"); let mut local_b = [0i32; 100]; for (i, e) in local_b.iter_mut().enumerate() { *e = i as i32 * 2; } log::info!("Input b: {local_b:?}"); let instance = wgpu::Instance::default(); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions::default()) .await .unwrap(); let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) .await .unwrap(); let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let storage_buffer_a = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: None, contents: bytemuck::cast_slice(&local_a[..]), usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, }); let storage_buffer_b = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: None, contents: bytemuck::cast_slice(&local_b[..]), usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, }); let output_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: size_of_val(&local_a) as u64, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false, }); let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, ], }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: storage_buffer_a.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: storage_buffer_b.as_entire_binding(), }, ], }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: Some(&pipeline_layout), module: &shader, entry_point: Some("main"), compilation_options: Default::default(), cache: None, }); //---------------------------------------------------------- let mut command_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut compute_pass = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); compute_pass.set_pipeline(&pipeline); compute_pass.set_bind_group(0, &bind_group, &[]); /* Note that since each workgroup will cover both arrays, we only need to cover the length of one array. */ compute_pass.dispatch_workgroups(local_a.len() as u32, 1, 1); } queue.submit(Some(command_encoder.finish())); //---------------------------------------------------------- get_data( &mut local_a[..], &storage_buffer_a, &output_staging_buffer, &device, &queue, ) .await; get_data( &mut local_b[..], &storage_buffer_b, &output_staging_buffer, &device, &queue, ) .await; log::info!("Output in A: {local_a:?}"); log::info!("Output in B: {local_b:?}"); } async fn get_data( output: &mut [T], storage_buffer: &wgpu::Buffer, staging_buffer: &wgpu::Buffer, device: &wgpu::Device, queue: &wgpu::Queue, ) { let mut command_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); command_encoder.copy_buffer_to_buffer( storage_buffer, 0, staging_buffer, 0, size_of_val(output) as u64, ); queue.submit(Some(command_encoder.finish())); let buffer_slice = staging_buffer.slice(..); let (sender, receiver) = flume::bounded(1); buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap()); device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); receiver.recv_async().await.unwrap().unwrap(); output.copy_from_slice(bytemuck::cast_slice(&buffer_slice.get_mapped_range()[..])); staging_buffer.unmap(); } pub fn main() { #[cfg(not(target_arch = "wasm32"))] { env_logger::builder() .filter_level(log::LevelFilter::Info) .format_timestamp_nanos() .init(); pollster::block_on(run()); } #[cfg(target_arch = "wasm32")] { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); console_log::init_with_level(log::Level::Info).expect("could not initialize logger"); crate::utils::add_web_nothing_to_see_msg(); wasm_bindgen_futures::spawn_local(run()); } } ================================================ FILE: examples/features/src/hello_workgroups/shader.wgsl ================================================ // This is useful because we can't use, say, vec2> because // of array being unsized. Normally we would interweave them or use // and array of structs but this is just for the sake of demonstration. @group(0) @binding(0) var a: array; @group(0) @binding(1) var b: array; @compute @workgroup_size(2, 1, 1) fn main(@builtin(local_invocation_id) lid: vec3, @builtin(workgroup_id) wid: vec3) { if lid.x == 0u { // Do computation (use your imagionation) a[wid.x] += 1; } else if lid.x == 1u { // Do computation b[wid.x] += 1; } } ================================================ FILE: examples/features/src/lib.rs ================================================ #![allow(clippy::arc_with_non_send_sync)] // False positive on wasm #![warn(clippy::allow_attributes)] pub mod framework; pub mod utils; pub mod big_compute_buffers; pub mod boids; pub mod bunnymark; pub mod conservative_raster; pub mod cooperative_matrix; pub mod cube; pub mod hello_synchronization; pub mod hello_triangle; pub mod hello_windows; pub mod hello_workgroups; pub mod mesh_shader; pub mod mipmap; pub mod msaa_line; pub mod multiple_render_targets; pub mod multiview; pub mod ray_cube_compute; pub mod ray_cube_fragment; pub mod ray_cube_normals; pub mod ray_scene; pub mod ray_shadows; pub mod ray_traced_triangle; pub mod render_to_texture; pub mod render_with_compute; pub mod repeated_compute; pub mod shadow; pub mod skybox; pub mod srgb_blend; pub mod stencil_triangles; pub mod storage_texture; pub mod texture_arrays; pub mod timestamp_queries; pub mod uniform_values; pub mod water; #[cfg(test)] fn all_tests() -> Vec { #[cfg_attr( target_arch = "wasm32", expect(unused_mut, reason = "non-wasm32 needs this mutable") )] let mut test_list = vec![ boids::TEST, bunnymark::TEST, conservative_raster::TEST, cube::TEST, cube::TEST_LINES, hello_synchronization::tests::SYNC, mesh_shader::TEST, mipmap::TEST, mipmap::TEST_QUERY, msaa_line::TEST, multiple_render_targets::TEST, ray_cube_compute::TEST, ray_cube_fragment::TEST, ray_cube_normals::TEST, ray_scene::TEST, ray_shadows::TEST, ray_traced_triangle::TEST, shadow::TEST, skybox::TEST, skybox::TEST_ASTC, skybox::TEST_BCN, skybox::TEST_ETC2, srgb_blend::TEST_LINEAR, srgb_blend::TEST_SRGB, stencil_triangles::TEST, texture_arrays::TEST, texture_arrays::TEST_NON_UNIFORM, texture_arrays::TEST_UNIFORM, timestamp_queries::tests::TIMESTAMPS_ENCODER, timestamp_queries::tests::TIMESTAMPS_PASSES, timestamp_queries::tests::TIMESTAMPS_PASS_BOUNDARIES, water::TEST, ]; #[cfg(not(target_arch = "wasm32"))] { test_list.push(big_compute_buffers::tests::TWO_BUFFERS); test_list.push(cooperative_matrix::tests::COOPERATIVE_MATRIX); } test_list } #[cfg(test)] wgpu_test::gpu_test_main!(all_tests()); ================================================ FILE: examples/features/src/main.rs ================================================ struct ExampleDesc { name: &'static str, function: fn(), #[cfg_attr(not(target_arch = "wasm32"), expect(dead_code))] webgl: bool, #[cfg_attr(not(target_arch = "wasm32"), expect(dead_code))] webgpu: bool, } const EXAMPLES: &[ExampleDesc] = &[ ExampleDesc { name: "big_compute_buffers", function: wgpu_examples::big_compute_buffers::main, webgl: false, // Native only example webgpu: false, // Native only example }, ExampleDesc { name: "boids", function: wgpu_examples::boids::main, webgl: false, // No compute webgpu: true, }, ExampleDesc { name: "bunnymark", function: wgpu_examples::bunnymark::main, webgl: true, webgpu: true, }, ExampleDesc { name: "conservative_raster", function: wgpu_examples::conservative_raster::main, webgl: false, // No conservative raster webgpu: false, // No conservative raster }, ExampleDesc { name: "cooperative_matrix", function: wgpu_examples::cooperative_matrix::main, webgl: false, // No cooperative matrix support webgpu: false, // No cooperative matrix support }, ExampleDesc { name: "cube", function: wgpu_examples::cube::main, webgl: true, webgpu: true, }, ExampleDesc { name: "hello_synchronization", function: wgpu_examples::hello_synchronization::main, webgl: false, // No canvas for WebGL webgpu: true, }, ExampleDesc { name: "hello_triangle", function: wgpu_examples::hello_triangle::main, webgl: true, webgpu: true, }, ExampleDesc { name: "hello_windows", function: wgpu_examples::hello_windows::main, webgl: false, // Native only example webgpu: false, // Native only example }, ExampleDesc { name: "hello_workgroups", function: wgpu_examples::hello_workgroups::main, webgl: false, webgpu: true, }, ExampleDesc { name: "mipmap", function: wgpu_examples::mipmap::main, webgl: true, webgpu: true, }, ExampleDesc { name: "msaa_line", function: wgpu_examples::msaa_line::main, webgl: true, webgpu: true, }, ExampleDesc { name: "multiple_render_targets", function: wgpu_examples::multiple_render_targets::main, webgl: false, webgpu: true, }, ExampleDesc { name: "render_to_texture", function: wgpu_examples::render_to_texture::main, webgl: false, // No canvas for WebGL webgpu: true, }, ExampleDesc { name: "render_with_compute", function: wgpu_examples::render_with_compute::main, webgl: false, webgpu: true, }, ExampleDesc { name: "repeated_compute", function: wgpu_examples::repeated_compute::main, webgl: false, // No compute webgpu: true, }, ExampleDesc { name: "shadow", function: wgpu_examples::shadow::main, webgl: true, webgpu: true, }, ExampleDesc { name: "skybox", function: wgpu_examples::skybox::main, webgl: true, webgpu: true, }, ExampleDesc { name: "srgb_blend", function: wgpu_examples::srgb_blend::main, webgl: true, webgpu: true, }, ExampleDesc { name: "stencil_triangles", function: wgpu_examples::stencil_triangles::main, webgl: true, webgpu: true, }, ExampleDesc { name: "storage_texture", function: wgpu_examples::storage_texture::main, webgl: false, // No storage textures webgpu: true, }, ExampleDesc { name: "texture_arrays", function: wgpu_examples::texture_arrays::main, webgl: false, // No texture arrays webgpu: false, // No texture arrays }, ExampleDesc { name: "timestamp_queries", function: wgpu_examples::timestamp_queries::main, webgl: false, // No canvas for WebGL webgpu: false, // No timestamp queries }, ExampleDesc { name: "uniform_values", function: wgpu_examples::uniform_values::main, webgl: false, // No compute webgpu: true, }, ExampleDesc { name: "water", function: wgpu_examples::water::main, webgl: false, // No RODS webgpu: true, }, ExampleDesc { name: "ray_cube_compute", function: wgpu_examples::ray_cube_compute::main, webgl: false, // No Ray-tracing extensions webgpu: false, // No Ray-tracing extensions (yet) }, ExampleDesc { name: "ray_cube_fragment", function: wgpu_examples::ray_cube_fragment::main, webgl: false, // No Ray-tracing extensions webgpu: false, // No Ray-tracing extensions (yet) }, ExampleDesc { name: "ray_scene", function: wgpu_examples::ray_scene::main, webgl: false, // No Ray-tracing extensions webgpu: false, // No Ray-tracing extensions (yet) }, ExampleDesc { name: "ray_shadows", function: wgpu_examples::ray_shadows::main, webgl: false, // No Ray-tracing extensions webgpu: false, // No Ray-tracing extensions (yet) }, ExampleDesc { name: "ray_traced_triangle", function: wgpu_examples::ray_traced_triangle::main, webgl: false, webgpu: false, }, ExampleDesc { name: "ray_cube_normals", function: wgpu_examples::ray_cube_normals::main, webgl: false, // No Ray-tracing extensions webgpu: false, // No Ray-tracing extensions (yet) }, ExampleDesc { name: "mesh_shader", function: wgpu_examples::mesh_shader::main, webgl: false, webgpu: false, }, ExampleDesc { name: "multiview", function: wgpu_examples::multiview::main, webgl: false, webgpu: false, }, ]; fn get_example_name() -> Option { cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { let query_string = web_sys::window()?.location().search().ok()?; wgpu_examples::framework::parse_url_query_string(&query_string, "example").map(String::from) } else { std::env::args().nth(1) } } } #[cfg(target_arch = "wasm32")] fn print_examples() { // Get the document, header, and body elements. let document = web_sys::window().unwrap().document().unwrap(); for backend in ["webgl2", "webgpu"] { let ul = document .get_element_by_id(&format!("{backend}-list")) .unwrap(); for example in EXAMPLES { if backend == "webgl2" && !example.webgl { continue; } if backend == "webgpu" && !example.webgpu { continue; } let link = document.create_element("a").unwrap(); link.set_text_content(Some(example.name)); link.set_attribute( "href", &format!("?backend={backend}&example={}", example.name), ) .unwrap(); link.set_class_name("example-link"); let item = document.create_element("div").unwrap(); item.append_child(&link).unwrap(); item.set_class_name("example-item"); ul.append_child(&item).unwrap(); } } } #[cfg(target_arch = "wasm32")] fn print_unknown_example(_result: Option) {} #[cfg(not(target_arch = "wasm32"))] fn print_unknown_example(result: Option) { if let Some(example) = result { println!("Unknown example: {example}"); } else { println!("Please specify an example as the first argument!"); } println!("\nAvailable Examples:"); for examples in EXAMPLES { println!("\t{}", examples.name); } } fn main() { #[cfg(target_arch = "wasm32")] print_examples(); let Some(example) = get_example_name() else { print_unknown_example(None); return; }; let Some(found) = EXAMPLES.iter().find(|e| e.name == example) else { print_unknown_example(Some(example)); return; }; (found.function)(); } ================================================ FILE: examples/features/src/mesh_shader/README.md ================================================ # mesh_shader This example renders a triangle to a window with mesh shaders, while showcasing most mesh shader related features(task shaders, payloads, per primitive data). ## To Run ``` cargo run --bin wgpu-examples mesh_shader ``` ================================================ FILE: examples/features/src/mesh_shader/mod.rs ================================================ // Same as in mesh shader tests fn compile_wgsl(device: &wgpu::Device) -> wgpu::ShaderModule { // Workgroup memory zero initialization can be expensive for mesh shaders unsafe { device.create_shader_module_trusted( wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), }, wgpu::ShaderRuntimeChecks::unchecked(), ) } } fn compile_hlsl(device: &wgpu::Device, entry: &str, stage_str: &str) -> wgpu::ShaderModule { let out_path = format!( "{}/src/mesh_shader/shader.{stage_str}.cso", env!("CARGO_MANIFEST_DIR") ); let cmd = std::process::Command::new("dxc") .args([ "-T", &format!("{stage_str}_6_5"), "-E", entry, &format!("{}/src/mesh_shader/shader.hlsl", env!("CARGO_MANIFEST_DIR")), "-Fo", &out_path, ]) .output() .unwrap(); if !cmd.status.success() { panic!("DXC failed:\n{}", String::from_utf8(cmd.stderr).unwrap()); } let file = std::fs::read(&out_path).unwrap(); std::fs::remove_file(out_path).unwrap(); unsafe { device.create_shader_module_passthrough(wgpu::ShaderModuleDescriptorPassthrough { label: None, num_workgroups: (1, 1, 1), dxil: Some(std::borrow::Cow::Owned(file)), ..Default::default() }) } } fn compile_msl(device: &wgpu::Device) -> wgpu::ShaderModule { unsafe { device.create_shader_module_passthrough(wgpu::ShaderModuleDescriptorPassthrough { label: None, msl: Some(std::borrow::Cow::Borrowed(include_str!("shader.metal"))), num_workgroups: (1, 1, 1), ..Default::default() }) } } struct Shaders { ts: wgpu::ShaderModule, ms: wgpu::ShaderModule, fs: wgpu::ShaderModule, ts_name: &'static str, ms_name: &'static str, fs_name: &'static str, } fn get_shaders(device: &wgpu::Device, backend: wgpu::Backend) -> Shaders { // In the case that the platform does support mesh shaders, the dummy // shader is used to avoid requiring PASSTHROUGH_SHADERS. match backend { wgpu::Backend::Vulkan => { let compiled = compile_wgsl(device); Shaders { ts: compiled.clone(), ms: compiled.clone(), fs: compiled.clone(), ts_name: "ts_main", ms_name: "ms_main", fs_name: "fs_main", } } wgpu::Backend::Dx12 => Shaders { ts: compile_hlsl(device, "Task", "as"), ms: compile_hlsl(device, "Mesh", "ms"), fs: compile_hlsl(device, "Frag", "ps"), ts_name: "main", ms_name: "main", fs_name: "main", }, wgpu::Backend::Metal => { let compiled = compile_msl(device); Shaders { ts: compiled.clone(), ms: compiled.clone(), fs: compiled.clone(), ts_name: "taskShader", ms_name: "meshShader", fs_name: "fragShader", } } _ => unreachable!(), } } pub struct Example { pipeline: wgpu::RenderPipeline, } impl crate::framework::Example for Example { fn init( config: &wgpu::SurfaceConfiguration, adapter: &wgpu::Adapter, device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { let Shaders { ts, ms, fs, ts_name, ms_name, fs_name, } = get_shaders(device, adapter.get_info().backend); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[], immediate_size: 0, }); let pipeline = device.create_mesh_pipeline(&wgpu::MeshPipelineDescriptor { label: None, layout: Some(&pipeline_layout), task: Some(wgpu::TaskState { module: &ts, entry_point: Some(ts_name), compilation_options: Default::default(), }), mesh: wgpu::MeshState { module: &ms, entry_point: Some(ms_name), compilation_options: Default::default(), }, fragment: Some(wgpu::FragmentState { module: &fs, entry_point: Some(fs_name), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), primitive: wgpu::PrimitiveState { cull_mode: Some(wgpu::Face::Back), ..Default::default() }, depth_stencil: None, multisample: Default::default(), multiview: None, cache: None, }); Self { pipeline } } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.1, g: 0.2, b: 0.3, a: 1.0, }), store: wgpu::StoreOp::Store, }, depth_slice: None, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.push_debug_group("Prepare data for draw."); rpass.set_pipeline(&self.pipeline); rpass.pop_debug_group(); rpass.insert_debug_marker("Draw!"); rpass.draw_mesh_tasks(1, 1, 1); } queue.submit(Some(encoder.finish())); } fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { Default::default() } fn required_features() -> wgpu::Features { wgpu::Features::EXPERIMENTAL_MESH_SHADER | wgpu::Features::PASSTHROUGH_SHADERS } fn required_limits() -> wgpu::Limits { wgpu::Limits::defaults().using_recommended_minimum_mesh_shader_values() } fn resize( &mut self, _config: &wgpu::SurfaceConfiguration, _device: &wgpu::Device, _queue: &wgpu::Queue, ) { // empty } fn update(&mut self, _event: winit::event::WindowEvent) { // empty } } pub fn main() { crate::framework::run::("mesh_shader"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "mesh_shader", image_path: "/examples/features/src/mesh_shader/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default() .features(wgpu::Features::EXPERIMENTAL_MESH_SHADER | wgpu::Features::PASSTHROUGH_SHADERS) .instance_flags(wgpu::InstanceFlags::advanced_debugging()) .limits(wgpu::Limits::defaults().using_recommended_minimum_mesh_shader_values()) .skip(wgpu_test::FailureCase { backends: None, // Skip Mesa because LLVMPIPE has what is believed to be a driver bug vendor: Some(0x10005), adapter: None, driver: None, reasons: vec![], behavior: wgpu_test::FailureBehavior::Ignore, }), comparisons: &[wgpu_test::ComparisonType::Mean(0.005)], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/mesh_shader/shader.hlsl ================================================ struct OutVertex { float4 Position : SV_POSITION; float4 Color: COLOR; }; struct OutPrimitive { float4 ColorMask : COLOR_MASK : PRIMITIVE; bool CullPrimitive: SV_CullPrimitive; }; struct InVertex { float4 Color: COLOR; }; struct InPrimitive { float4 ColorMask : COLOR_MASK : PRIMITIVE; }; struct PayloadData { float4 ColorMask; bool Visible; }; static const float4 positions[3] = {float4(0., 1.0, 0., 1.0), float4(-1.0, -1.0, 0., 1.0), float4(1.0, -1.0, 0., 1.0)}; static const float4 colors[3] = {float4(0., 1., 0., 1.), float4(0., 0., 1., 1.), float4(1., 0., 0., 1.)}; groupshared PayloadData outPayload; [numthreads(1, 1, 1)] void Task() { outPayload.ColorMask = float4(1.0, 1.0, 0.0, 1.0); outPayload.Visible = true; DispatchMesh(3, 1, 1, outPayload); } [outputtopology("triangle")] [numthreads(1, 1, 1)] void Mesh(out indices uint3 triangles[1], out vertices OutVertex vertices[3], out primitives OutPrimitive primitives[1], in payload PayloadData payload) { SetMeshOutputCounts(3, 1); vertices[0].Position = positions[0]; vertices[1].Position = positions[1]; vertices[2].Position = positions[2]; vertices[0].Color = colors[0] * payload.ColorMask; vertices[1].Color = colors[1] * payload.ColorMask; vertices[2].Color = colors[2] * payload.ColorMask; triangles[0] = uint3(0, 1, 2); primitives[0].ColorMask = float4(1.0, 0.0, 0.0, 1.0); primitives[0].CullPrimitive = !payload.Visible; } float4 Frag(InVertex vertex, InPrimitive primitive) : SV_Target { return vertex.Color * primitive.ColorMask; } ================================================ FILE: examples/features/src/mesh_shader/shader.metal ================================================ using namespace metal; struct OutVertex { float4 Position [[position]]; float4 Color [[user(locn0)]]; }; struct OutPrimitive { float4 ColorMask [[flat]] [[user(locn1)]]; bool CullPrimitive [[primitive_culled]]; }; struct InVertex { }; struct InPrimitive { float4 ColorMask [[flat]] [[user(locn1)]]; }; struct FragmentIn { float4 Color [[user(locn0)]]; float4 ColorMask [[flat]] [[user(locn1)]]; }; struct PayloadData { float4 ColorMask; bool Visible; }; using Meshlet = metal::mesh; constant float4 positions[3] = { float4(0.0, 1.0, 0.0, 1.0), float4(-1.0, -1.0, 0.0, 1.0), float4(1.0, -1.0, 0.0, 1.0) }; constant float4 colors[3] = { float4(0.0, 1.0, 0.0, 1.0), float4(0.0, 0.0, 1.0, 1.0), float4(1.0, 0.0, 0.0, 1.0) }; [[object]] void taskShader(uint3 tid [[thread_position_in_grid]], object_data PayloadData &outPayload [[payload]], mesh_grid_properties grid) { outPayload.ColorMask = float4(1.0, 1.0, 0.0, 1.0); outPayload.Visible = true; grid.set_threadgroups_per_grid(uint3(3, 1, 1)); } [[mesh]] void meshShader( object_data PayloadData const& payload [[payload]], Meshlet out ) { out.set_primitive_count(1); for(int i = 0;i < 3;i++) { OutVertex vert; vert.Position = positions[i]; vert.Color = colors[i] * payload.ColorMask; out.set_vertex(i, vert); out.set_index(i, i); } OutPrimitive prim; prim.ColorMask = float4(1.0, 0.0, 0.0, 1.0); prim.CullPrimitive = !payload.Visible; out.set_primitive(0, prim); } fragment float4 fragShader(FragmentIn data [[stage_in]]) { return data.Color * data.ColorMask; } ================================================ FILE: examples/features/src/mesh_shader/shader.wgsl ================================================ enable wgpu_mesh_shader; const positions = array( vec4(0., 1., 0., 1.), vec4(-1., -1., 0., 1.), vec4(1., -1., 0., 1.) ); const colors = array( vec4(0., 1., 0., 1.), vec4(0., 0., 1., 1.), vec4(1., 0., 0., 1.) ); struct TaskPayload { colorMask: vec4, visible: bool, } struct VertexOutput { @builtin(position) position: vec4, @location(0) color: vec4, } struct PrimitiveOutput { @builtin(triangle_indices) indices: vec3, @builtin(cull_primitive) cull: bool, @per_primitive @location(1) colorMask: vec4, } struct PrimitiveInput { @per_primitive @location(1) colorMask: vec4, } var taskPayload: TaskPayload; var workgroupData: f32; @task @payload(taskPayload) @workgroup_size(64) fn ts_main(@builtin(local_invocation_id) thread_id: vec3) -> @builtin(mesh_task_size) vec3 { if thread_id.x == 0 { workgroupData = 1.0; taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0); taskPayload.visible = true; return vec3(1, 1, 1); } return vec3(0, 0, 0); } struct MeshOutput { @builtin(vertices) vertices: array, @builtin(primitives) primitives: array, @builtin(vertex_count) vertex_count: u32, @builtin(primitive_count) primitive_count: u32, } var mesh_output: MeshOutput; @mesh(mesh_output) @payload(taskPayload) @workgroup_size(64) fn ms_main(@builtin(local_invocation_id) thread_id: vec3) { if thread_id.x == 0 { mesh_output.vertex_count = 3; mesh_output.primitive_count = 1; workgroupData = 2.0; mesh_output.primitives[0].indices = vec3(0, 1, 2); mesh_output.primitives[0].cull = !taskPayload.visible; mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } if thread_id.x < 3 { mesh_output.vertices[thread_id.x].position = positions[thread_id.x]; mesh_output.vertices[thread_id.x].color = colors[thread_id.x] * taskPayload.colorMask; } } @fragment fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { return vertex.color * primitive.colorMask; } ================================================ FILE: examples/features/src/mipmap/README.md ================================================ # mipmap This example shows how to generate and make use of mipmaps. ## To Run ``` cargo run --bin wgpu-examples mipmap ``` ## Screenshots ![Mip maps](./screenshot.png) ================================================ FILE: examples/features/src/mipmap/blit.wgsl ================================================ struct VertexOutput { @builtin(position) position: vec4, @location(0) tex_coords: vec2, }; // meant to be called with 3 vertex indices: 0, 1, 2 // draws one large triangle over the clip space like this: // (the asterisks represent the clip space bounds) //-1,1 1,1 // --------------------------------- // | * . // | * . // | * . // | * . // | * . // | * . // |*************** // | . 1,-1 // | . // | . // | . // | . // |. @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { var result: VertexOutput; let x = i32(vertex_index) / 2; let y = i32(vertex_index) & 1; let tc = vec2( f32(x) * 2.0, f32(y) * 2.0 ); result.position = vec4( tc.x * 2.0 - 1.0, 1.0 - tc.y * 2.0, 0.0, 1.0 ); result.tex_coords = tc; return result; } @group(0) @binding(0) var r_color: texture_2d; @group(0) @binding(1) var r_sampler: sampler; @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { return textureSample(r_color, r_sampler, vertex.tex_coords); } ================================================ FILE: examples/features/src/mipmap/draw.wgsl ================================================ struct VertexOutput { @builtin(position) position: vec4, @location(0) tex_coords: vec2, }; struct Locals { transform: mat4x4 }; @group(0) @binding(0) var r_data: Locals; @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { let pos = vec2( 100.0 * (1.0 - f32(vertex_index & 2u)), 1000.0 * f32(vertex_index & 1u) ); var result: VertexOutput; result.tex_coords = 0.05 * pos + vec2(0.5, 0.5); result.position = r_data.transform * vec4(pos, 0.0, 1.0); return result; } @group(0) @binding(1) var r_color: texture_2d; @group(0) @binding(2) var r_sampler: sampler; @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { return textureSample(r_color, r_sampler, vertex.tex_coords); } ================================================ FILE: examples/features/src/mipmap/mod.rs ================================================ use bytemuck::{Pod, Zeroable}; use std::f32::consts; use wgpu::util::DeviceExt; const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb; const MIP_LEVEL_COUNT: u32 = 10; const MIP_PASS_COUNT: u32 = MIP_LEVEL_COUNT - 1; const QUERY_FEATURES: wgpu::Features = { wgpu::Features::TIMESTAMP_QUERY .union(wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES) .union(wgpu::Features::PIPELINE_STATISTICS_QUERY) }; fn create_texels(size: usize, cx: f32, cy: f32) -> Vec { use std::iter; (0..size * size) .flat_map(|id| { // get high five for recognizing this ;) let mut x = 4.0 * (id % size) as f32 / (size - 1) as f32 - 2.0; let mut y = 2.0 * (id / size) as f32 / (size - 1) as f32 - 1.0; let mut count = 0; while count < 0xFF && x * x + y * y < 4.0 { let old_x = x; x = x * x - y * y + cx; y = 2.0 * old_x * y + cy; count += 1; } iter::once(0xFF - (count * 2) as u8) .chain(iter::once(0xFF - (count * 5) as u8)) .chain(iter::once(0xFF - (count * 13) as u8)) .chain(iter::once(u8::MAX)) }) .collect() } struct QuerySets { timestamp: wgpu::QuerySet, timestamp_period: f32, pipeline_statistics: wgpu::QuerySet, data_buffer: wgpu::Buffer, mapping_buffer: wgpu::Buffer, } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct TimestampData { start: u64, end: u64, } type TimestampQueries = [TimestampData; MIP_PASS_COUNT as usize]; type PipelineStatisticsQueries = [u64; MIP_PASS_COUNT as usize]; fn pipeline_statistics_offset() -> wgpu::BufferAddress { (size_of::() as wgpu::BufferAddress).max(wgpu::QUERY_RESOLVE_BUFFER_ALIGNMENT) } struct Example { bind_group: wgpu::BindGroup, uniform_buf: wgpu::Buffer, draw_pipeline: wgpu::RenderPipeline, } impl Example { fn generate_matrix(aspect_ratio: f32) -> glam::Mat4 { let projection = glam::Mat4::perspective_rh(consts::FRAC_PI_4, aspect_ratio, 1.0, 1000.0); let view = glam::Mat4::look_at_rh( glam::Vec3::new(0f32, 0.0, 10.0), glam::Vec3::new(0f32, 50.0, 0.0), glam::Vec3::Z, ); projection * view } fn generate_mipmaps( encoder: &mut wgpu::CommandEncoder, device: &wgpu::Device, texture: &wgpu::Texture, query_sets: &Option, mip_count: u32, ) { let shader = device.create_shader_module(wgpu::include_wgsl!("blit.wgsl")); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("blit"), layout: None, vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(TEXTURE_FORMAT.into())], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let bind_group_layout = pipeline.get_bind_group_layout(0); let sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("mip"), address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, mipmap_filter: wgpu::MipmapFilterMode::Nearest, ..Default::default() }); let views = (0..mip_count) .map(|mip| { texture.create_view(&wgpu::TextureViewDescriptor { label: Some("mip"), format: None, dimension: None, usage: None, aspect: wgpu::TextureAspect::All, base_mip_level: mip, mip_level_count: Some(1), base_array_layer: 0, array_layer_count: None, }) }) .collect::>(); for target_mip in 1..mip_count as usize { let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&views[target_mip - 1]), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler), }, ], label: None, }); let pipeline_query_index_base = target_mip as u32 - 1; let timestamp_query_index_base = (target_mip as u32 - 1) * 2; let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &views[target_mip], depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); if let Some(ref query_sets) = query_sets { rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base); rpass.begin_pipeline_statistics_query( &query_sets.pipeline_statistics, pipeline_query_index_base, ); } rpass.set_pipeline(&pipeline); rpass.set_bind_group(0, &bind_group, &[]); rpass.draw(0..3, 0..1); if let Some(ref query_sets) = query_sets { rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base + 1); rpass.end_pipeline_statistics_query(); } } if let Some(ref query_sets) = query_sets { let timestamp_query_count = MIP_PASS_COUNT * 2; encoder.resolve_query_set( &query_sets.timestamp, 0..timestamp_query_count, &query_sets.data_buffer, 0, ); encoder.resolve_query_set( &query_sets.pipeline_statistics, 0..MIP_PASS_COUNT, &query_sets.data_buffer, pipeline_statistics_offset(), ); } } } impl crate::framework::Example for Example { fn optional_features() -> wgpu::Features { QUERY_FEATURES } fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { let mut init_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); // Create the texture let size = 1 << MIP_PASS_COUNT; let texels = create_texels(size as usize, -0.8, 0.156); let texture_extent = wgpu::Extent3d { width: size, height: size, depth_or_array_layers: 1, }; let texture = device.create_texture(&wgpu::TextureDescriptor { size: texture_extent, mip_level_count: MIP_LEVEL_COUNT, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: TEXTURE_FORMAT, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, label: None, view_formats: &[], }); let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); //Note: we could use queue.write_texture instead, and this is what other // examples do, but here we want to show another way to do this. let temp_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Temporary Buffer"), contents: texels.as_slice(), usage: wgpu::BufferUsages::COPY_SRC, }); init_encoder.copy_buffer_to_texture( wgpu::TexelCopyBufferInfo { buffer: &temp_buf, layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(4 * size), rows_per_image: None, }, }, texture.as_image_copy(), texture_extent, ); // Create other resources let sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: None, address_mode_u: wgpu::AddressMode::Repeat, address_mode_v: wgpu::AddressMode::Repeat, address_mode_w: wgpu::AddressMode::Repeat, mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, mipmap_filter: wgpu::MipmapFilterMode::Linear, ..Default::default() }); let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32); let mx_ref: &[f32; 16] = mx_total.as_ref(); let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Uniform Buffer"), contents: bytemuck::cast_slice(mx_ref), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); // Create the render pipeline let shader = device.create_shader_module(wgpu::include_wgsl!("draw.wgsl")); let draw_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("draw"), layout: None, vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleStrip, front_face: wgpu::FrontFace::Ccw, cull_mode: Some(wgpu::Face::Back), ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); // Create bind group let bind_group_layout = draw_pipeline.get_bind_group_layout(0); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: uniform_buf.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&texture_view), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&sampler), }, ], label: None, }); // If both kinds of query are supported, use queries let query_sets = if device.features().contains(QUERY_FEATURES) { // For N total mips, it takes N - 1 passes to generate them, and we're measuring those. let mip_passes = MIP_LEVEL_COUNT - 1; // Create the timestamp query set. We need twice as many queries as we have passes, // as we need a query at the beginning and at the end of the operation. let timestamp = device.create_query_set(&wgpu::QuerySetDescriptor { label: None, count: mip_passes * 2, ty: wgpu::QueryType::Timestamp, }); // Timestamp queries use an device-specific timestamp unit. We need to figure out how many // nanoseconds go by for the timestamp to be incremented by one. The period is this value. let timestamp_period = queue.get_timestamp_period(); // We only need one pipeline statistics query per pass. let pipeline_statistics = device.create_query_set(&wgpu::QuerySetDescriptor { label: None, count: mip_passes, ty: wgpu::QueryType::PipelineStatistics( wgpu::PipelineStatisticsTypes::FRAGMENT_SHADER_INVOCATIONS, ), }); // This databuffer has to store all of the query results, 2 * passes timestamp queries // and 1 * passes statistics queries. Each query returns a u64 value. let buffer_size = pipeline_statistics_offset() + size_of::() as wgpu::BufferAddress; let data_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("query buffer"), size: buffer_size, usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: false, }); // Mapping buffer let mapping_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("query buffer"), size: buffer_size, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false, }); Some(QuerySets { timestamp, timestamp_period, pipeline_statistics, data_buffer, mapping_buffer, }) } else { None }; Self::generate_mipmaps( &mut init_encoder, device, &texture, &query_sets, MIP_LEVEL_COUNT, ); if let Some(ref query_sets) = query_sets { init_encoder.copy_buffer_to_buffer( &query_sets.data_buffer, 0, &query_sets.mapping_buffer, 0, query_sets.data_buffer.size(), ); } queue.submit(Some(init_encoder.finish())); if let Some(ref query_sets) = query_sets { // We can ignore the callback as we're about to wait for the device. query_sets .mapping_buffer .slice(..) .map_async(wgpu::MapMode::Read, |_| ()); // Wait for device to be done rendering mipmaps device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); // This is guaranteed to be ready. let timestamp_view = query_sets .mapping_buffer .slice(..size_of::() as wgpu::BufferAddress) .get_mapped_range(); let pipeline_stats_view = query_sets .mapping_buffer .slice(pipeline_statistics_offset()..) .get_mapped_range(); // Convert the raw data into a useful structure let timestamp_data: &TimestampQueries = bytemuck::from_bytes(×tamp_view); let pipeline_stats_data: &PipelineStatisticsQueries = bytemuck::from_bytes(&pipeline_stats_view); // Iterate over the data for (idx, (timestamp, pipeline)) in timestamp_data .iter() .zip(pipeline_stats_data.iter()) .enumerate() { // Figure out the timestamp differences and multiply by the period to get nanoseconds let nanoseconds = (timestamp.end - timestamp.start) as f32 * query_sets.timestamp_period; // Nanoseconds is a bit small, so lets use microseconds. let microseconds = nanoseconds / 1000.0; // Print the data! println!( "Generating mip level {} took {:.3} μs and called the fragment shader {} times", idx + 1, microseconds, pipeline ); } } Example { bind_group, uniform_buf, draw_pipeline, } } fn update(&mut self, _event: winit::event::WindowEvent) { //empty } fn resize( &mut self, config: &wgpu::SurfaceConfiguration, _device: &wgpu::Device, queue: &wgpu::Queue, ) { let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32); let mx_ref: &[f32; 16] = mx_total.as_ref(); queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(mx_ref)); } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let clear_color = wgpu::Color { r: 0.1, g: 0.2, b: 0.3, a: 1.0, }; let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(clear_color), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.draw_pipeline); rpass.set_bind_group(0, &self.bind_group, &[]); rpass.draw(0..4, 0..1); } queue.submit(Some(encoder.finish())); } } pub fn main() { crate::framework::run::("mipmap"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "mipmap", image_path: "/examples/features/src/mipmap/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], _phantom: std::marker::PhantomData::, }; #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST_QUERY: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "mipmap-query", image_path: "/examples/features/src/mipmap/screenshot_query.png", width: 1024, height: 768, optional_features: QUERY_FEATURES, base_test_parameters: wgpu_test::TestParameters::default(), // Somehow, this test on CI lavapipe reasonably often gets error of 0.025341, significantly higher // than the comparison we usually do with mean 0.005. This only happens when the query is used. comparisons: &[ wgpu_test::ComparisonType::Mean(0.03), wgpu_test::ComparisonType::Percentile { percentile: 0.99, threshold: 0.1, }, ], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/msaa_line/README.md ================================================ # msaa_line This example shows how to render lines using MSAA. ## To Run ``` cargo run --bin wgpu-examples msaa_line ``` ## Screenshots ![MSAA line](./screenshot.png) ================================================ FILE: examples/features/src/msaa_line/mod.rs ================================================ //! The parts of this example enabling MSAA are: //! * The render pipeline is created with a sample_count > 1. //! * A new texture with a sample_count > 1 is created and set as the color_attachment instead of the swapchain. //! * The swapchain is now specified as a resolve_target. //! //! The parts of this example enabling LineList are: //! * Set the primitive_topology to PrimitiveTopology::LineList. //! * Vertices and Indices describe the two points that make up a line. use std::iter; use bytemuck::{Pod, Zeroable}; use wgpu::util::DeviceExt; use winit::{ event::{ElementState, KeyEvent, WindowEvent}, keyboard::{Key, NamedKey}, }; #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Vertex { _pos: [f32; 2], _color: [f32; 4], } struct Example { bundle: wgpu::RenderBundle, shader: wgpu::ShaderModule, pipeline_layout: wgpu::PipelineLayout, multisampled_framebuffer: wgpu::TextureView, vertex_buffer: wgpu::Buffer, vertex_count: u32, sample_count: u32, rebuild_bundle: bool, config: wgpu::SurfaceConfiguration, max_sample_count: u32, } impl Example { fn create_bundle( device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, shader: &wgpu::ShaderModule, pipeline_layout: &wgpu::PipelineLayout, sample_count: u32, vertex_buffer: &wgpu::Buffer, vertex_count: u32, ) -> wgpu::RenderBundle { log::info!("sample_count: {sample_count}"); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(pipeline_layout), vertex: wgpu::VertexState { module: shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[wgpu::VertexBufferLayout { array_stride: size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x4], }], }, fragment: Some(wgpu::FragmentState { module: shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::LineList, front_face: wgpu::FrontFace::Ccw, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState { count: sample_count, ..Default::default() }, multiview_mask: None, cache: None, }); let mut encoder = device.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor { label: None, color_formats: &[Some(config.view_formats[0])], depth_stencil: None, sample_count, multiview: None, }); encoder.set_pipeline(&pipeline); encoder.set_vertex_buffer(0, vertex_buffer.slice(..)); encoder.draw(0..vertex_count, 0..1); encoder.finish(&wgpu::RenderBundleDescriptor { label: Some("main"), }) } fn create_multisampled_framebuffer( device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, sample_count: u32, ) -> wgpu::TextureView { let multisampled_texture_extent = wgpu::Extent3d { width: config.width, height: config.height, depth_or_array_layers: 1, }; let multisampled_frame_descriptor = &wgpu::TextureDescriptor { size: multisampled_texture_extent, mip_level_count: 1, sample_count, dimension: wgpu::TextureDimension::D2, format: config.view_formats[0], usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TRANSIENT, label: None, view_formats: &[], }; device .create_texture(multisampled_frame_descriptor) .create_view(&wgpu::TextureViewDescriptor::default()) } } impl crate::framework::Example for Example { fn optional_features() -> wgpu::Features { wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES } fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { log::info!("Press left/right arrow keys to change sample_count."); let sample_flags = _adapter .get_texture_format_features(config.view_formats[0]) .flags; let max_sample_count = { if sample_flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X16) { 16 } else if sample_flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X8) { 8 } else if sample_flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X4) { 4 } else if sample_flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X2) { 2 } else { 1 } }; let sample_count = max_sample_count; let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[], immediate_size: 0, }); let multisampled_framebuffer = Example::create_multisampled_framebuffer(device, config, sample_count); let mut vertex_data = vec![]; let max = 50; for i in 0..max { let percent = i as f32 / max as f32; let (sin, cos) = (percent * 2.0 * std::f32::consts::PI).sin_cos(); vertex_data.push(Vertex { _pos: [0.0, 0.0], _color: [1.0, -sin, cos, 1.0], }); vertex_data.push(Vertex { _pos: [1.0 * cos, 1.0 * sin], _color: [sin, -cos, 1.0, 1.0], }); } let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::cast_slice(&vertex_data), usage: wgpu::BufferUsages::VERTEX, }); let vertex_count = vertex_data.len() as u32; let bundle = Example::create_bundle( device, config, &shader, &pipeline_layout, sample_count, &vertex_buffer, vertex_count, ); Example { bundle, shader, pipeline_layout, multisampled_framebuffer, vertex_buffer, vertex_count, sample_count, max_sample_count, rebuild_bundle: false, config: config.clone(), } } #[expect(clippy::single_match)] fn update(&mut self, event: winit::event::WindowEvent) { match event { WindowEvent::KeyboardInput { event: KeyEvent { logical_key, state: ElementState::Pressed, .. }, .. } => match logical_key { // TODO: Switch back to full scans of possible options when we expose // supported sample counts to the user. Key::Named(NamedKey::ArrowLeft) => { if self.sample_count == self.max_sample_count { self.sample_count = 1; self.rebuild_bundle = true; } } Key::Named(NamedKey::ArrowRight) => { if self.sample_count == 1 { self.sample_count = self.max_sample_count; self.rebuild_bundle = true; } } _ => {} }, _ => {} } } fn resize( &mut self, config: &wgpu::SurfaceConfiguration, device: &wgpu::Device, _queue: &wgpu::Queue, ) { self.config = config.clone(); self.multisampled_framebuffer = Example::create_multisampled_framebuffer(device, config, self.sample_count); } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { if self.rebuild_bundle { self.bundle = Example::create_bundle( device, &self.config, &self.shader, &self.pipeline_layout, self.sample_count, &self.vertex_buffer, self.vertex_count, ); self.multisampled_framebuffer = Example::create_multisampled_framebuffer(device, &self.config, self.sample_count); self.rebuild_bundle = false; } let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let rpass_color_attachment = if self.sample_count == 1 { wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store, }, } } else { wgpu::RenderPassColorAttachment { view: &self.multisampled_framebuffer, depth_slice: None, resolve_target: Some(view), ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), // Storing pre-resolve MSAA data is unnecessary if it isn't used later. // On tile-based GPU, avoid store can reduce your app's memory footprint. store: wgpu::StoreOp::Discard, }, } }; encoder .begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(rpass_color_attachment)], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }) .execute_bundles(iter::once(&self.bundle)); } queue.submit(iter::once(encoder.finish())); } } pub fn main() { crate::framework::run::("msaa-line"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "msaa-line", image_path: "/examples/features/src/msaa_line/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, base_test_parameters: wgpu_test::TestParameters::default(), // There's a lot of natural variance so we check the weighted median too to differentiate // real failures from variance. comparisons: &[ wgpu_test::ComparisonType::Mean(0.065), wgpu_test::ComparisonType::Percentile { percentile: 0.5, threshold: 0.29, }, ], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/msaa_line/shader.wgsl ================================================ struct VertexOutput { @location(0) color: vec4, @builtin(position) position: vec4, }; @vertex fn vs_main( @location(0) position: vec2, @location(1) color: vec4, ) -> VertexOutput { var result: VertexOutput; result.position = vec4(position, 0.0, 1.0); result.color = color; return result; } @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { return vertex.color; } ================================================ FILE: examples/features/src/multiple_render_targets/README.md ================================================ # multiple_render_targets This example demonstrates how to use fragment shader to output to two color attachments of a renderpass simultaneously. The program generates a black and white ball-shaped texture from scratch and uses the fragment shader to draw it to two separate texture targets. The first texture target receives a red version of the texture and the second target receives a green version. Once the colored shapes have been drawn to two separate textures, they will be displayed on the screen by rendering each one of them to their own viewports that split the screen in half. ## To Run ``` cargo run --bin wgpu-examples multiple_render_targets ``` ## Screenshots ![Multi render target](./screenshot.png) ================================================ FILE: examples/features/src/multiple_render_targets/mod.rs ================================================ const EXAMPLE_NAME: &str = "multiple_render_targets"; /// Renderer that draws its outputs to two output texture targets at the same time. struct MultiTargetRenderer { pipeline: wgpu::RenderPipeline, bindgroup: wgpu::BindGroup, } fn create_ball_texture_data(width: usize, height: usize) -> Vec { // Creates black and white pixel data for the texture to sample. let mut img_data = Vec::with_capacity(width * height); let center: glam::Vec2 = glam::Vec2::new(width as f32 * 0.5, height as f32 * 0.5); let half_distance = width as f32 * 0.5; for y in 0..width { for x in 0..height { let cur_pos = glam::Vec2::new(x as f32, y as f32); let distance_to_center_normalized = 1.0 - (cur_pos - center).length() / half_distance; let val: u8 = (u8::MAX as f32 * distance_to_center_normalized) as u8; img_data.push(val) } } img_data } impl MultiTargetRenderer { fn create_image_texture( device: &wgpu::Device, queue: &wgpu::Queue, ) -> (wgpu::Texture, wgpu::TextureView) { const WIDTH: usize = 256; const HEIGHT: usize = 256; let size = wgpu::Extent3d { width: WIDTH as u32, height: HEIGHT as u32, depth_or_array_layers: 1, }; let texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("data texture"), size, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R8Unorm, // we need only the red channel for black/white image, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); let ball_texture_data = &create_ball_texture_data(WIDTH, HEIGHT); queue.write_texture( wgpu::TexelCopyTextureInfo { aspect: wgpu::TextureAspect::All, texture: &texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, }, ball_texture_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(WIDTH as u32), rows_per_image: Some(HEIGHT as u32), }, size, ); let view = texture.create_view(&wgpu::TextureViewDescriptor { label: Some("view"), format: None, dimension: Some(wgpu::TextureViewDimension::D2), usage: None, aspect: wgpu::TextureAspect::All, base_mip_level: 0, mip_level_count: None, base_array_layer: 0, array_layer_count: None, }); (texture, view) } fn init( device: &wgpu::Device, queue: &wgpu::Queue, shader: &wgpu::ShaderModule, target_states: &[Option], ) -> MultiTargetRenderer { let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { multisampled: false, sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, ], label: None, }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&texture_bind_group_layout)], immediate_size: 0, }); let sampler = device.create_sampler(&wgpu::SamplerDescriptor { address_mode_u: wgpu::AddressMode::Repeat, address_mode_v: wgpu::AddressMode::Repeat, address_mode_w: wgpu::AddressMode::Repeat, mag_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::MipmapFilterMode::Nearest, ..Default::default() }); let (_, texture_view) = Self::create_image_texture(device, queue); let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &texture_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&texture_view), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler), }, ], label: None, }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: shader, entry_point: Some("fs_multi_main"), // IMPORTANT: specify the color states for the outputs: compilation_options: Default::default(), targets: target_states, }), primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); Self { pipeline, bindgroup, } } fn draw( &self, encoder: &mut wgpu::CommandEncoder, targets: &[Option], ) { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: targets, depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, &self.bindgroup, &[]); rpass.draw(0..3, 0..1); } } /// Renderer that displays results on the screen. struct TargetRenderer { pipeline: wgpu::RenderPipeline, bindgroup_layout: wgpu::BindGroupLayout, bindgroup_left: wgpu::BindGroup, bindgroup_right: wgpu::BindGroup, sampler: wgpu::Sampler, } impl TargetRenderer { fn init( device: &wgpu::Device, shader: &wgpu::ShaderModule, format: wgpu::TextureFormat, targets: &TextureTargets, ) -> TargetRenderer { let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { multisampled: false, sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, ], label: None, }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&texture_bind_group_layout)], immediate_size: 0, }); let sampler = device.create_sampler(&wgpu::SamplerDescriptor { address_mode_u: wgpu::AddressMode::Repeat, address_mode_v: wgpu::AddressMode::Repeat, address_mode_w: wgpu::AddressMode::Repeat, mag_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::MipmapFilterMode::Nearest, ..Default::default() }); let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: shader, entry_point: Some("fs_display_main"), compilation_options: Default::default(), targets: &[Some(wgpu::ColorTargetState { format, blend: None, write_mask: Default::default(), })], }), primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let (bg_left, bg_right) = Self::create_bindgroups(device, &texture_bind_group_layout, targets, &sampler); Self { pipeline: render_pipeline, bindgroup_layout: texture_bind_group_layout, bindgroup_left: bg_left, bindgroup_right: bg_right, sampler, } } fn create_bindgroups( device: &wgpu::Device, layout: &wgpu::BindGroupLayout, texture_targets: &TextureTargets, sampler: &wgpu::Sampler, ) -> (wgpu::BindGroup, wgpu::BindGroup) { let left = device.create_bind_group(&wgpu::BindGroupDescriptor { layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&texture_targets.red_view), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(sampler), }, ], label: None, }); let right = device.create_bind_group(&wgpu::BindGroupDescriptor { layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&texture_targets.green_view), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(sampler), }, ], label: None, }); (left, right) } fn draw( &self, encoder: &mut wgpu::CommandEncoder, surface_view: &wgpu::TextureView, width: u32, height: u32, ) { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: surface_view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, &self.bindgroup_left, &[]); let height = height as f32; let half_w = width as f32 * 0.5; // draw results in two separate viewports that split the screen: rpass.set_viewport(0.0, 0.0, half_w, height, 0.0, 1.0); rpass.draw(0..3, 0..1); rpass.set_viewport(half_w, 0.0, half_w, height, 0.0, 1.0); rpass.set_bind_group(0, &self.bindgroup_right, &[]); rpass.draw(0..3, 0..1); } fn rebuild_resources(&mut self, device: &wgpu::Device, texture_targets: &TextureTargets) { (self.bindgroup_left, self.bindgroup_right) = Self::create_bindgroups( device, &self.bindgroup_layout, texture_targets, &self.sampler, ) } } struct TextureTargets { red_view: wgpu::TextureView, green_view: wgpu::TextureView, } impl TextureTargets { fn new( device: &wgpu::Device, format: wgpu::TextureFormat, width: u32, height: u32, ) -> TextureTargets { let size = wgpu::Extent3d { width, height, depth_or_array_layers: 1, }; let red_texture = device.create_texture(&wgpu::TextureDescriptor { label: None, size, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[format], }); let green_texture = device.create_texture(&wgpu::TextureDescriptor { label: None, size, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[format], }); let red_view = red_texture.create_view(&wgpu::TextureViewDescriptor { format: Some(format), dimension: Some(wgpu::TextureViewDimension::D2), ..wgpu::TextureViewDescriptor::default() }); let green_view = green_texture.create_view(&wgpu::TextureViewDescriptor { format: Some(format), dimension: Some(wgpu::TextureViewDimension::D2), ..wgpu::TextureViewDescriptor::default() }); TextureTargets { red_view, green_view, } } } struct Example { drawer: TargetRenderer, multi_target_renderer: MultiTargetRenderer, texture_targets: TextureTargets, screen_width: u32, screen_height: u32, } impl crate::framework::Example for Example { fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!( "shader.wgsl" ))), }); // Renderer that draws to 2 textures at the same time: let multi_target_renderer = MultiTargetRenderer::init( device, queue, &shader, // ColorTargetStates specify how the data will be written to the // output textures: &[ Some(wgpu::ColorTargetState { format: config.view_formats[0], blend: None, write_mask: Default::default(), }), Some(wgpu::ColorTargetState { format: config.view_formats[0], blend: None, write_mask: Default::default(), }), ], ); // create our target textures that will receive the simultaneous rendering: let texture_targets = TextureTargets::new(device, config.view_formats[0], config.width, config.height); // helper renderer that displays the results in 2 separate viewports: let drawer = TargetRenderer::init(device, &shader, config.view_formats[0], &texture_targets); Self { texture_targets, multi_target_renderer, drawer, screen_width: config.width, screen_height: config.height, } } fn resize( &mut self, config: &wgpu::SurfaceConfiguration, device: &wgpu::Device, _queue: &wgpu::Queue, ) { self.screen_width = config.width; self.screen_height = config.height; self.texture_targets = TextureTargets::new(device, config.view_formats[0], config.width, config.height); self.drawer.rebuild_resources(device, &self.texture_targets); } fn update(&mut self, _event: winit::event::WindowEvent) {} fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); // draw to 2 textures at the same time: self.multi_target_renderer.draw( &mut encoder, &[ Some(wgpu::RenderPassColorAttachment { view: &self.texture_targets.red_view, depth_slice: None, resolve_target: None, ops: Default::default(), }), Some(wgpu::RenderPassColorAttachment { view: &self.texture_targets.green_view, depth_slice: None, resolve_target: None, ops: Default::default(), }), ], ); // display results of the both drawn textures on screen: self.drawer .draw(&mut encoder, view, self.screen_width, self.screen_height); queue.submit(Some(encoder.finish())); } } pub fn main() { crate::framework::run::(EXAMPLE_NAME); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: EXAMPLE_NAME, image_path: "/examples/features/src/multiple_render_targets/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default(), // Bounded by lavapipe comparisons: &[wgpu_test::ComparisonType::Mean(0.014)], // Bounded by Apple A9 _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/multiple_render_targets/shader.wgsl ================================================ struct VertexOutput { @builtin(position) clip_position: vec4, @location(0) uv: vec2, }; @vertex fn vs_main( @builtin(vertex_index) vi: u32) -> VertexOutput { var out: VertexOutput; out.uv = vec2( f32((vi << 1u) & 2u), f32(vi & 2u), ); out.clip_position = vec4(out.uv * 2.0 - 1.0, 0.0, 1.0); out.uv.y = 1.0 - out.uv.y; return out; } @group(0) @binding(0) var image_texture: texture_2d; @group(0) @binding(1) var image_sampler: sampler; struct FragmentOutput { @location(0) red_target : vec4, @location(1) green_target : vec4, } @fragment fn fs_multi_main(vs: VertexOutput) -> FragmentOutput { let smp = textureSample(image_texture, image_sampler, vs.uv).x; var output: FragmentOutput; output.red_target = vec4(smp, 0.0, 0.0, 1.0); output.green_target = vec4(0.0, smp, 0.0, 1.0); return output; } @fragment fn fs_display_main(vs: VertexOutput) -> @location(0) vec4 { let smp = textureSample(image_texture, image_sampler, vs.uv).xyz; return vec4(smp, 1.0); } ================================================ FILE: examples/features/src/multiview/mod.rs ================================================ //! Renders different content to different layers of an array texture using multiview, //! a feature commonly used for VR rendering. use std::{num::NonZero, time::Instant}; use wgpu::util::TextureBlitter; const TEXTURE_SIZE: u32 = 512; // Change this to demonstrate non-contiguous multiview functionality const LAYER_MASK: u32 = 0b11; const NUM_LAYERS: u32 = 32 - LAYER_MASK.leading_zeros(); pub struct Example { pipeline: wgpu::RenderPipeline, entire_texture_view: wgpu::TextureView, views: Vec, start_time: Instant, blitter: TextureBlitter, } impl crate::framework::Example for Example { fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { let shader_src = include_str!("shader.wgsl"); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(shader_src.into()), }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, vertex: wgpu::VertexState { buffers: &[], module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), }, primitive: wgpu::PrimitiveState::default(), fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(wgpu::ColorTargetState { format: wgpu::TextureFormat::Rgba8Unorm, blend: None, write_mask: wgpu::ColorWrites::ALL, })], }), multiview_mask: NonZero::new(LAYER_MASK), multisample: Default::default(), layout: None, depth_stencil: None, cache: None, }); let texture = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: TEXTURE_SIZE, height: TEXTURE_SIZE, depth_or_array_layers: NUM_LAYERS, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); let entire_texture_view = texture.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(wgpu::TextureFormat::Rgba8Unorm), dimension: Some(wgpu::TextureViewDimension::D2Array), usage: Some(wgpu::TextureUsages::RENDER_ATTACHMENT), aspect: wgpu::TextureAspect::All, base_mip_level: 0, mip_level_count: None, base_array_layer: 0, array_layer_count: Some(NUM_LAYERS), }); let mut views = Vec::new(); for i in 0..NUM_LAYERS { views.push(texture.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(wgpu::TextureFormat::Rgba8Unorm), dimension: Some(wgpu::TextureViewDimension::D2), usage: Some(wgpu::TextureUsages::TEXTURE_BINDING), aspect: wgpu::TextureAspect::All, base_mip_level: 0, mip_level_count: None, base_array_layer: i, array_layer_count: Some(1), })); } let blitter = wgpu::util::TextureBlitter::new(device, config.format); Self { pipeline, entire_texture_view, views, blitter, start_time: Instant::now(), } } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &self.entire_texture_view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.02, g: 0.02, b: 0.02, a: 1.0, }), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: NonZero::new(LAYER_MASK), }); rpass.set_pipeline(&self.pipeline); rpass.draw(0..6, 0..1); } let layer = (Instant::now() - self.start_time).as_secs() % NUM_LAYERS as u64; self.blitter .copy(device, &mut encoder, &self.views[layer as usize], view); queue.submit(Some(encoder.finish())); } fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { Default::default() } fn required_features() -> wgpu::Features { wgpu::Features::MULTIVIEW | if !(LAYER_MASK + 1).is_power_of_two() { wgpu::Features::SELECTIVE_MULTIVIEW } else { wgpu::Features::empty() } } fn required_limits() -> wgpu::Limits { wgpu::Limits { max_multiview_view_count: NUM_LAYERS, ..Default::default() } } fn resize( &mut self, _config: &wgpu::SurfaceConfiguration, _device: &wgpu::Device, _queue: &wgpu::Queue, ) { // empty } fn update(&mut self, _event: winit::event::WindowEvent) { // empty } } pub fn main() { crate::framework::run::("multiview"); } ================================================ FILE: examples/features/src/multiview/shader.wgsl ================================================ const triangles = array(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0)); @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4f { return vec4f(triangles[vertex_index], 0.0, 1.0); } @fragment fn fs_main(@builtin(view_index) view_index: u32) -> @location(0) vec4f { return vec4f(f32(view_index) * 0.25 + 0.125, 1.0 - f32(view_index) * 0.25, 1.0 - 0.5 * f32(view_index), 1.0); } ================================================ FILE: examples/features/src/ray_cube_compute/README.md ================================================ # ray-cube This example renders a ray traced cube with hardware acceleration. A separate compute shader is used to perform the ray queries. ## To Run ``` cargo run --bin wgpu-examples ray_cube_compute ``` ## Screenshots ![Cube example](screenshot.png) ================================================ FILE: examples/features/src/ray_cube_compute/blit.wgsl ================================================ struct VertexOutput { @builtin(position) position: vec4, @location(0) tex_coords: vec2, }; // meant to be called with 3 vertex indices: 0, 1, 2 // draws one large triangle over the clip space like this: // (the asterisks represent the clip space bounds) //-1,1 1,1 // --------------------------------- // | * . // | * . // | * . // | * . // | * . // | * . // |*************** // | . 1,-1 // | . // | . // | . // | . // |. @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { var result: VertexOutput; let x = i32(vertex_index) / 2; let y = i32(vertex_index) & 1; let tc = vec2( f32(x) * 2.0, f32(y) * 2.0 ); result.position = vec4( tc.x * 2.0 - 1.0, 1.0 - tc.y * 2.0, 0.0, 1.0 ); result.tex_coords = tc; return result; } @group(0) @binding(0) var r_color: texture_2d; @group(0) @binding(1) var r_sampler: sampler; @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { return textureSample(r_color, r_sampler, vertex.tex_coords); } ================================================ FILE: examples/features/src/ray_cube_compute/mod.rs ================================================ use std::{borrow::Cow, iter, mem}; use bytemuck::{Pod, Zeroable}; use glam::{Affine3A, Mat4, Quat, Vec3}; use wgpu::util::DeviceExt; use wgpu::StoreOp; use crate::utils; // from cube #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Vertex { _pos: [f32; 4], _tex_coord: [f32; 2], } fn vertex(pos: [i8; 3], tc: [i8; 2]) -> Vertex { Vertex { _pos: [pos[0] as f32, pos[1] as f32, pos[2] as f32, 1.0], _tex_coord: [tc[0] as f32, tc[1] as f32], } } fn create_vertices() -> (Vec, Vec) { let vertex_data = [ // top (0, 0, 1) vertex([-1, -1, 1], [0, 0]), vertex([1, -1, 1], [1, 0]), vertex([1, 1, 1], [1, 1]), vertex([-1, 1, 1], [0, 1]), // bottom (0, 0, -1) vertex([-1, 1, -1], [1, 0]), vertex([1, 1, -1], [0, 0]), vertex([1, -1, -1], [0, 1]), vertex([-1, -1, -1], [1, 1]), // right (1, 0, 0) vertex([1, -1, -1], [0, 0]), vertex([1, 1, -1], [1, 0]), vertex([1, 1, 1], [1, 1]), vertex([1, -1, 1], [0, 1]), // left (-1, 0, 0) vertex([-1, -1, 1], [1, 0]), vertex([-1, 1, 1], [0, 0]), vertex([-1, 1, -1], [0, 1]), vertex([-1, -1, -1], [1, 1]), // front (0, 1, 0) vertex([1, 1, -1], [1, 0]), vertex([-1, 1, -1], [0, 0]), vertex([-1, 1, 1], [0, 1]), vertex([1, 1, 1], [1, 1]), // back (0, -1, 0) vertex([1, -1, 1], [0, 0]), vertex([-1, -1, 1], [1, 0]), vertex([-1, -1, -1], [1, 1]), vertex([1, -1, -1], [0, 1]), ]; let index_data: &[u16] = &[ 0, 1, 2, 2, 3, 0, // top 4, 5, 6, 6, 7, 4, // bottom 8, 9, 10, 10, 11, 8, // right 12, 13, 14, 14, 15, 12, // left 16, 17, 18, 18, 19, 16, // front 20, 21, 22, 22, 23, 20, // back ]; (vertex_data.to_vec(), index_data.to_vec()) } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Uniforms { view_inverse: Mat4, proj_inverse: Mat4, } #[inline] fn affine_to_rows(mat: &Affine3A) -> [f32; 12] { let row_0 = mat.matrix3.row(0); let row_1 = mat.matrix3.row(1); let row_2 = mat.matrix3.row(2); let translation = mat.translation; [ row_0.x, row_0.y, row_0.z, translation.x, row_1.x, row_1.y, row_1.z, translation.y, row_2.x, row_2.y, row_2.z, translation.z, ] } struct Example { rt_target: wgpu::Texture, #[expect(dead_code)] rt_view: wgpu::TextureView, #[expect(dead_code)] sampler: wgpu::Sampler, #[expect(dead_code)] uniform_buf: wgpu::Buffer, #[expect(dead_code)] vertex_buf: wgpu::Buffer, #[expect(dead_code)] index_buf: wgpu::Buffer, tlas: wgpu::Tlas, compute_pipeline: wgpu::ComputePipeline, compute_bind_group: wgpu::BindGroup, blit_pipeline: wgpu::RenderPipeline, blit_bind_group: wgpu::BindGroup, animation_timer: utils::AnimationTimer, } impl crate::framework::Example for Example { fn required_features() -> wgpu::Features { wgpu::Features::TEXTURE_BINDING_ARRAY | wgpu::Features::VERTEX_WRITABLE_STORAGE | wgpu::Features::EXPERIMENTAL_RAY_QUERY } fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { wgpu::DownlevelCapabilities { flags: wgpu::DownlevelFlags::COMPUTE_SHADERS, ..Default::default() } } fn required_limits() -> wgpu::Limits { wgpu::Limits::default().using_minimum_supported_acceleration_structure_values() } fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { let side_count = 8; let rt_target = device.create_texture(&wgpu::TextureDescriptor { label: Some("rt_target"), size: wgpu::Extent3d { width: config.width, height: config.height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING, view_formats: &[wgpu::TextureFormat::Rgba8Unorm], }); let rt_view = rt_target.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(wgpu::TextureFormat::Rgba8Unorm), dimension: Some(wgpu::TextureViewDimension::D2), usage: None, aspect: wgpu::TextureAspect::All, base_mip_level: 0, mip_level_count: None, base_array_layer: 0, array_layer_count: None, }); let sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("rt_sampler"), address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, mipmap_filter: wgpu::MipmapFilterMode::Nearest, ..Default::default() }); let uniforms = { let view = Mat4::look_at_rh(Vec3::new(0.0, 0.0, 2.5), Vec3::ZERO, Vec3::Y); let proj = Mat4::perspective_rh( 59.0_f32.to_radians(), config.width as f32 / config.height as f32, 0.001, 1000.0, ); Uniforms { view_inverse: view.inverse(), proj_inverse: proj.inverse(), } }; let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Uniform Buffer"), contents: bytemuck::cast_slice(&[uniforms]), usage: wgpu::BufferUsages::UNIFORM, }); let (vertex_data, index_data) = create_vertices(); let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::cast_slice(&vertex_data), usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::BLAS_INPUT, }); let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Index Buffer"), contents: bytemuck::cast_slice(&index_data), usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::BLAS_INPUT, }); let blas_geo_size_desc = wgpu::BlasTriangleGeometrySizeDescriptor { vertex_format: wgpu::VertexFormat::Float32x3, vertex_count: vertex_data.len() as u32, index_format: Some(wgpu::IndexFormat::Uint16), index_count: Some(index_data.len() as u32), flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE, }; let blas = device.create_blas( &wgpu::CreateBlasDescriptor { label: None, flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, update_mode: wgpu::AccelerationStructureUpdateMode::Build, }, wgpu::BlasGeometrySizeDescriptors::Triangles { descriptors: vec![blas_geo_size_desc.clone()], }, ); let mut tlas = device.create_tlas(&wgpu::CreateTlasDescriptor { label: None, flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, update_mode: wgpu::AccelerationStructureUpdateMode::Build, max_instances: side_count * side_count, }); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("rt_computer"), source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), }); let blit_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("blit"), source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("blit.wgsl"))), }); let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some("rt"), layout: None, module: &shader, entry_point: Some("main"), compilation_options: Default::default(), cache: None, }); let compute_bind_group_layout = compute_pipeline.get_bind_group_layout(0); let compute_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &compute_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&rt_view), }, wgpu::BindGroupEntry { binding: 1, resource: uniform_buf.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::AccelerationStructure(&tlas), }, ], }); let blit_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("blit"), layout: None, vertex: wgpu::VertexState { module: &blit_shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &blit_shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(config.format.into())], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let blit_bind_group_layout = blit_pipeline.get_bind_group_layout(0); let blit_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &blit_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&rt_view), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler), }, ], }); let dist = 3.0; for x in 0..side_count { for y in 0..side_count { tlas[(x + y * side_count) as usize] = Some(wgpu::TlasInstance::new( &blas, affine_to_rows(&Affine3A::from_rotation_translation( Quat::from_rotation_y(45.9_f32.to_radians()), Vec3 { x: x as f32 * dist, y: y as f32 * dist, z: -30.0, }, )), 0, 0xff, )); } } let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); encoder.build_acceleration_structures( iter::once(&wgpu::BlasBuildEntry { blas: &blas, geometry: wgpu::BlasGeometries::TriangleGeometries(vec![ wgpu::BlasTriangleGeometry { size: &blas_geo_size_desc, vertex_buffer: &vertex_buf, first_vertex: 0, vertex_stride: mem::size_of::() as u64, index_buffer: Some(&index_buf), first_index: Some(0), transform_buffer: None, transform_buffer_offset: None, }, ]), }), iter::once(&tlas), ); queue.submit(Some(encoder.finish())); Example { rt_target, rt_view, sampler, uniform_buf, vertex_buf, index_buf, tlas, compute_pipeline, compute_bind_group, blit_pipeline, blit_bind_group, animation_timer: utils::AnimationTimer::default(), } } fn update(&mut self, _event: winit::event::WindowEvent) { //empty } fn resize( &mut self, _config: &wgpu::SurfaceConfiguration, _device: &wgpu::Device, _queue: &wgpu::Queue, ) { } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let anim_time = self.animation_timer.time(); self.tlas[0].as_mut().unwrap().transform = affine_to_rows(&Affine3A::from_rotation_translation( Quat::from_euler( glam::EulerRot::XYZ, anim_time * 0.342, anim_time * 0.254, anim_time * 0.832, ), Vec3 { x: 0.0, y: 0.0, z: -6.0, }, )); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); encoder.build_acceleration_structures(iter::empty(), iter::once(&self.tlas)); { let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); cpass.set_pipeline(&self.compute_pipeline); cpass.set_bind_group(0, Some(&self.compute_bind_group), &[]); cpass.dispatch_workgroups(self.rt_target.width() / 8, self.rt_target.height() / 8, 1); } { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), store: StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.blit_pipeline); rpass.set_bind_group(0, Some(&self.blit_bind_group), &[]); rpass.draw(0..3, 0..1); } queue.submit(Some(encoder.finish())); } } pub fn main() { crate::framework::run::("ray-cube"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "ray_cube_compute", image_path: "/examples/features/src/ray_cube_compute/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default() // https://github.com/gfx-rs/wgpu/issues/9100 .expect_fail(wgpu_test::FailureCase::backend(wgpu::Backends::METAL)), comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/ray_cube_compute/shader.wgsl ================================================ /* The contents of the RayQuery struct are roughly as follows let RAY_FLAG_NONE = 0x00u; let RAY_FLAG_OPAQUE = 0x01u; let RAY_FLAG_NO_OPAQUE = 0x02u; let RAY_FLAG_TERMINATE_ON_FIRST_HIT = 0x04u; let RAY_FLAG_SKIP_CLOSEST_HIT_SHADER = 0x08u; let RAY_FLAG_CULL_BACK_FACING = 0x10u; let RAY_FLAG_CULL_FRONT_FACING = 0x20u; let RAY_FLAG_CULL_OPAQUE = 0x40u; let RAY_FLAG_CULL_NO_OPAQUE = 0x80u; let RAY_FLAG_SKIP_TRIANGLES = 0x100u; let RAY_FLAG_SKIP_AABBS = 0x200u; let RAY_QUERY_INTERSECTION_NONE = 0u; let RAY_QUERY_INTERSECTION_TRIANGLE = 1u; let RAY_QUERY_INTERSECTION_GENERATED = 2u; let RAY_QUERY_INTERSECTION_AABB = 3u; struct RayDesc { flags: u32, cull_mask: u32, t_min: f32, t_max: f32, origin: vec3, dir: vec3, } struct RayIntersection { kind: u32, t: f32, instance_custom_data: u32, instance_index: u32, sbt_record_offset: u32, geometry_index: u32, primitive_index: u32, barycentrics: vec2, front_face: bool, object_to_world: mat4x3, world_to_object: mat4x3, } */ enable wgpu_ray_query; struct Uniforms { view_inv: mat4x4, proj_inv: mat4x4, }; @group(0) @binding(0) var output: texture_storage_2d; @group(0) @binding(1) var uniforms: Uniforms; @group(0) @binding(2) var acc_struct: acceleration_structure; @compute @workgroup_size(8, 8) fn main(@builtin(global_invocation_id) global_id: vec3) { let target_size = textureDimensions(output); var color = vec4(vec2(global_id.xy) / vec2(target_size), 0.0, 1.0); let pixel_center = vec2(global_id.xy) + vec2(0.5); let in_uv = pixel_center/vec2(target_size.xy); let d = in_uv * 2.0 - 1.0; let origin = (uniforms.view_inv * vec4(0.0,0.0,0.0,1.0)).xyz; let temp = uniforms.proj_inv * vec4(d.x, d.y, 1.0, 1.0); let direction = (uniforms.view_inv * vec4(normalize(temp.xyz), 0.0)).xyz; var rq: ray_query; rayQueryInitialize(&rq, acc_struct, RayDesc(0u, 0xFFu, 0.1, 200.0, origin, direction)); rayQueryProceed(&rq); let intersection = rayQueryGetCommittedIntersection(&rq); if (intersection.kind != RAY_QUERY_INTERSECTION_NONE) { color = vec4(intersection.barycentrics, 1.0 - intersection.barycentrics.x - intersection.barycentrics.y, 1.0); } textureStore(output, global_id.xy, color); } ================================================ FILE: examples/features/src/ray_cube_fragment/README.md ================================================ # ray-cube This example renders a ray traced cube with hardware acceleration. ## To Run ``` cargo run --bin wgpu-examples ray_cube_fragment ``` ## Screenshots ![Cube example](screenshot.png) ================================================ FILE: examples/features/src/ray_cube_fragment/mod.rs ================================================ use crate::utils; use bytemuck::{Pod, Zeroable}; use glam::{Mat4, Quat, Vec3}; use std::ops::IndexMut; use std::{borrow::Cow, iter, mem}; use wgpu::util::DeviceExt; // from cube #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Vertex { _pos: [f32; 4], _tex_coord: [f32; 2], } fn vertex(pos: [i8; 3], tc: [i8; 2]) -> Vertex { Vertex { _pos: [pos[0] as f32, pos[1] as f32, pos[2] as f32, 1.0], _tex_coord: [tc[0] as f32, tc[1] as f32], } } fn create_vertices() -> (Vec, Vec) { let vertex_data = [ // top (0, 0, 1) vertex([-1, -1, 1], [0, 0]), vertex([1, -1, 1], [1, 0]), vertex([1, 1, 1], [1, 1]), vertex([-1, 1, 1], [0, 1]), // bottom (0, 0, -1) vertex([-1, 1, -1], [1, 0]), vertex([1, 1, -1], [0, 0]), vertex([1, -1, -1], [0, 1]), vertex([-1, -1, -1], [1, 1]), // right (1, 0, 0) vertex([1, -1, -1], [0, 0]), vertex([1, 1, -1], [1, 0]), vertex([1, 1, 1], [1, 1]), vertex([1, -1, 1], [0, 1]), // left (-1, 0, 0) vertex([-1, -1, 1], [1, 0]), vertex([-1, 1, 1], [0, 0]), vertex([-1, 1, -1], [0, 1]), vertex([-1, -1, -1], [1, 1]), // front (0, 1, 0) vertex([1, 1, -1], [1, 0]), vertex([-1, 1, -1], [0, 0]), vertex([-1, 1, 1], [0, 1]), vertex([1, 1, 1], [1, 1]), // back (0, -1, 0) vertex([1, -1, 1], [0, 0]), vertex([-1, -1, 1], [1, 0]), vertex([-1, -1, -1], [1, 1]), vertex([1, -1, -1], [0, 1]), ]; let index_data: &[u16] = &[ 0, 1, 2, 2, 3, 0, // top 4, 5, 6, 6, 7, 4, // bottom 8, 9, 10, 10, 11, 8, // right 12, 13, 14, 14, 15, 12, // left 16, 17, 18, 18, 19, 16, // front 20, 21, 22, 22, 23, 20, // back ]; (vertex_data.to_vec(), index_data.to_vec()) } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Uniforms { view_inverse: Mat4, proj_inverse: Mat4, } struct Example { uniforms: Uniforms, uniform_buf: wgpu::Buffer, blas: wgpu::Blas, tlas: wgpu::Tlas, pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, animation_timer: utils::AnimationTimer, } impl crate::framework::Example for Example { fn required_features() -> wgpu::Features { wgpu::Features::EXPERIMENTAL_RAY_QUERY } fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { wgpu::DownlevelCapabilities { flags: wgpu::DownlevelFlags::COMPUTE_SHADERS, ..Default::default() } } fn required_limits() -> wgpu::Limits { wgpu::Limits::default().using_minimum_supported_acceleration_structure_values() } fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { let side_count = 8; let uniforms = { let view = Mat4::look_at_rh(Vec3::new(0.0, 0.0, 2.5), Vec3::ZERO, Vec3::Y); let proj = Mat4::perspective_rh( 59.0_f32.to_radians(), config.width as f32 / config.height as f32, 0.001, 1000.0, ); Uniforms { view_inverse: view.inverse(), proj_inverse: proj.inverse(), } }; let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Uniform Buffer"), contents: bytemuck::cast_slice(&[uniforms]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); let (vertex_data, index_data) = create_vertices(); let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::cast_slice(&vertex_data), usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::BLAS_INPUT, }); let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Index Buffer"), contents: bytemuck::cast_slice(&index_data), usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::BLAS_INPUT, }); let blas_geo_size_desc = wgpu::BlasTriangleGeometrySizeDescriptor { vertex_format: wgpu::VertexFormat::Float32x3, vertex_count: vertex_data.len() as u32, index_format: Some(wgpu::IndexFormat::Uint16), index_count: Some(index_data.len() as u32), flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE, }; let blas = device.create_blas( &wgpu::CreateBlasDescriptor { label: None, flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, update_mode: wgpu::AccelerationStructureUpdateMode::Build, }, wgpu::BlasGeometrySizeDescriptors::Triangles { descriptors: vec![blas_geo_size_desc.clone()], }, ); let tlas = device.create_tlas(&wgpu::CreateTlasDescriptor { label: None, flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, update_mode: wgpu::AccelerationStructureUpdateMode::Build, max_instances: side_count * side_count, }); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: None, vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(config.format.into())], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let bind_group_layout = pipeline.get_bind_group_layout(0); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: uniform_buf.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::AccelerationStructure(&tlas), }, ], }); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); encoder.build_acceleration_structures( iter::once(&wgpu::BlasBuildEntry { blas: &blas, geometry: wgpu::BlasGeometries::TriangleGeometries(vec![ wgpu::BlasTriangleGeometry { size: &blas_geo_size_desc, vertex_buffer: &vertex_buf, first_vertex: 0, vertex_stride: mem::size_of::() as u64, index_buffer: Some(&index_buf), first_index: Some(0), transform_buffer: None, transform_buffer_offset: None, }, ]), }), // iter::empty(), iter::once(&tlas), ); queue.submit(Some(encoder.finish())); Example { uniforms, uniform_buf, blas, tlas, pipeline, bind_group, animation_timer: utils::AnimationTimer::default(), } } fn update(&mut self, _event: winit::event::WindowEvent) {} fn resize( &mut self, config: &wgpu::SurfaceConfiguration, _device: &wgpu::Device, queue: &wgpu::Queue, ) { let proj = Mat4::perspective_rh( 59.0_f32.to_radians(), config.width as f32 / config.height as f32, 0.001, 1000.0, ); self.uniforms.proj_inverse = proj.inverse(); queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(&[self.uniforms])); } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { // scene update { let dist = 12.0; let side_count = 8; let anim_time = self.animation_timer.time(); for x in 0..side_count { for y in 0..side_count { let instance = self.tlas.index_mut((x + y * side_count) as usize); let x = x as f32 / (side_count - 1) as f32; let y = y as f32 / (side_count - 1) as f32; let x = x * 2.0 - 1.0; let y = y * 2.0 - 1.0; let transform = Mat4::from_rotation_translation( Quat::from_euler( glam::EulerRot::XYZ, anim_time * 0.5 * 0.342, anim_time * 0.5 * 0.254, anim_time * 0.5 * 0.832, ), Vec3 { x: x * dist, y: y * dist, z: -24.0, }, ); let transform = transform.transpose().to_cols_array()[..12] .try_into() .unwrap(); *instance = Some(wgpu::TlasInstance::new(&self.blas, transform, 0, 0xff)); } } } let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); encoder.build_acceleration_structures(iter::empty(), iter::once(&self.tlas)); { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, Some(&self.bind_group), &[]); rpass.draw(0..3, 0..1); } queue.submit(Some(encoder.finish())); } } pub fn main() { crate::framework::run::("ray-cube"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "ray_cube_fragment", image_path: "/examples/features/src/ray_cube_fragment/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default() // https://github.com/gfx-rs/wgpu/issues/9100 .expect_fail(wgpu_test::FailureCase::backend(wgpu::Backends::METAL)), comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/ray_cube_fragment/shader.wgsl ================================================ enable wgpu_ray_query; struct VertexOutput { @builtin(position) position: vec4, @location(0) tex_coords: vec2, }; // meant to be called with 3 vertex indices: 0, 1, 2 // draws one large triangle over the clip space like this: // (the asterisks represent the clip space bounds) //-1,1 1,1 // --------------------------------- // | * . // | * . // | * . // | * . // | * . // | * . // |*************** // | . 1,-1 // | . // | . // | . // | . // |. @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { var result: VertexOutput; let x = i32(vertex_index) / 2; let y = i32(vertex_index) & 1; let tc = vec2( f32(x) * 2.0, f32(y) * 2.0 ); result.position = vec4( tc.x * 2.0 - 1.0, 1.0 - tc.y * 2.0, 0.0, 1.0 ); result.tex_coords = tc; return result; } struct Uniforms { view_inv: mat4x4, proj_inv: mat4x4, }; @group(0) @binding(0) var uniforms: Uniforms; @group(0) @binding(1) var acc_struct: acceleration_structure; @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { var color = vec4(vertex.tex_coords, 0.0, 1.0); let d = vertex.tex_coords * 2.0 - 1.0; let origin = (uniforms.view_inv * vec4(0.0,0.0,0.0,1.0)).xyz; let temp = uniforms.proj_inv * vec4(d.x, d.y, 1.0, 1.0); let direction = (uniforms.view_inv * vec4(normalize(temp.xyz), 0.0)).xyz; var rq: ray_query; rayQueryInitialize(&rq, acc_struct, RayDesc(0u, 0xFFu, 0.1, 200.0, origin, direction)); rayQueryProceed(&rq); let intersection = rayQueryGetCommittedIntersection(&rq); if (intersection.kind != RAY_QUERY_INTERSECTION_NONE) { color = vec4(intersection.barycentrics, 1.0 - intersection.barycentrics.x - intersection.barycentrics.y, 1.0); } return color; } ================================================ FILE: examples/features/src/ray_cube_normals/README.md ================================================ # ray-cube This example renders a ray traced cube with hardware acceleration. A separate compute shader is used to perform the ray queries. ## To Run ``` cargo run --bin wgpu-examples ray_cube_normals ``` ## Screenshots ![Cube example](screenshot.png) ================================================ FILE: examples/features/src/ray_cube_normals/blit.wgsl ================================================ struct VertexOutput { @builtin(position) position: vec4, @location(0) tex_coords: vec2, }; // meant to be called with 3 vertex indices: 0, 1, 2 // draws one large triangle over the clip space like this: // (the asterisks represent the clip space bounds) //-1,1 1,1 // --------------------------------- // | * . // | * . // | * . // | * . // | * . // | * . // |*************** // | . 1,-1 // | . // | . // | . // | . // |. @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { var result: VertexOutput; let x = i32(vertex_index) / 2; let y = i32(vertex_index) & 1; let tc = vec2( f32(x) * 2.0, f32(y) * 2.0 ); result.position = vec4( tc.x * 2.0 - 1.0, 1.0 - tc.y * 2.0, 0.0, 1.0 ); result.tex_coords = tc; return result; } @group(0) @binding(0) var r_color: texture_2d; @group(0) @binding(1) var r_sampler: sampler; @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { return textureSample(r_color, r_sampler, vertex.tex_coords); } ================================================ FILE: examples/features/src/ray_cube_normals/mod.rs ================================================ use std::{borrow::Cow, iter, mem}; use bytemuck::{Pod, Zeroable}; use glam::{Affine3A, Mat4, Quat, Vec3}; use wgpu::util::DeviceExt; use crate::utils; use wgpu::StoreOp; // from cube #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Vertex { _pos: [f32; 4], _tex_coord: [f32; 2], } fn vertex(pos: [i8; 3], tc: [i8; 2]) -> Vertex { Vertex { _pos: [pos[0] as f32, pos[1] as f32, pos[2] as f32, 1.0], _tex_coord: [tc[0] as f32, tc[1] as f32], } } fn create_vertices() -> (Vec, Vec) { let vertex_data = [ // top (0, 0, 1) vertex([-1, -1, 1], [0, 0]), vertex([1, -1, 1], [1, 0]), vertex([1, 1, 1], [1, 1]), vertex([-1, 1, 1], [0, 1]), // bottom (0, 0, -1) vertex([-1, 1, -1], [1, 0]), vertex([1, 1, -1], [0, 0]), vertex([1, -1, -1], [0, 1]), vertex([-1, -1, -1], [1, 1]), // right (1, 0, 0) vertex([1, -1, -1], [0, 0]), vertex([1, 1, -1], [1, 0]), vertex([1, 1, 1], [1, 1]), vertex([1, -1, 1], [0, 1]), // left (-1, 0, 0) vertex([-1, -1, 1], [1, 0]), vertex([-1, 1, 1], [0, 0]), vertex([-1, 1, -1], [0, 1]), vertex([-1, -1, -1], [1, 1]), // front (0, 1, 0) vertex([1, 1, -1], [1, 0]), vertex([-1, 1, -1], [0, 0]), vertex([-1, 1, 1], [0, 1]), vertex([1, 1, 1], [1, 1]), // back (0, -1, 0) vertex([1, -1, 1], [0, 0]), vertex([-1, -1, 1], [1, 0]), vertex([-1, -1, -1], [1, 1]), vertex([1, -1, -1], [0, 1]), ]; let index_data: &[u16] = &[ 0, 1, 2, 2, 3, 0, // top 4, 5, 6, 6, 7, 4, // bottom 8, 9, 10, 10, 11, 8, // right 12, 13, 14, 14, 15, 12, // left 16, 17, 18, 18, 19, 16, // front 20, 21, 22, 22, 23, 20, // back ]; (vertex_data.to_vec(), index_data.to_vec()) } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Uniforms { view_inverse: Mat4, proj_inverse: Mat4, } #[inline] fn affine_to_rows(mat: &Affine3A) -> [f32; 12] { let row_0 = mat.matrix3.row(0); let row_1 = mat.matrix3.row(1); let row_2 = mat.matrix3.row(2); let translation = mat.translation; [ row_0.x, row_0.y, row_0.z, translation.x, row_1.x, row_1.y, row_1.z, translation.y, row_2.x, row_2.y, row_2.z, translation.z, ] } struct Example { rt_target: wgpu::Texture, tlas: wgpu::Tlas, compute_pipeline: wgpu::ComputePipeline, compute_bind_group: wgpu::BindGroup, blit_pipeline: wgpu::RenderPipeline, blit_bind_group: wgpu::BindGroup, animation_timer: utils::AnimationTimer, } impl crate::framework::Example for Example { // Don't want srgb, so normals show up better. const SRGB: bool = false; fn required_features() -> wgpu::Features { wgpu::Features::EXPERIMENTAL_RAY_QUERY | wgpu::Features::EXPERIMENTAL_RAY_HIT_VERTEX_RETURN } fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { wgpu::DownlevelCapabilities { flags: wgpu::DownlevelFlags::COMPUTE_SHADERS, ..Default::default() } } fn required_limits() -> wgpu::Limits { wgpu::Limits::default().using_minimum_supported_acceleration_structure_values() } fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { let side_count = 8; let rt_target = device.create_texture(&wgpu::TextureDescriptor { label: Some("rt_target"), size: wgpu::Extent3d { width: config.width, height: config.height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING, view_formats: &[wgpu::TextureFormat::Rgba8Unorm], }); let rt_view = rt_target.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(wgpu::TextureFormat::Rgba8Unorm), dimension: Some(wgpu::TextureViewDimension::D2), usage: None, aspect: wgpu::TextureAspect::All, base_mip_level: 0, mip_level_count: None, base_array_layer: 0, array_layer_count: None, }); let sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("rt_sampler"), address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, mipmap_filter: wgpu::MipmapFilterMode::Nearest, ..Default::default() }); let uniforms = { let view = Mat4::look_at_rh(Vec3::new(0.0, 0.0, 2.5), Vec3::ZERO, Vec3::Y); let proj = Mat4::perspective_rh( 59.0_f32.to_radians(), config.width as f32 / config.height as f32, 0.001, 1000.0, ); Uniforms { view_inverse: view.inverse(), proj_inverse: proj.inverse(), } }; let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Uniform Buffer"), contents: bytemuck::cast_slice(&[uniforms]), usage: wgpu::BufferUsages::UNIFORM, }); let (vertex_data, index_data) = create_vertices(); let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::cast_slice(&vertex_data), usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::BLAS_INPUT, }); let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Index Buffer"), contents: bytemuck::cast_slice(&index_data), usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::BLAS_INPUT, }); let blas_geo_size_desc = wgpu::BlasTriangleGeometrySizeDescriptor { vertex_format: wgpu::VertexFormat::Float32x3, vertex_count: vertex_data.len() as u32, index_format: Some(wgpu::IndexFormat::Uint16), index_count: Some(index_data.len() as u32), flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE, }; let blas = device.create_blas( &wgpu::CreateBlasDescriptor { label: None, flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE | wgpu::AccelerationStructureFlags::ALLOW_RAY_HIT_VERTEX_RETURN, update_mode: wgpu::AccelerationStructureUpdateMode::Build, }, wgpu::BlasGeometrySizeDescriptors::Triangles { descriptors: vec![blas_geo_size_desc.clone()], }, ); let mut tlas = device.create_tlas(&wgpu::CreateTlasDescriptor { label: None, flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE | wgpu::AccelerationStructureFlags::ALLOW_RAY_HIT_VERTEX_RETURN, update_mode: wgpu::AccelerationStructureUpdateMode::Build, max_instances: side_count * side_count, }); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("rt_computer"), source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), }); let blit_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("blit"), source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("blit.wgsl"))), }); let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some("rt"), layout: None, module: &shader, entry_point: None, compilation_options: Default::default(), cache: None, }); let compute_bind_group_layout = compute_pipeline.get_bind_group_layout(0); let compute_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &compute_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&rt_view), }, wgpu::BindGroupEntry { binding: 1, resource: uniform_buf.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::AccelerationStructure(&tlas), }, ], }); let blit_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("blit"), layout: None, vertex: wgpu::VertexState { module: &blit_shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &blit_shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(config.format.into())], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let blit_bind_group_layout = blit_pipeline.get_bind_group_layout(0); let blit_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &blit_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&rt_view), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler), }, ], }); let dist = 3.0; for x in 0..side_count { for y in 0..side_count { tlas[(x + y * side_count) as usize] = Some(wgpu::TlasInstance::new( &blas, affine_to_rows(&Affine3A::from_rotation_translation( Quat::from_rotation_y(45.9_f32.to_radians()), Vec3 { x: x as f32 * dist, y: y as f32 * dist, z: -30.0, }, )), 0, 0xff, )); } } let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); encoder.build_acceleration_structures( iter::once(&wgpu::BlasBuildEntry { blas: &blas, geometry: wgpu::BlasGeometries::TriangleGeometries(vec![ wgpu::BlasTriangleGeometry { size: &blas_geo_size_desc, vertex_buffer: &vertex_buf, first_vertex: 0, vertex_stride: mem::size_of::() as u64, index_buffer: Some(&index_buf), first_index: Some(0), transform_buffer: None, transform_buffer_offset: None, }, ]), }), iter::once(&tlas), ); queue.submit(Some(encoder.finish())); Example { rt_target, tlas, compute_pipeline, compute_bind_group, blit_pipeline, blit_bind_group, animation_timer: utils::AnimationTimer::default(), } } fn update(&mut self, _event: winit::event::WindowEvent) { //empty } fn resize( &mut self, _config: &wgpu::SurfaceConfiguration, _device: &wgpu::Device, _queue: &wgpu::Queue, ) { } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let anim_time = self.animation_timer.time(); self.tlas[0].as_mut().unwrap().transform = affine_to_rows(&Affine3A::from_rotation_translation( Quat::from_euler( glam::EulerRot::XYZ, anim_time * 0.342, anim_time * 0.254, anim_time * 0.832, ), Vec3 { x: 0.0, y: 0.0, z: -6.0, }, )); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); encoder.build_acceleration_structures(iter::empty(), iter::once(&self.tlas)); { let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); cpass.set_pipeline(&self.compute_pipeline); cpass.set_bind_group(0, Some(&self.compute_bind_group), &[]); cpass.dispatch_workgroups(self.rt_target.width() / 8, self.rt_target.height() / 8, 1); } { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), store: StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.blit_pipeline); rpass.set_bind_group(0, Some(&self.blit_bind_group), &[]); rpass.draw(0..3, 0..1); } queue.submit(Some(encoder.finish())); } } pub fn main() { crate::framework::run::("ray-cube"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "ray_cube_normals", image_path: "/examples/features/src/ray_cube_normals/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default().expect_fail( // RADV does this fine. wgpu_test::FailureCase { backends: Some(wgpu::Backends::VULKAN), adapter: Some("AMD"), driver: Some("AMD proprietary driver"), ..wgpu_test::FailureCase::default() } .panic("Image data mismatch"), ), comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/ray_cube_normals/shader.wgsl ================================================ /* let RAY_FLAG_NONE = 0x00u; let RAY_FLAG_OPAQUE = 0x01u; let RAY_FLAG_NO_OPAQUE = 0x02u; let RAY_FLAG_TERMINATE_ON_FIRST_HIT = 0x04u; let RAY_FLAG_SKIP_CLOSEST_HIT_SHADER = 0x08u; let RAY_FLAG_CULL_BACK_FACING = 0x10u; let RAY_FLAG_CULL_FRONT_FACING = 0x20u; let RAY_FLAG_CULL_OPAQUE = 0x40u; let RAY_FLAG_CULL_NO_OPAQUE = 0x80u; let RAY_FLAG_SKIP_TRIANGLES = 0x100u; let RAY_FLAG_SKIP_AABBS = 0x200u; let RAY_QUERY_INTERSECTION_NONE = 0u; let RAY_QUERY_INTERSECTION_TRIANGLE = 1u; let RAY_QUERY_INTERSECTION_GENERATED = 2u; let RAY_QUERY_INTERSECTION_AABB = 4u; struct RayDesc { flags: u32, cull_mask: u32, t_min: f32, t_max: f32, origin: vec3, dir: vec3, } struct RayIntersection { kind: u32, t: f32, instance_custom_index: u32, instance_id: u32, sbt_record_offset: u32, geometry_index: u32, primitive_index: u32, barycentrics: vec2, front_face: bool, object_to_world: mat4x3, world_to_object: mat4x3, } */ enable wgpu_ray_query; enable wgpu_ray_query_vertex_return; struct Uniforms { view_inv: mat4x4, proj_inv: mat4x4, }; @group(0) @binding(0) var output: texture_storage_2d; @group(0) @binding(1) var uniforms: Uniforms; @group(0) @binding(2) var acc_struct: acceleration_structure; @compute @workgroup_size(8, 8) fn main(@builtin(global_invocation_id) global_id: vec3) { let target_size = textureDimensions(output); var color = vec4(vec2(global_id.xy) / vec2(target_size), 0.0, 1.0); let pixel_center = vec2(global_id.xy) + vec2(0.5); let in_uv = pixel_center/vec2(target_size.xy); let d = in_uv * 2.0 - 1.0; let origin = (uniforms.view_inv * vec4(0.0,0.0,0.0,1.0)).xyz; let temp = uniforms.proj_inv * vec4(d.x, d.y, 1.0, 1.0); let direction = (uniforms.view_inv * vec4(normalize(temp.xyz), 0.0)).xyz; var rq: ray_query; rayQueryInitialize(&rq, acc_struct, RayDesc(0u, 0xFFu, 0.1, 200.0, origin, direction)); rayQueryProceed(&rq); let intersection = rayQueryGetCommittedIntersection(&rq); if (intersection.kind != RAY_QUERY_INTERSECTION_NONE) { var positions : array = getCommittedHitVertexPositions(&rq); // The cube should change colour as it rotates because it's normals are changing let normals = intersection.object_to_world * vec4f(normalize(cross(positions[0] - positions[1], positions[0] - positions[2])), 0.0); // the y is negated because the texture coordinates are inverted color = vec4f(normals.x, -normals.y, normals.z, 1.0); } textureStore(output, global_id.xy, color); } ================================================ FILE: examples/features/src/ray_scene/cube.mtl ================================================ # Blender MTL File: 'None' # Material Count: 2 newmtl Material Ns 250.000000 Ka 1.000000 1.000000 1.000000 Kd 0.000000 0.009087 0.800000 Ks 0.500000 0.500000 0.500000 Ke 0.000000 0.000000 0.000000 Ni 1.450000 d 1.000000 illum 2 newmtl None Ns 500 Ka 0.8 0.8 0.8 Kd 0.8 0.8 0.8 Ks 0.8 0.8 0.8 d 1 illum 2 ================================================ FILE: examples/features/src/ray_scene/cube.obj ================================================ # Blender v3.3.1 OBJ File: '' # www.blender.org mtllib cube.mtl o Cube v 1.000000 1.000000 -1.000000 v 1.000000 -1.000000 -1.000000 v 1.000000 1.000000 1.000000 v 1.000000 -1.000000 1.000000 v -1.000000 1.000000 -1.000000 v -1.000000 -1.000000 -1.000000 v -1.000000 1.000000 1.000000 v -1.000000 -1.000000 1.000000 vt 0.875000 0.500000 vt 0.625000 0.750000 vt 0.625000 0.500000 vt 0.375000 1.000000 vt 0.375000 0.750000 vt 0.625000 0.000000 vt 0.375000 0.250000 vt 0.375000 0.000000 vt 0.375000 0.500000 vt 0.125000 0.750000 vt 0.125000 0.500000 vt 0.625000 0.250000 vt 0.875000 0.750000 vt 0.625000 1.000000 vn 0.0000 1.0000 0.0000 vn 0.0000 0.0000 1.0000 vn -1.0000 0.0000 0.0000 vn 0.0000 -1.0000 0.0000 vn 1.0000 0.0000 0.0000 vn 0.0000 0.0000 -1.0000 usemtl Material s off f 5/1/1 3/2/1 1/3/1 f 3/2/2 8/4/2 4/5/2 f 7/6/3 6/7/3 8/8/3 f 2/9/4 8/10/4 6/11/4 f 1/3/5 4/5/5 2/9/5 f 5/12/6 2/9/6 6/7/6 f 5/1/1 7/13/1 3/2/1 f 3/2/2 7/14/2 8/4/2 f 7/6/3 5/12/3 6/7/3 f 2/9/4 4/5/4 8/10/4 f 1/3/5 3/2/5 4/5/5 f 5/12/6 1/3/6 2/9/6 o Suzanne v 0.437500 2.679682 0.765625 v -0.437500 2.679682 0.765625 v 0.500000 2.609370 0.687500 v -0.500000 2.609370 0.687500 v 0.546875 2.570307 0.578125 v -0.546875 2.570307 0.578125 v 0.351562 2.492182 0.617188 v -0.351562 2.492182 0.617188 v 0.351562 2.546870 0.718750 v -0.351562 2.546870 0.718750 v 0.351562 2.648432 0.781250 v -0.351562 2.648432 0.781250 v 0.273438 2.679682 0.796875 v -0.273438 2.679682 0.796875 v 0.203125 2.609370 0.742188 v -0.203125 2.609370 0.742188 v 0.156250 2.570307 0.648438 v -0.156250 2.570307 0.648438 v 0.078125 2.757807 0.656250 v -0.078125 2.757807 0.656250 v 0.140625 2.757807 0.742188 v -0.140625 2.757807 0.742188 v 0.242188 2.757807 0.796875 v -0.242188 2.757807 0.796875 v 0.273438 2.843745 0.796875 v -0.273438 2.843745 0.796875 v 0.203125 2.906245 0.742188 v -0.203125 2.906245 0.742188 v 0.156250 2.953120 0.648438 v -0.156250 2.953120 0.648438 v 0.351562 3.031245 0.617188 v -0.351562 3.031245 0.617188 v 0.351562 2.968745 0.718750 v -0.351562 2.968745 0.718750 v 0.351562 2.874995 0.781250 v -0.351562 2.874995 0.781250 v 0.437500 2.843745 0.765625 v -0.437500 2.843745 0.765625 v 0.500000 2.906245 0.687500 v -0.500000 2.906245 0.687500 v 0.546875 2.953120 0.578125 v -0.546875 2.953120 0.578125 v 0.625000 2.757807 0.562500 v -0.625000 2.757807 0.562500 v 0.562500 2.757807 0.671875 v -0.562500 2.757807 0.671875 v 0.468750 2.757807 0.757812 v -0.468750 2.757807 0.757812 v 0.476562 2.757807 0.773438 v -0.476562 2.757807 0.773438 v 0.445312 2.851557 0.781250 v -0.445312 2.851557 0.781250 v 0.351562 2.890620 0.804688 v -0.351562 2.890620 0.804688 v 0.265625 2.851557 0.820312 v -0.265625 2.851557 0.820312 v 0.226562 2.757807 0.820312 v -0.226562 2.757807 0.820312 v 0.265625 2.671870 0.820312 v -0.265625 2.671870 0.820312 v 0.351562 2.757807 0.828125 v -0.351562 2.757807 0.828125 v 0.351562 2.632807 0.804688 v -0.351562 2.632807 0.804688 v 0.445312 2.671870 0.781250 v -0.445312 2.671870 0.781250 v 0.000000 2.945307 0.742188 v 0.000000 2.867182 0.820312 v 0.000000 1.835932 0.734375 v 0.000000 2.195307 0.781250 v 0.000000 2.328120 0.796875 v 0.000000 1.742182 0.718750 v 0.000000 2.921870 0.601562 v 0.000000 3.085932 0.570312 v 0.000000 3.414057 -0.546875 v 0.000000 3.078120 -0.851562 v 0.000000 2.585932 -0.828125 v 0.000000 2.132807 -0.351562 v 0.203125 2.328120 0.562500 v -0.203125 2.328120 0.562500 v 0.312500 2.078120 0.570312 v -0.312500 2.078120 0.570312 v 0.351562 1.820307 0.570312 v -0.351562 1.820307 0.570312 v 0.367188 1.624995 0.531250 v -0.367188 1.624995 0.531250 v 0.328125 1.570307 0.523438 v -0.328125 1.570307 0.523438 v 0.179688 1.546870 0.554688 v -0.179688 1.546870 0.554688 v 0.000000 1.531245 0.578125 v 0.437500 2.374995 0.531250 v -0.437500 2.374995 0.531250 v 0.632812 2.476557 0.539062 v -0.632812 2.476557 0.539062 v 0.828125 2.664057 0.445312 v -0.828125 2.664057 0.445312 v 0.859375 2.945307 0.593750 v -0.859375 2.945307 0.593750 v 0.710938 2.999995 0.625000 v -0.710938 2.999995 0.625000 v 0.492188 3.117182 0.687500 v -0.492188 3.117182 0.687500 v 0.320312 3.273432 0.734375 v -0.320312 3.273432 0.734375 v 0.156250 3.234370 0.757812 v -0.156250 3.234370 0.757812 v 0.062500 3.007807 0.750000 v -0.062500 3.007807 0.750000 v 0.164062 2.929682 0.773438 v -0.164062 2.929682 0.773438 v 0.125000 2.820307 0.765625 v -0.125000 2.820307 0.765625 v 0.203125 2.609370 0.742188 v -0.203125 2.609370 0.742188 v 0.375000 2.531245 0.703125 v -0.375000 2.531245 0.703125 v 0.492188 2.578120 0.671875 v -0.492188 2.578120 0.671875 v 0.625000 2.703120 0.648438 v -0.625000 2.703120 0.648438 v 0.640625 2.812495 0.648438 v -0.640625 2.812495 0.648438 v 0.601562 2.890620 0.664062 v -0.601562 2.890620 0.664062 v 0.429688 2.953120 0.718750 v -0.429688 2.953120 0.718750 v 0.250000 2.984370 0.757812 v -0.250000 2.984370 0.757812 v 0.000000 1.749995 0.734375 v 0.109375 1.796870 0.734375 v -0.109375 1.796870 0.734375 v 0.117188 1.679682 0.710938 v -0.117188 1.679682 0.710938 v 0.062500 1.632807 0.695312 v -0.062500 1.632807 0.695312 v 0.000000 1.624995 0.687500 v 0.000000 2.320307 0.750000 v 0.000000 2.374995 0.742188 v 0.101562 2.367182 0.742188 v -0.101562 2.367182 0.742188 v 0.125000 2.289057 0.750000 v -0.125000 2.289057 0.750000 v 0.085938 2.226557 0.742188 v -0.085938 2.226557 0.742188 v 0.398438 2.468745 0.671875 v -0.398438 2.468745 0.671875 v 0.617188 2.570307 0.625000 v -0.617188 2.570307 0.625000 v 0.726562 2.718745 0.601562 v -0.726562 2.718745 0.601562 v 0.742188 2.890620 0.656250 v -0.742188 2.890620 0.656250 v 0.687500 2.929682 0.726562 v -0.687500 2.929682 0.726562 v 0.437500 3.062495 0.796875 v -0.437500 3.062495 0.796875 v 0.312500 3.156245 0.835938 v -0.312500 3.156245 0.835938 v 0.203125 3.132807 0.851562 v -0.203125 3.132807 0.851562 v 0.101562 2.945307 0.843750 v -0.101562 2.945307 0.843750 v 0.125000 2.414057 0.812500 v -0.125000 2.414057 0.812500 v 0.210938 2.070307 0.710938 v -0.210938 2.070307 0.710938 v 0.250000 1.812495 0.687500 v -0.250000 1.812495 0.687500 v 0.265625 1.695307 0.664062 v -0.265625 1.695307 0.664062 v 0.234375 1.601557 0.632812 v -0.234375 1.601557 0.632812 v 0.164062 1.585932 0.632812 v -0.164062 1.585932 0.632812 v 0.000000 1.570307 0.640625 v 0.000000 2.562495 0.726562 v 0.000000 2.726557 0.765625 v 0.328125 2.992182 0.742188 v -0.328125 2.992182 0.742188 v 0.164062 2.656245 0.750000 v -0.164062 2.656245 0.750000 v 0.132812 2.726557 0.757812 v -0.132812 2.726557 0.757812 v 0.117188 1.828120 0.734375 v -0.117188 1.828120 0.734375 v 0.078125 2.070307 0.750000 v -0.078125 2.070307 0.750000 v 0.000000 2.070307 0.750000 v 0.000000 2.187495 0.742188 v 0.093750 2.242182 0.781250 v -0.093750 2.242182 0.781250 v 0.132812 2.289057 0.796875 v -0.132812 2.289057 0.796875 v 0.109375 2.382807 0.781250 v -0.109375 2.382807 0.781250 v 0.039062 2.390620 0.781250 v -0.039062 2.390620 0.781250 v 0.000000 2.312495 0.828125 v 0.046875 2.367182 0.812500 v -0.046875 2.367182 0.812500 v 0.093750 2.359370 0.812500 v -0.093750 2.359370 0.812500 v 0.109375 2.289057 0.828125 v -0.109375 2.289057 0.828125 v 0.078125 2.265620 0.804688 v -0.078125 2.265620 0.804688 v 0.000000 2.226557 0.804688 v 0.257812 2.203120 0.554688 v -0.257812 2.203120 0.554688 v 0.164062 2.273432 0.710938 v -0.164062 2.273432 0.710938 v 0.179688 2.203120 0.710938 v -0.179688 2.203120 0.710938 v 0.234375 2.265620 0.554688 v -0.234375 2.265620 0.554688 v 0.000000 1.640620 0.687500 v 0.046875 1.648432 0.687500 v -0.046875 1.648432 0.687500 v 0.093750 1.695307 0.710938 v -0.093750 1.695307 0.710938 v 0.093750 1.773432 0.726562 v -0.093750 1.773432 0.726562 v 0.000000 1.734370 0.656250 v 0.093750 1.765620 0.664062 v -0.093750 1.765620 0.664062 v 0.093750 1.703120 0.640625 v -0.093750 1.703120 0.640625 v 0.046875 1.664057 0.632812 v -0.046875 1.664057 0.632812 v 0.000000 1.656245 0.632812 v 0.171875 2.734370 0.781250 v -0.171875 2.734370 0.781250 v 0.187500 2.671870 0.773438 v -0.187500 2.671870 0.773438 v 0.335938 2.945307 0.757812 v -0.335938 2.945307 0.757812 v 0.273438 2.937495 0.773438 v -0.273438 2.937495 0.773438 v 0.421875 2.914057 0.773438 v -0.421875 2.914057 0.773438 v 0.562500 2.867182 0.695312 v -0.562500 2.867182 0.695312 v 0.585938 2.804682 0.687500 v -0.585938 2.804682 0.687500 v 0.578125 2.710932 0.679688 v -0.578125 2.710932 0.679688 v 0.476562 2.617182 0.718750 v -0.476562 2.617182 0.718750 v 0.375000 2.578120 0.742188 v -0.375000 2.578120 0.742188 v 0.226562 2.624995 0.781250 v -0.226562 2.624995 0.781250 v 0.179688 2.812495 0.781250 v -0.179688 2.812495 0.781250 v 0.210938 2.890620 0.781250 v -0.210938 2.890620 0.781250 v 0.234375 2.874995 0.757812 v -0.234375 2.874995 0.757812 v 0.195312 2.812495 0.757812 v -0.195312 2.812495 0.757812 v 0.242188 2.640620 0.757812 v -0.242188 2.640620 0.757812 v 0.375000 2.601557 0.726562 v -0.375000 2.601557 0.726562 v 0.460938 2.632807 0.703125 v -0.460938 2.632807 0.703125 v 0.546875 2.726557 0.671875 v -0.546875 2.726557 0.671875 v 0.554688 2.796870 0.671875 v -0.554688 2.796870 0.671875 v 0.531250 2.851557 0.679688 v -0.531250 2.851557 0.679688 v 0.414062 2.906245 0.750000 v -0.414062 2.906245 0.750000 v 0.281250 2.914057 0.765625 v -0.281250 2.914057 0.765625 v 0.335938 2.921870 0.750000 v -0.335938 2.921870 0.750000 v 0.203125 2.687495 0.750000 v -0.203125 2.687495 0.750000 v 0.195312 2.742182 0.750000 v -0.195312 2.742182 0.750000 v 0.109375 2.976557 0.609375 v -0.109375 2.976557 0.609375 v 0.195312 3.179682 0.617188 v -0.195312 3.179682 0.617188 v 0.335938 3.203120 0.593750 v -0.335938 3.203120 0.593750 v 0.484375 3.070307 0.554688 v -0.484375 3.070307 0.554688 v 0.679688 2.968745 0.492188 v -0.679688 2.968745 0.492188 v 0.796875 2.921870 0.460938 v -0.796875 2.921870 0.460938 v 0.773438 2.679682 0.375000 v -0.773438 2.679682 0.375000 v 0.601562 2.515620 0.414062 v -0.601562 2.515620 0.414062 v 0.437500 2.421870 0.468750 v -0.437500 2.421870 0.468750 v 0.000000 3.414057 0.289062 v 0.000000 3.499995 -0.078125 v 0.000000 2.320307 -0.671875 v 0.000000 2.054682 0.187500 v 0.000000 1.539057 0.460938 v 0.000000 1.710932 0.343750 v 0.000000 1.945307 0.320312 v 0.000000 2.031245 0.281250 v 0.851562 2.749995 0.054688 v -0.851562 2.749995 0.054688 v 0.859375 2.835932 -0.046875 v -0.859375 2.835932 -0.046875 v 0.773438 2.781245 -0.437500 v -0.773438 2.781245 -0.437500 v 0.460938 2.953120 -0.703125 v -0.460938 2.953120 -0.703125 v 0.734375 2.468745 0.070312 v -0.734375 2.468745 0.070312 v 0.593750 2.390620 -0.164062 v -0.593750 2.390620 -0.164062 v 0.640625 2.507807 -0.429688 v -0.640625 2.507807 -0.429688 v 0.335938 2.570307 -0.664062 v -0.335938 2.570307 -0.664062 v 0.234375 2.164057 0.406250 v -0.234375 2.164057 0.406250 v 0.179688 2.101557 0.257812 v -0.179688 2.101557 0.257812 v 0.289062 1.804682 0.382812 v -0.289062 1.804682 0.382812 v 0.250000 2.015620 0.390625 v -0.250000 2.015620 0.390625 v 0.328125 1.601557 0.398438 v -0.328125 1.601557 0.398438 v 0.140625 1.757807 0.367188 v -0.140625 1.757807 0.367188 v 0.125000 1.976557 0.359375 v -0.125000 1.976557 0.359375 v 0.164062 1.570307 0.437500 v -0.164062 1.570307 0.437500 v 0.218750 2.234370 0.429688 v -0.218750 2.234370 0.429688 v 0.210938 2.289057 0.468750 v -0.210938 2.289057 0.468750 v 0.203125 2.343745 0.500000 v -0.203125 2.343745 0.500000 v 0.210938 2.124995 0.164062 v -0.210938 2.124995 0.164062 v 0.296875 2.203120 -0.265625 v -0.296875 2.203120 -0.265625 v 0.343750 2.367182 -0.539062 v -0.343750 2.367182 -0.539062 v 0.453125 3.382807 -0.382812 v -0.453125 3.382807 -0.382812 v 0.453125 3.445307 -0.070312 v -0.453125 3.445307 -0.070312 v 0.453125 3.367182 0.234375 v -0.453125 3.367182 0.234375 v 0.460938 3.039057 0.429688 v -0.460938 3.039057 0.429688 v 0.726562 2.921870 0.335938 v -0.726562 2.921870 0.335938 v 0.632812 2.968745 0.281250 v -0.632812 2.968745 0.281250 v 0.640625 3.218745 0.054688 v -0.640625 3.218745 0.054688 v 0.796875 3.078120 0.125000 v -0.796875 3.078120 0.125000 v 0.796875 3.132807 -0.117188 v -0.796875 3.132807 -0.117188 v 0.640625 3.265620 -0.195312 v -0.640625 3.265620 -0.195312 v 0.640625 3.195307 -0.445312 v -0.640625 3.195307 -0.445312 v 0.796875 3.054682 -0.359375 v -0.796875 3.054682 -0.359375 v 0.617188 2.843745 -0.585938 v -0.617188 2.843745 -0.585938 v 0.484375 2.539057 -0.546875 v -0.484375 2.539057 -0.546875 v 0.820312 2.843745 -0.203125 v -0.820312 2.843745 -0.203125 v 0.406250 2.343745 0.148438 v -0.406250 2.343745 0.148438 v 0.429688 2.320307 -0.210938 v -0.429688 2.320307 -0.210938 v 0.890625 2.921870 -0.234375 v -0.890625 2.921870 -0.234375 v 0.773438 2.374995 -0.125000 v -0.773438 2.374995 -0.125000 v 1.039062 2.414057 -0.328125 v -1.039062 2.414057 -0.328125 v 1.281250 2.570307 -0.429688 v -1.281250 2.570307 -0.429688 v 1.351562 2.835932 -0.421875 v -1.351562 2.835932 -0.421875 v 1.234375 3.023432 -0.421875 v -1.234375 3.023432 -0.421875 v 1.023438 2.992182 -0.312500 v -1.023438 2.992182 -0.312500 v 1.015625 2.929682 -0.289062 v -1.015625 2.929682 -0.289062 v 1.187500 2.953120 -0.390625 v -1.187500 2.953120 -0.390625 v 1.265625 2.804682 -0.406250 v -1.265625 2.804682 -0.406250 v 1.210938 2.593745 -0.406250 v -1.210938 2.593745 -0.406250 v 1.031250 2.476557 -0.304688 v -1.031250 2.476557 -0.304688 v 0.828125 2.445307 -0.132812 v -0.828125 2.445307 -0.132812 v 0.921875 2.874995 -0.218750 v -0.921875 2.874995 -0.218750 v 0.945312 2.820307 -0.289062 v -0.945312 2.820307 -0.289062 v 0.882812 2.492182 -0.210938 v -0.882812 2.492182 -0.210938 v 1.039062 2.515620 -0.367188 v -1.039062 2.515620 -0.367188 v 1.187500 2.609370 -0.445312 v -1.187500 2.609370 -0.445312 v 1.234375 2.765620 -0.445312 v -1.234375 2.765620 -0.445312 v 1.171875 2.874995 -0.437500 v -1.171875 2.874995 -0.437500 v 1.023438 2.859370 -0.359375 v -1.023438 2.859370 -0.359375 v 0.843750 2.804682 -0.210938 v -0.843750 2.804682 -0.210938 v 0.835938 2.687495 -0.273438 v -0.835938 2.687495 -0.273438 v 0.757812 2.609370 -0.273438 v -0.757812 2.609370 -0.273438 v 0.820312 2.601557 -0.273438 v -0.820312 2.601557 -0.273438 v 0.843750 2.531245 -0.273438 v -0.843750 2.531245 -0.273438 v 0.812500 2.499995 -0.273438 v -0.812500 2.499995 -0.273438 v 0.726562 2.515620 -0.070312 v -0.726562 2.515620 -0.070312 v 0.718750 2.492182 -0.171875 v -0.718750 2.492182 -0.171875 v 0.718750 2.554682 -0.187500 v -0.718750 2.554682 -0.187500 v 0.796875 2.718745 -0.210938 v -0.796875 2.718745 -0.210938 v 0.890625 2.757807 -0.265625 v -0.890625 2.757807 -0.265625 v 0.890625 2.749995 -0.320312 v -0.890625 2.749995 -0.320312 v 0.812500 2.499995 -0.320312 v -0.812500 2.499995 -0.320312 v 0.851562 2.531245 -0.320312 v -0.851562 2.531245 -0.320312 v 0.828125 2.593745 -0.320312 v -0.828125 2.593745 -0.320312 v 0.765625 2.609370 -0.320312 v -0.765625 2.609370 -0.320312 v 0.843750 2.687495 -0.320312 v -0.843750 2.687495 -0.320312 v 1.039062 2.843745 -0.414062 v -1.039062 2.843745 -0.414062 v 1.187500 2.859370 -0.484375 v -1.187500 2.859370 -0.484375 v 1.257812 2.757807 -0.492188 v -1.257812 2.757807 -0.492188 v 1.210938 2.601557 -0.484375 v -1.210938 2.601557 -0.484375 v 1.046875 2.515620 -0.421875 v -1.046875 2.515620 -0.421875 v 0.882812 2.499995 -0.265625 v -0.882812 2.499995 -0.265625 v 0.953125 2.804682 -0.343750 v -0.953125 2.804682 -0.343750 v 0.890625 2.624995 -0.328125 v -0.890625 2.624995 -0.328125 v 0.937500 2.578120 -0.335938 v -0.937500 2.578120 -0.335938 v 1.000000 2.640620 -0.367188 v -1.000000 2.640620 -0.367188 v 0.960938 2.687495 -0.351562 v -0.960938 2.687495 -0.351562 v 1.015625 2.749995 -0.375000 v -1.015625 2.749995 -0.375000 v 1.054688 2.703120 -0.382812 v -1.054688 2.703120 -0.382812 v 1.109375 2.726557 -0.390625 v -1.109375 2.726557 -0.390625 v 1.085938 2.789057 -0.390625 v -1.085938 2.789057 -0.390625 v 1.023438 2.953120 -0.484375 v -1.023438 2.953120 -0.484375 v 1.250000 2.984370 -0.546875 v -1.250000 2.984370 -0.546875 v 1.367188 2.812495 -0.500000 v -1.367188 2.812495 -0.500000 v 1.312500 2.570307 -0.531250 v -1.312500 2.570307 -0.531250 v 1.039062 2.429682 -0.492188 v -1.039062 2.429682 -0.492188 v 0.789062 2.390620 -0.328125 v -0.789062 2.390620 -0.328125 v 0.859375 2.898432 -0.382812 v -0.859375 2.898432 -0.382812 vt 0.890955 0.590063 vt 0.860081 0.560115 vt 0.904571 0.559404 vt 0.856226 0.850547 vt 0.888398 0.821999 vt 0.900640 0.853232 vt 0.853018 0.521562 vt 0.920166 0.524546 vt 0.847458 0.888748 vt 0.914672 0.888748 vt 0.798481 0.569535 vt 0.795104 0.838402 vt 0.870622 0.589649 vt 0.828900 0.590771 vt 0.826436 0.818537 vt 0.868067 0.821510 vt 0.854402 0.604754 vt 0.828171 0.633354 vt 0.827598 0.775964 vt 0.852534 0.805700 vt 0.791018 0.645443 vt 0.791018 0.762238 vt 0.855181 0.668527 vt 0.856142 0.742025 vt 0.844839 0.707525 vt 0.854107 0.625459 vt 0.853157 0.785002 vt 0.867508 0.642291 vt 0.900375 0.666964 vt 0.901223 0.745592 vt 0.867293 0.768782 vt 0.842358 0.702491 vt 0.921180 0.713713 vt 0.931889 0.636832 vt 0.918898 0.699697 vt 0.931368 0.777093 vt 0.968213 0.770220 vt 0.905882 0.627902 vt 0.890474 0.641909 vt 0.904990 0.784860 vt 0.906232 0.605742 vt 0.904357 0.807013 vt 0.931250 0.820926 vt 0.933717 0.593037 vt 0.968392 0.645333 vt 0.965038 0.841671 vt 0.968392 0.573812 vt 0.889591 0.593275 vt 0.887178 0.818729 vt 0.900583 0.804677 vt 0.902359 0.607909 vt 0.898822 0.786233 vt 0.899781 0.626257 vt 0.890219 0.770183 vt 0.887351 0.775442 vt 0.887842 0.636527 vt 0.870376 0.775972 vt 0.859881 0.623942 vt 0.870908 0.635245 vt 0.858859 0.786774 vt 0.859664 0.608186 vt 0.857942 0.802505 vt 0.871664 0.593961 vt 0.869299 0.817249 vt 0.879400 0.616512 vt 0.878029 0.795063 vt 0.536419 0.062072 vt 0.518916 0.050294 vt 0.540260 0.053805 vt 0.501452 0.062043 vt 0.518925 0.059681 vt 0.542788 0.064089 vt 0.551930 0.058338 vt 0.495083 0.064047 vt 0.497626 0.053770 vt 0.555073 0.061900 vt 0.482805 0.061829 vt 0.485955 0.058273 vt 0.563812 0.076586 vt 0.546290 0.072669 vt 0.491565 0.072625 vt 0.474014 0.076511 vt 0.583135 0.108495 vt 0.548333 0.084893 vt 0.489507 0.084858 vt 0.454527 0.108481 vt 0.605512 0.165134 vt 0.621513 0.227818 vt 0.553118 0.209599 vt 0.416514 0.229490 vt 0.432024 0.165644 vt 0.485339 0.210053 vt 0.676379 0.233241 vt 0.647395 0.200502 vt 0.360308 0.235899 vt 0.372747 0.256357 vt 0.683908 0.279995 vt 0.664761 0.253225 vt 0.353696 0.284606 vt 0.707254 0.310054 vt 0.715342 0.265392 vt 0.330721 0.316853 vt 0.351187 0.317440 vt 0.697446 0.332673 vt 0.687515 0.311539 vt 0.341964 0.339667 vt 0.362723 0.329722 vt 0.662817 0.372521 vt 0.676824 0.323937 vt 0.379297 0.378686 vt 0.402772 0.362131 vt 0.618316 0.375151 vt 0.639050 0.357330 vt 0.424583 0.379267 vt 0.604826 0.397804 vt 0.626842 0.395792 vt 0.439252 0.401540 vt 0.442396 0.381222 vt 0.553095 0.390512 vt 0.600808 0.377857 vt 0.490934 0.391862 vt 0.482938 0.358497 vt 0.521923 0.386009 vt 0.559674 0.357011 vt 0.521086 0.343868 vt 0.599845 0.344815 vt 0.577279 0.340156 vt 0.441977 0.347815 vt 0.615546 0.342005 vt 0.634472 0.332311 vt 0.425972 0.345582 vt 0.662406 0.312804 vt 0.406362 0.336480 vt 0.668440 0.297958 vt 0.377061 0.317685 vt 0.664101 0.277872 vt 0.370304 0.302644 vt 0.639236 0.253047 vt 0.374100 0.281778 vt 0.613992 0.242662 vt 0.398938 0.255633 vt 0.572941 0.258564 vt 0.424464 0.244473 vt 0.519760 0.248864 vt 0.466409 0.259709 vt 0.558527 0.316594 vt 0.482619 0.317843 vt 0.520277 0.294764 vt 0.556923 0.291214 vt 0.483433 0.292249 vt 0.563905 0.272007 vt 0.475886 0.273078 vt 0.525483 0.068967 vt 0.512375 0.068956 vt 0.531231 0.073829 vt 0.506626 0.073811 vt 0.531019 0.087431 vt 0.555621 0.121749 vt 0.532669 0.090920 vt 0.505177 0.090908 vt 0.482177 0.121781 vt 0.506827 0.087416 vt 0.518981 0.151749 vt 0.532042 0.127713 vt 0.538112 0.158382 vt 0.505828 0.127728 vt 0.518941 0.128358 vt 0.518925 0.093952 vt 0.518927 0.085180 vt 0.548362 0.173560 vt 0.535214 0.166808 vt 0.502799 0.166857 vt 0.489683 0.173693 vt 0.499851 0.158434 vt 0.544281 0.193366 vt 0.537959 0.175966 vt 0.500100 0.176033 vt 0.493996 0.193428 vt 0.528757 0.191785 vt 0.519841 0.200843 vt 0.509219 0.191626 vt 0.500890 0.187571 vt 0.519132 0.185382 vt 0.517577 0.190607 vt 0.518998 0.159028 vt 0.519016 0.165599 vt 0.506910 0.171667 vt 0.528222 0.186316 vt 0.509787 0.186260 vt 0.533528 0.184215 vt 0.537248 0.187577 vt 0.504547 0.184206 vt 0.504604 0.176791 vt 0.531131 0.171631 vt 0.533449 0.176739 vt 0.519099 0.179457 vt 0.561572 0.167779 vt 0.476363 0.167996 vt 0.478371 0.149447 vt 0.559475 0.149319 vt 0.596138 0.133426 vt 0.441395 0.133592 vt 0.601169 0.147885 vt 0.436337 0.148194 vt 0.528933 0.084957 vt 0.508915 0.084945 vt 0.518925 0.083865 vt 0.529036 0.075429 vt 0.508820 0.075415 vt 0.523751 0.070508 vt 0.514106 0.070501 vt 0.518928 0.067899 vt 0.518929 0.069468 vt 0.518928 0.074259 vt 0.516297 0.074966 vt 0.524236 0.076691 vt 0.521560 0.074970 vt 0.513619 0.076684 vt 0.524601 0.079886 vt 0.513252 0.079879 vt 0.518926 0.079331 vt 0.571787 0.277295 vt 0.568351 0.292904 vt 0.468070 0.278617 vt 0.471978 0.294282 vt 0.573085 0.311386 vt 0.467790 0.313081 vt 0.584855 0.327708 vt 0.456477 0.329961 vt 0.458737 0.268049 vt 0.611720 0.255725 vt 0.580734 0.266620 vt 0.427062 0.257728 vt 0.632494 0.262853 vt 0.406068 0.265508 vt 0.653658 0.279971 vt 0.384904 0.283634 vt 0.656064 0.297636 vt 0.383015 0.301864 vt 0.386858 0.314615 vt 0.652752 0.310186 vt 0.411556 0.327673 vt 0.614408 0.331972 vt 0.629040 0.323864 vt 0.426727 0.335361 vt 0.601033 0.333624 vt 0.440344 0.336537 vt 0.601799 0.328453 vt 0.439372 0.331331 vt 0.450408 0.323919 vt 0.613335 0.327083 vt 0.427623 0.330358 vt 0.626851 0.320513 vt 0.413648 0.324175 vt 0.646248 0.306421 vt 0.393381 0.310510 vt 0.649541 0.296225 vt 0.389662 0.300183 vt 0.647785 0.283486 vt 0.391040 0.287071 vt 0.629829 0.267263 vt 0.408893 0.269959 vt 0.612641 0.261560 vt 0.426254 0.263693 vt 0.585166 0.270991 vt 0.454369 0.272583 vt 0.578124 0.281900 vt 0.461798 0.283441 vt 0.579548 0.309340 vt 0.590644 0.321516 vt 0.461204 0.311233 vt 0.577524 0.293776 vt 0.462754 0.295432 vt 0.553209 0.433063 vt 0.523031 0.433628 vt 0.492809 0.434538 vt 0.609819 0.431516 vt 0.435860 0.435740 vt 0.416915 0.400552 vt 0.396518 0.425416 vt 0.648174 0.419316 vt 0.350292 0.396229 vt 0.692106 0.388274 vt 0.312756 0.350588 vt 0.735879 0.312112 vt 0.726332 0.341754 vt 0.301067 0.320593 vt 0.320452 0.270303 vt 0.304876 0.261087 vt 0.698172 0.216906 vt 0.729900 0.256393 vt 0.337414 0.219179 vt 0.663103 0.190671 vt 0.373474 0.191872 vt 0.649444 0.022378 vt 0.621440 0.048089 vt 0.626908 0.015608 vt 0.388827 0.021586 vt 0.416419 0.047631 vt 0.376796 0.075296 vt 0.577206 0.032801 vt 0.567460 0.000144 vt 0.411318 0.015131 vt 0.460782 0.032656 vt 0.547413 0.041724 vt 0.518922 0.024886 vt 0.470636 0.000144 vt 0.490511 0.041669 vt 0.558059 0.053871 vt 0.479842 0.053785 vt 0.576951 0.057998 vt 0.460920 0.057845 vt 0.611687 0.078268 vt 0.425932 0.077985 vt 0.660451 0.076084 vt 0.626663 0.111357 vt 0.410618 0.111244 vt 0.629482 0.130456 vt 0.407648 0.130594 vt 0.413741 0.147158 vt 0.619303 0.159841 vt 0.418035 0.160361 vt 0.389677 0.201890 vt 0.886245 0.121777 vt 0.891780 0.036916 vt 0.945900 0.079569 vt 0.141314 0.112482 vt 0.142277 0.021467 vt 0.183115 0.092127 vt 0.849114 0.099732 vt 0.805584 0.010786 vt 0.232648 0.003484 vt 0.246353 0.076510 vt 0.687018 0.077204 vt 0.672384 0.022201 vt 0.349875 0.075955 vt 0.365979 0.020991 vt 0.760215 0.193244 vt 0.789046 0.233323 vt 0.271553 0.193871 vt 0.241255 0.236977 vt 0.909112 0.183261 vt 0.994525 0.167705 vt 0.107928 0.179083 vt 0.078961 0.060719 vt 0.862868 0.338556 vt 0.962901 0.344752 vt 0.911671 0.402429 vt 0.160557 0.356821 vt 0.043968 0.367038 vt 0.123776 0.315519 vt 0.915360 0.259804 vt 0.999856 0.254640 vt 0.098965 0.266968 vt 0.000144 0.259113 vt 0.011829 0.155367 vt 0.749542 0.334683 vt 0.766337 0.300809 vt 0.789162 0.313727 vt 0.267408 0.310142 vt 0.288183 0.346496 vt 0.242992 0.325552 vt 0.815314 0.276388 vt 0.846174 0.293397 vt 0.213065 0.285164 vt 0.178537 0.304983 vt 0.845007 0.256352 vt 0.873517 0.265922 vt 0.179662 0.263312 vt 0.147089 0.274284 vt 0.859075 0.228168 vt 0.886999 0.233769 vt 0.162803 0.231720 vt 0.131514 0.237587 vt 0.875030 0.184705 vt 0.842355 0.195160 vt 0.145224 0.182749 vt 0.894128 0.301884 vt 0.794286 0.364062 vt 0.770185 0.379538 vt 0.239776 0.382592 vt 0.845499 0.449967 vt 0.106400 0.432652 vt 0.815858 0.445381 vt 0.755700 0.418603 vt 0.287033 0.442912 vt 0.219260 0.477186 vt 0.268122 0.398737 vt 0.185281 0.484099 vt 0.819845 0.468071 vt 0.215894 0.503605 vt 0.809631 0.233887 vt 0.219168 0.237388 vt 0.829287 0.219562 vt 0.199067 0.222464 vt 0.788458 0.080826 vt 0.715482 0.139727 vt 0.319538 0.139409 vt 0.246666 0.114850 vt 0.785486 0.152330 vt 0.245969 0.151002 vt 0.623495 0.146796 vt 0.837382 0.156361 vt 0.196622 0.155241 vt 0.171653 0.132294 vt 0.786480 0.117591 vt 0.858171 0.137775 vt 0.432388 0.894943 vt 0.491058 0.881714 vt 0.506166 0.904851 vt 0.321637 0.893225 vt 0.263032 0.878321 vt 0.315867 0.868209 vt 0.572792 0.860484 vt 0.604825 0.879946 vt 0.181486 0.854693 vt 0.247207 0.901159 vt 0.148729 0.873349 vt 0.619962 0.791615 vt 0.136063 0.784093 vt 0.169745 0.787474 vt 0.586396 0.793977 vt 0.563786 0.739211 vt 0.194086 0.733241 vt 0.208656 0.740879 vt 0.549027 0.746412 vt 0.508270 0.697693 vt 0.250811 0.693249 vt 0.258399 0.707497 vt 0.438641 0.680683 vt 0.434803 0.658882 vt 0.320962 0.677959 vt 0.325318 0.656224 vt 0.500314 0.711729 vt 0.452955 0.700023 vt 0.306136 0.696976 vt 0.505666 0.730944 vt 0.252524 0.726592 vt 0.568148 0.787367 vt 0.188269 0.781375 vt 0.214575 0.750414 vt 0.555495 0.826352 vt 0.199850 0.820889 vt 0.501231 0.844356 vt 0.253846 0.840502 vt 0.457832 0.840040 vt 0.297562 0.837358 vt 0.783193 0.187449 vt 0.246955 0.187075 vt 0.233625 0.175620 vt 0.394766 0.686125 vt 0.391039 0.611891 vt 0.364838 0.684445 vt 0.391747 0.862097 vt 0.438797 0.870229 vt 0.363377 0.861308 vt 0.435018 0.718280 vt 0.323658 0.715731 vt 0.384658 0.710299 vt 0.433669 0.729661 vt 0.374400 0.708969 vt 0.410995 0.747662 vt 0.427812 0.742828 vt 0.324726 0.727177 vt 0.347028 0.745816 vt 0.330270 0.740536 vt 0.384657 0.795423 vt 0.418086 0.784946 vt 0.372270 0.794472 vt 0.431333 0.817535 vt 0.401605 0.841460 vt 0.324790 0.815460 vt 0.338952 0.783073 vt 0.354026 0.840297 vt 0.825107 0.209762 vt 0.199767 0.214827 vt 0.816266 0.203086 vt 0.209828 0.206161 vt 0.226485 0.183086 vt 0.796021 0.176969 vt 0.802192 0.184609 vt 0.448505 0.804621 vt 0.473386 0.824700 vt 0.307886 0.802031 vt 0.282357 0.821525 vt 0.321237 0.777208 vt 0.423718 0.754191 vt 0.435868 0.779569 vt 0.334089 0.752045 vt 0.319919 0.747250 vt 0.437950 0.749777 vt 0.312907 0.729222 vt 0.440995 0.724383 vt 0.445392 0.731997 vt 0.317510 0.721697 vt 0.455277 0.713731 vt 0.303460 0.710657 vt 0.512485 0.828811 vt 0.242975 0.824574 vt 0.550942 0.811814 vt 0.204839 0.806417 vt 0.552139 0.787682 vt 0.204331 0.782156 vt 0.539407 0.764539 vt 0.542850 0.755753 vt 0.217774 0.759319 vt 0.508439 0.743135 vt 0.249419 0.738732 vt 0.454776 0.761665 vt 0.302729 0.758742 vt 0.286960 0.745020 vt 0.470841 0.748408 vt 0.475403 0.783904 vt 0.281439 0.780511 vt 0.268291 0.766661 vt 0.503673 0.787562 vt 0.494476 0.802470 vt 0.252972 0.783410 vt 0.261790 0.798626 vt 0.516802 0.807339 vt 0.239243 0.802891 vt 0.237920 0.787045 vt 0.518562 0.791602 vt 0.484068 0.628776 vt 0.543385 0.683538 vt 0.276936 0.625067 vt 0.216123 0.678120 vt 0.581052 0.726933 vt 0.177176 0.720426 vt 0.616701 0.759965 vt 0.140379 0.752377 vt 0.660647 0.741167 vt 0.707492 0.759884 vt 0.097038 0.732052 vt 0.677256 0.670436 vt 0.745511 0.652100 vt 0.049526 0.748824 vt 0.083564 0.662038 vt 0.671403 0.592656 vt 0.740843 0.572428 vt 0.019409 0.639749 vt 0.092820 0.589862 vt 0.834705 0.206959 vt 0.051216 0.522659 vt 0.033664 0.564403 vt 0.620420 0.565675 vt 0.498072 0.552315 vt 0.145041 0.562595 vt 0.264218 0.550140 vt 0.369913 0.610196 vt 0.464579 0.342230 vt 0.176788 0.196179 vt 0.770572 0.444261 vt 0.271364 0.473316 vt 0.488870 0.770464 vt 0.834578 0.206879 vn 0.9693 -0.0118 0.2456 vn 0.6076 -0.5104 0.6085 vn 0.8001 -0.0028 0.5999 vn -0.6076 -0.5104 0.6085 vn -0.9693 -0.0118 0.2456 vn -0.8001 -0.0028 0.5999 vn 0.6802 -0.5463 0.4888 vn 0.8682 -0.0048 0.4961 vn -0.6802 -0.5463 0.4888 vn -0.8682 -0.0048 0.4961 vn 0.1193 -0.8712 0.4763 vn -0.1193 -0.8712 0.4763 vn 0.7290 -0.6566 0.1934 vn 0.0995 -0.7515 0.6522 vn -0.0995 -0.7515 0.6522 vn -0.7290 -0.6566 0.1934 vn 0.0314 -0.9670 0.2529 vn -0.4563 -0.5362 0.7101 vn 0.4563 -0.5362 0.7101 vn -0.0314 -0.9670 0.2529 vn -0.5539 -0.6332 0.5406 vn 0.5539 -0.6332 0.5406 vn -0.6899 -0.0041 0.7239 vn 0.6899 -0.0041 0.7239 vn 0.8097 -0.0070 0.5868 vn -0.6506 -0.6883 0.3210 vn 0.6506 -0.6883 0.3210 vn -0.9521 -0.0102 0.3057 vn -0.4560 0.5222 0.7207 vn 0.4560 0.5222 0.7207 vn 0.9521 -0.0102 0.3057 vn -0.8097 -0.0070 0.5868 vn 0.5306 0.6258 0.5717 vn 0.1031 0.7402 0.6644 vn -0.5306 0.6258 0.5717 vn -0.1031 0.7402 0.6644 vn -0.1257 0.8416 0.5253 vn 0.0258 0.9726 0.2312 vn -0.6644 0.6821 0.3056 vn -0.0258 0.9726 0.2312 vn 0.7364 0.6521 0.1803 vn -0.7364 0.6521 0.1803 vn -0.6102 0.4956 0.6181 vn 0.6102 0.4956 0.6181 vn 0.1257 0.8416 0.5253 vn -0.6682 0.5371 0.5148 vn 0.6682 0.5371 0.5148 vn 0.9645 -0.0127 0.2639 vn -0.9645 -0.0127 0.2639 vn -0.7216 0.6556 0.2224 vn 0.7216 0.6556 0.2224 vn -0.0432 0.9389 0.3415 vn 0.0432 0.9389 0.3415 vn 0.6644 0.6821 0.3056 vn 0.6237 0.6285 0.4647 vn -0.6237 0.6285 0.4647 vn 0.9270 -0.0130 0.3749 vn -0.6159 -0.6366 0.4641 vn -0.9270 -0.0130 0.3749 vn 0.6159 -0.6366 0.4641 vn 0.0426 -0.9404 0.3375 vn -0.0426 -0.9404 0.3375 vn 0.7152 -0.6625 0.2227 vn -0.7152 -0.6625 0.2227 vn 0.1836 -0.0053 0.9830 vn -0.1836 -0.0053 0.9830 vn 0.1554 -0.7590 0.6323 vn 0.0000 -0.9677 0.2523 vn 0.1596 -0.9753 0.1529 vn -0.1554 -0.7590 0.6323 vn 0.0000 -0.7753 0.6316 vn 0.3502 -0.6392 0.6847 vn 0.5267 -0.8347 0.1611 vn -0.3502 -0.6392 0.6847 vn -0.1596 -0.9753 0.1529 vn 0.9457 -0.2579 0.1977 vn -0.9457 -0.2579 0.1977 vn -0.5267 -0.8347 0.1611 vn 0.9728 0.1003 0.2087 vn 0.5557 -0.2264 0.8000 vn -0.5557 -0.2264 0.8000 vn -0.9728 0.1003 0.2087 vn 0.9557 0.2492 0.1565 vn 0.5652 -0.0297 0.8244 vn -0.5652 -0.0297 0.8244 vn -0.9557 0.2492 0.1565 vn 0.8916 -0.3307 0.3095 vn 0.3842 -0.5671 0.7286 vn 0.0402 -0.2722 0.9614 vn -0.3842 -0.5671 0.7286 vn -0.8916 -0.3307 0.3095 vn -0.0402 -0.2722 0.9614 vn 0.5875 -0.7849 0.1970 vn 0.3489 -0.9371 -0.0082 vn -0.5875 -0.7849 0.1970 vn -0.4991 -0.3761 0.7807 vn 0.5666 -0.3188 0.7598 vn 0.4991 -0.3761 0.7807 vn -0.5666 -0.3188 0.7598 vn 0.8451 0.4434 0.2985 vn 0.9070 -0.4009 -0.1290 vn -0.8451 0.4434 0.2985 vn -0.4607 -0.1448 0.8757 vn 0.5171 0.8291 0.2125 vn 0.4607 -0.1448 0.8757 vn -0.5171 0.8291 0.2125 vn -0.4801 -0.1833 0.8578 vn 0.5976 0.7847 0.1646 vn 0.4801 -0.1833 0.8578 vn -0.5976 0.7847 0.1646 vn -0.3085 0.0039 0.9512 vn 0.2666 0.2166 0.9392 vn 0.3085 0.0039 0.9512 vn -0.2666 0.2166 0.9392 vn -0.6051 0.7680 0.2098 vn 0.2313 0.9570 0.1751 vn 0.6051 0.7680 0.2098 vn 0.1574 0.1660 0.9735 vn -0.8242 0.5468 0.1473 vn -0.1574 0.1660 0.9735 vn 0.8242 0.5468 0.1473 vn 0.0611 -0.0253 0.9978 vn 0.0000 0.9636 0.2673 vn -0.0611 -0.0253 0.9978 vn 0.0000 -0.0827 0.9966 vn 0.2582 -0.1265 0.9578 vn 0.3679 -0.2836 0.8856 vn -0.2582 -0.1265 0.9578 vn 0.1490 -0.1542 0.9767 vn 0.2190 0.0372 0.9750 vn -0.1490 -0.1542 0.9767 vn 0.2254 -0.3608 0.9050 vn -0.2190 0.0372 0.9750 vn 0.3588 -0.1192 0.9258 vn -0.2254 -0.3608 0.9050 vn 0.4602 -0.1651 0.8723 vn -0.3588 -0.1192 0.9258 vn 0.4279 -0.3895 0.8156 vn -0.4602 -0.1651 0.8723 vn 0.3322 -0.3667 0.8690 vn -0.4279 -0.3895 0.8156 vn -0.1522 -0.2549 0.9549 vn -0.3322 -0.3667 0.8690 vn -0.0000 0.0643 0.9979 vn 0.1522 -0.2549 0.9549 vn 0.0316 -0.1782 0.9835 vn -0.0316 -0.1782 0.9835 vn -0.0000 -0.2220 0.9750 vn -0.2006 -0.1350 0.9703 vn 0.2006 -0.1350 0.9703 vn -0.2393 -0.3012 0.9230 vn 0.2393 -0.3012 0.9230 vn -0.0589 -0.3784 0.9238 vn 0.0589 -0.3784 0.9238 vn 0.1307 -0.3187 0.9388 vn -0.1307 -0.3187 0.9388 vn 0.1460 -0.1202 0.9820 vn 0.5937 0.1082 0.7974 vn 0.1815 -0.0452 0.9823 vn -0.1815 -0.0452 0.9823 vn -0.5937 0.1082 0.7974 vn -0.1460 -0.1202 0.9820 vn 0.0000 -0.4760 0.8795 vn 0.1341 0.0063 0.9909 vn 0.5003 -0.4293 0.7520 vn -0.1341 0.0063 0.9909 vn 0.0000 0.0000 1.0000 vn 0.0000 -0.0341 0.9994 vn -0.0000 -0.5870 0.8096 vn 0.9304 -0.1242 0.3448 vn 0.5836 -0.6929 0.4235 vn -0.5836 -0.6929 0.4235 vn -0.9304 -0.1242 0.3448 vn -0.5003 -0.4293 0.7520 vn 0.4931 -0.3412 0.8002 vn 0.9306 -0.2353 0.2804 vn -0.9306 -0.2353 0.2804 vn -0.4931 -0.3412 0.8002 vn -0.2405 0.9491 0.2036 vn 0.0000 0.5166 0.8562 vn 0.2405 0.9491 0.2036 vn -0.6286 0.7688 0.1177 vn 0.0000 0.8287 0.5597 vn 0.0000 0.9515 0.3076 vn 0.0000 -0.8654 0.5011 vn 0.0000 -0.4815 0.8764 vn -0.1833 -0.5864 0.7890 vn -0.1858 0.5956 0.7815 vn 0.1858 0.5956 0.7815 vn 0.3611 0.4713 0.8047 vn 0.6286 0.7688 0.1177 vn -0.3611 0.4713 0.8047 vn -0.4488 -0.3147 0.8364 vn 0.1833 -0.5864 0.7890 vn 0.4488 -0.3147 0.8364 vn 0.0000 0.1578 0.9875 vn 0.7752 0.0387 0.6306 vn -0.7752 0.0387 0.6306 vn -0.6507 0.1488 0.7447 vn 0.6507 0.1488 0.7447 vn 0.9278 0.3530 0.1209 vn -0.9278 0.3530 0.1209 vn 0.9306 0.3435 0.1263 vn -0.9306 0.3435 0.1263 vn -0.1369 -0.5273 0.8386 vn 0.1369 -0.5273 0.8386 vn 0.0000 -0.9619 0.2732 vn -0.6351 0.0428 0.7712 vn 0.6351 0.0428 0.7712 vn -0.4141 0.5798 0.7016 vn 0.4141 0.5798 0.7016 vn 0.0000 -0.3465 0.9380 vn 0.0000 0.5588 0.8293 vn 0.0000 0.5334 0.8459 vn 0.2959 0.4750 0.8288 vn -0.6738 0.1155 0.7299 vn -0.2959 0.4750 0.8288 vn 0.6738 0.1155 0.7299 vn -0.5177 -0.7041 0.4860 vn 0.5177 -0.7041 0.4860 vn 0.0000 -0.6989 0.7152 vn -0.0101 -0.0700 0.9975 vn 0.1581 -0.0843 0.9838 vn 0.0101 -0.0700 0.9975 vn -0.1581 -0.0843 0.9838 vn 0.2934 -0.0602 0.9541 vn -0.2934 -0.0602 0.9541 vn 0.1588 -0.1065 0.9816 vn -0.1588 -0.1065 0.9816 vn 0.0317 -0.2198 0.9750 vn 0.1845 -0.1863 0.9650 vn -0.0317 -0.2198 0.9750 vn -0.1845 -0.1863 0.9650 vn 0.2990 -0.0356 0.9536 vn -0.2990 -0.0356 0.9536 vn 0.2943 -0.1021 0.9502 vn -0.2943 -0.1020 0.9502 vn 0.1776 -0.0608 0.9822 vn -0.1776 -0.0608 0.9822 vn -0.2944 0.0046 0.9557 vn 0.2944 0.0046 0.9557 vn -0.0887 -0.1272 0.9879 vn 0.2036 0.1032 0.9736 vn 0.0887 -0.1272 0.9879 vn -0.2036 0.1032 0.9736 vn 0.1435 0.0966 0.9849 vn -0.1435 0.0966 0.9849 vn 0.2886 -0.2786 0.9160 vn -0.2886 -0.2786 0.9160 vn -0.4508 -0.4658 0.7614 vn 0.1133 -0.3142 0.9426 vn -0.1133 -0.3142 0.9426 vn -0.2741 -0.8556 0.4391 vn 0.2741 -0.8556 0.4391 vn -0.1423 -0.5826 0.8002 vn 0.1423 -0.5826 0.8002 vn -0.4229 -0.1078 0.8997 vn 0.4229 -0.1078 0.8997 vn -0.1921 0.1914 0.9625 vn 0.1921 0.1914 0.9625 vn -0.1653 0.6098 0.7751 vn 0.1653 0.6098 0.7751 vn 0.1431 0.5587 0.8169 vn -0.1431 0.5587 0.8169 vn 0.4323 0.5833 0.6877 vn -0.4323 0.5833 0.6877 vn 0.6881 0.2985 0.6614 vn -0.6881 0.2985 0.6614 vn 0.7894 -0.2032 0.5793 vn 0.4508 -0.4658 0.7614 vn -0.7894 -0.2032 0.5793 vn 0.8016 0.0110 0.5977 vn -0.8016 0.0110 0.5977 vn -0.4603 0.8619 0.2127 vn 0.0000 0.8592 0.5116 vn 0.4603 0.8619 0.2127 vn -0.4792 0.5120 -0.7129 vn 0.4792 0.5120 -0.7129 vn -0.2313 0.9570 0.1751 vn -0.1217 0.6503 -0.7499 vn 0.1217 0.6503 -0.7499 vn -0.2275 0.8745 -0.4283 vn 0.2275 0.8745 -0.4283 vn -0.3456 0.9125 -0.2192 vn 0.6957 0.5814 -0.4218 vn 0.3456 0.9125 -0.2192 vn -0.6957 0.5814 -0.4218 vn -0.9070 -0.4009 -0.1290 vn -0.9302 -0.3062 -0.2024 vn 0.5444 -0.8372 -0.0533 vn 0.9302 -0.3062 -0.2024 vn -0.5444 -0.8372 -0.0533 vn 0.4720 -0.8637 -0.1768 vn -0.4720 -0.8637 -0.1768 vn 0.0000 -0.7711 -0.6367 vn 0.2771 -0.3147 -0.9078 vn -0.0000 -0.2133 -0.9770 vn -0.2771 -0.3147 -0.9078 vn -0.6894 -0.6687 -0.2786 vn 0.1514 -0.1510 -0.9769 vn 0.0000 -0.2974 -0.9548 vn -0.1514 -0.1510 -0.9769 vn 0.0675 -0.7832 -0.6181 vn 0.0000 -0.8818 -0.4716 vn -0.0675 -0.7832 -0.6181 vn 0.5551 -0.4762 -0.6820 vn -0.5551 -0.4762 -0.6820 vn 0.6204 0.0835 -0.7798 vn -0.6204 0.0835 -0.7798 vn 0.7799 -0.0105 -0.6259 vn -0.7799 -0.0105 -0.6259 vn 0.6894 -0.6687 -0.2786 vn 0.8957 0.2578 -0.3624 vn -0.8957 0.2578 -0.3624 vn 0.9787 -0.1959 0.0615 vn -0.9787 -0.1959 0.0615 vn -0.8872 -0.1577 0.4336 vn 0.7857 -0.5715 0.2368 vn -0.7857 -0.5715 0.2368 vn -0.3489 -0.9371 -0.0082 vn 0.4455 -0.3584 -0.8204 vn 0.0000 -0.6913 -0.7226 vn -0.0000 -0.3049 -0.9524 vn -0.4455 -0.3584 -0.8204 vn -0.5223 -0.6536 -0.5477 vn 0.5223 -0.6536 -0.5477 vn 0.0000 -0.9417 -0.3365 vn -0.5071 -0.8376 -0.2033 vn 0.5727 -0.8197 0.0120 vn 0.0000 -0.9831 -0.1833 vn -0.5727 -0.8197 0.0120 vn 0.7211 -0.6898 0.0651 vn 0.9850 -0.1605 0.0631 vn -0.7211 -0.6898 0.0651 vn -0.9850 -0.1605 0.0631 vn 0.4730 0.1763 -0.8632 vn 0.0000 0.3650 -0.9310 vn -0.4730 0.1763 -0.8632 vn 0.4442 0.7244 0.5271 vn 0.0000 0.9997 0.0226 vn 0.0000 0.8306 0.5568 vn -0.4442 0.7244 0.5271 vn -0.4135 0.9096 0.0395 vn 0.3913 0.8153 -0.4268 vn 0.0000 0.8343 -0.5514 vn -0.3913 0.8153 -0.4268 vn 0.7717 0.6311 0.0785 vn 0.4444 0.7886 0.4250 vn -0.7717 0.6311 0.0785 vn -0.4444 0.7886 0.4250 vn 0.7418 0.5164 0.4279 vn 0.6682 0.6719 0.3195 vn -0.7418 0.5164 0.4279 vn -0.6682 0.6719 0.3195 vn 0.8486 0.5288 -0.0140 vn 0.6784 0.7314 -0.0695 vn -0.8486 0.5288 -0.0140 vn -0.6784 0.7314 -0.0695 vn 0.8722 0.3146 -0.3747 vn 0.6075 0.5696 -0.5536 vn -0.8722 0.3146 -0.3747 vn -0.6075 0.5696 -0.5536 vn 0.6197 -0.0605 -0.7825 vn 0.6708 -0.0453 -0.7403 vn -0.6197 -0.0605 -0.7825 vn 0.4135 0.9096 0.0395 vn 0.3406 0.8832 0.3223 vn -0.3406 0.8832 0.3223 vn 0.0000 0.5293 0.8485 vn 0.9983 -0.0283 -0.0502 vn -0.9983 -0.0283 -0.0502 vn 0.8403 0.4934 0.2246 vn -0.8403 0.4934 0.2246 vn 0.5071 -0.8376 -0.2033 vn 0.5790 -0.8027 0.1427 vn -0.5790 -0.8027 0.1427 vn -0.5633 -0.8173 -0.1213 vn 0.3123 -0.9500 0.0012 vn -0.3123 -0.9500 0.0012 vn 0.8872 -0.1577 0.4336 vn 0.3255 -0.6029 -0.7284 vn -0.3255 -0.6029 -0.7284 vn -0.5292 -0.5051 -0.6817 vn 0.5633 -0.8173 -0.1213 vn 0.5292 -0.5051 -0.6817 vn -0.2793 0.7683 0.5759 vn 0.5512 -0.0788 0.8307 vn 0.0188 0.8723 0.4887 vn 0.2793 0.7683 0.5759 vn -0.5512 -0.0788 0.8307 vn -0.4493 -0.0383 0.8926 vn 0.3215 -0.0923 0.9424 vn 0.3836 0.8630 0.3288 vn -0.3215 -0.0923 0.9424 vn -0.0188 0.8723 0.4887 vn -0.3836 0.8630 0.3288 vn 0.7788 0.1678 0.6044 vn -0.7788 0.1678 0.6044 vn 0.1545 -0.1239 0.9802 vn -0.1545 -0.1239 0.9802 vn 0.6526 -0.4768 0.5888 vn -0.6526 -0.4768 0.5888 vn 0.0411 0.3108 0.9496 vn -0.0411 0.3108 0.9496 vn 0.5029 -0.7810 0.3703 vn -0.5029 -0.7810 0.3703 vn -0.5384 0.2953 0.7893 vn 0.3300 0.3157 0.8896 vn 0.0295 -0.6350 0.7719 vn -0.3300 0.3157 0.8896 vn -0.0295 -0.6350 0.7719 vn 0.5384 0.2953 0.7893 vn 0.1629 0.8581 0.4870 vn -0.1629 0.8581 0.4870 vn -0.1868 0.9538 0.2351 vn 0.1868 0.9538 0.2351 vn -0.9848 -0.0996 0.1426 vn 0.9848 -0.0996 0.1426 vn 0.7622 0.6471 -0.0193 vn -0.1496 -0.7455 0.6495 vn 0.1496 -0.7455 0.6495 vn 0.5605 -0.6609 0.4991 vn -0.5605 -0.6609 0.4991 vn 0.6842 -0.5558 0.4722 vn -0.6842 -0.5558 0.4722 vn 0.8572 -0.4931 -0.1483 vn -0.8572 -0.4931 -0.1483 vn -0.7312 0.1144 0.6725 vn 0.7312 0.1144 0.6725 vn 0.4493 -0.0383 0.8926 vn 0.5998 0.5131 0.6139 vn -0.5998 0.5131 0.6139 vn 0.9610 -0.1188 0.2499 vn 0.8420 -0.1763 0.5098 vn -0.9610 -0.1188 0.2499 vn 0.8515 0.0414 0.5228 vn 0.4814 0.6344 0.6048 vn -0.8420 -0.1763 0.5098 vn -0.8515 0.0414 0.5228 vn -0.4814 0.6344 0.6048 vn 0.8303 -0.4790 0.2850 vn 0.6864 -0.6234 0.3746 vn -0.8303 -0.4790 0.2850 vn 0.7261 -0.4989 0.4732 vn 0.7949 -0.2332 0.5601 vn -0.7261 -0.4989 0.4732 vn -0.6864 -0.6234 0.3746 vn -0.7949 -0.2332 0.5601 vn 0.6593 -0.4685 0.5881 vn 0.6482 -0.4206 0.6347 vn -0.6593 -0.4685 0.5881 vn -0.6482 -0.4206 0.6347 vn -0.5725 -0.4189 0.7048 vn 0.7584 0.2665 0.5948 vn 0.5725 -0.4189 0.7048 vn -0.7584 0.2665 0.5948 vn -0.4492 0.3799 0.8086 vn 0.4492 0.3799 0.8086 vn -0.2929 0.3709 0.8813 vn 0.6450 0.3102 0.6984 vn 0.2929 0.3709 0.8813 vn -0.6450 0.3102 0.6984 vn -0.0331 0.9449 0.3256 vn 0.0331 0.9449 0.3256 vn 0.4618 -0.3291 0.8237 vn -0.4618 -0.3291 0.8237 vn -0.2624 -0.5331 0.8043 vn 0.2624 -0.5331 0.8043 vn -0.7529 -0.0338 0.6573 vn 0.7529 -0.0338 0.6573 vn -0.5831 0.4999 0.6403 vn -0.7622 0.6471 -0.0193 vn 0.5831 0.4999 0.6403 vn 0.0650 0.7039 0.7074 vn -0.0650 0.7039 0.7074 vn 0.1951 0.0390 0.9800 vn -0.1951 0.0390 0.9800 vn -0.4085 0.1273 0.9039 vn 0.4085 0.1273 0.9039 vn 0.3347 -0.0046 0.9423 vn -0.3347 -0.0046 0.9423 vn -0.4448 -0.0937 0.8907 vn 0.3144 -0.1038 0.9436 vn 0.3343 0.1068 0.9364 vn -0.3144 -0.1038 0.9436 vn -0.3343 0.1068 0.9364 vn 0.2897 0.3158 0.9035 vn -0.2897 0.3158 0.9035 vn -0.3831 -0.0685 0.9211 vn 0.3831 -0.0685 0.9211 vn -0.0989 -0.8408 -0.5322 vn -0.0253 -0.6796 -0.7331 vn 0.0989 -0.8408 -0.5322 vn 0.0253 -0.6796 -0.7331 vn 0.6366 -0.5043 -0.5834 vn -0.6366 -0.5043 -0.5834 vn 0.9253 0.0918 -0.3680 vn -0.9253 0.0918 -0.3680 vn 0.2870 0.5978 -0.7485 vn -0.2870 0.5978 -0.7485 vn -0.4142 0.5509 -0.7245 vn 0.4142 0.5509 -0.7245 vn -0.6501 0.5847 -0.4854 vn 0.6501 0.5847 -0.4854 vn -0.6708 -0.0453 -0.7403 vn -0.3679 -0.2836 0.8856 vn 0.4448 -0.0937 0.8907 usemtl None s 1 f 55/15/7 11/16/8 53/17/9 f 12/18/10 56/19/11 54/20/12 f 53/17/9 13/21/13 51/22/14 f 14/23/15 54/20/12 52/24/16 f 11/16/8 15/25/17 13/21/13 f 16/26/18 12/18/10 14/23/15 f 9/27/19 17/28/20 11/16/8 f 18/29/21 10/30/22 12/18/10 f 19/31/23 23/32/24 17/28/20 f 24/33/25 20/34/26 18/29/21 f 17/28/20 25/35/27 15/25/17 f 26/36/28 18/29/21 16/26/18 f 29/37/29 25/35/27 23/32/24 f 30/38/30 26/36/28 28/39/31 f 21/40/32 29/37/29 23/32/24 f 30/38/30 22/41/33 24/33/25 f 31/42/34 35/43/35 29/37/29 f 36/44/36 32/45/37 30/38/30 f 35/43/35 27/46/38 29/37/29 f 36/44/36 28/39/31 38/47/39 f 41/48/40 37/49/41 35/43/35 f 42/50/42 38/47/39 40/51/43 f 43/52/44 35/43/35 33/53/45 f 44/54/46 36/44/36 42/50/42 f 45/55/47 41/48/40 43/52/44 f 46/56/48 42/50/42 48/57/49 f 47/58/50 39/59/51 41/48/40 f 48/57/49 40/51/43 50/60/52 f 53/17/9 49/61/53 47/58/50 f 54/20/12 50/60/52 52/24/16 f 55/15/7 47/58/50 45/55/47 f 56/19/11 48/57/49 54/20/12 f 45/55/47 57/62/54 55/15/7 f 46/56/48 58/63/55 60/64/56 f 43/52/44 59/65/57 45/55/47 f 44/54/46 60/64/56 62/66/58 f 33/53/45 61/67/59 43/52/44 f 34/68/60 62/66/58 64/69/61 f 31/42/34 63/70/62 33/53/45 f 32/45/37 64/69/61 66/71/63 f 31/42/34 67/72/64 65/73/65 f 68/74/66 32/45/37 66/71/63 f 21/40/32 71/75/67 67/72/64 f 72/76/68 22/41/33 68/74/66 f 19/31/23 73/77/69 71/75/67 f 74/78/70 20/34/26 72/76/68 f 9/27/19 57/62/54 73/77/69 f 58/63/55 10/30/22 74/78/70 f 69/79/71 73/77/69 57/62/54 f 58/63/55 74/78/70 70/80/72 f 71/75/67 73/77/69 69/79/71 f 70/80/72 74/78/70 72/76/68 f 69/79/71 67/72/64 71/75/67 f 72/76/68 68/74/66 70/80/72 f 69/79/71 65/73/65 67/72/64 f 68/74/66 66/71/63 70/80/72 f 69/79/71 63/70/62 65/73/65 f 66/71/63 64/69/61 70/80/72 f 69/79/71 61/67/59 63/70/62 f 64/69/61 62/66/58 70/80/72 f 69/79/71 59/65/57 61/67/59 f 62/66/58 60/64/56 70/80/72 f 69/79/71 57/62/54 59/65/57 f 60/64/56 58/63/55 70/80/72 f 182/81/73 99/82/74 97/83/75 f 183/84/76 99/82/74 184/85/77 f 180/86/78 97/83/75 95/87/79 f 181/88/80 98/89/81 183/84/76 f 93/90/82 180/86/78 95/87/79 f 181/88/80 94/91/83 96/92/84 f 91/93/85 178/94/86 93/90/82 f 179/95/87 92/96/88 94/91/83 f 89/97/89 176/98/90 91/93/85 f 177/99/91 90/100/92 92/96/88 f 87/101/93 154/102/94 172/103/95 f 155/104/96 88/105/97 173/106/98 f 102/107/99 154/102/94 100/108/100 f 103/109/101 155/104/96 157/110/102 f 102/107/99 158/111/103 156/112/104 f 159/113/105 103/109/101 157/110/102 f 106/114/106 158/111/103 104/115/107 f 107/116/108 159/113/105 161/117/109 f 108/118/110 160/119/111 106/114/106 f 109/120/112 161/117/109 163/121/113 f 110/122/114 162/123/115 108/118/110 f 111/124/116 163/121/113 165/125/117 f 110/122/114 166/126/118 164/127/119 f 167/128/120 111/124/116 165/125/117 f 114/129/121 166/126/118 112/130/122 f 115/131/123 167/128/120 169/132/124 f 116/133/125 168/134/126 114/129/121 f 117/135/127 169/132/124 171/136/128 f 75/137/129 170/138/130 116/133/125 f 75/137/129 171/136/128 76/139/131 f 136/140/132 170/138/130 118/141/133 f 137/142/134 171/136/128 169/132/124 f 136/140/132 166/126/118 168/134/126 f 167/128/120 137/142/134 169/132/124 f 164/127/119 187/143/135 134/144/136 f 165/125/117 188/145/137 167/128/120 f 162/123/115 134/144/136 132/146/138 f 163/121/113 135/147/139 165/125/117 f 160/119/111 132/146/138 130/148/140 f 161/117/109 133/149/141 163/121/113 f 158/111/103 130/148/140 128/150/142 f 159/113/105 131/151/143 161/117/109 f 156/112/104 128/150/142 126/152/144 f 157/110/102 129/153/145 159/113/105 f 154/102/94 126/152/144 124/154/146 f 155/104/96 127/155/147 157/110/102 f 172/103/95 124/154/146 122/156/148 f 173/106/98 125/157/149 155/104/96 f 122/156/148 185/158/150 172/103/95 f 185/158/150 123/159/151 173/106/98 f 170/138/130 120/160/152 118/141/133 f 171/136/128 121/161/153 76/139/131 f 120/160/152 186/162/154 191/163/155 f 186/162/154 121/161/153 192/164/156 f 189/165/157 186/162/154 185/158/150 f 190/166/158 186/162/154 192/164/156 f 143/167/159 184/85/77 182/81/73 f 184/85/77 144/168/160 183/84/76 f 141/169/161 182/81/73 180/86/78 f 183/84/76 142/170/162 181/88/80 f 141/169/161 178/94/86 139/171/163 f 142/170/162 179/95/87 181/88/80 f 174/172/164 193/173/165 176/98/90 f 194/174/166 175/175/167 177/99/91 f 139/171/163 176/98/90 193/173/165 f 177/99/91 140/176/168 194/174/166 f 198/177/169 195/178/170 152/179/171 f 198/177/169 196/180/172 197/181/173 f 195/178/170 77/182/174 193/173/165 f 196/180/172 77/182/174 197/181/173 f 139/171/163 77/182/174 138/183/175 f 140/176/168 77/182/174 194/174/166 f 150/184/176 199/185/177 152/179/171 f 200/186/178 151/187/179 153/188/180 f 148/189/181 201/190/182 150/184/176 f 202/191/183 149/192/184 151/187/179 f 205/193/185 148/189/181 147/194/186 f 206/195/187 149/192/184 204/196/188 f 79/197/189 147/194/186 146/198/190 f 79/197/189 147/194/186 206/195/187 f 152/179/171 78/199/191 198/177/169 f 153/188/180 78/199/191 200/186/178 f 199/185/177 216/200/192 78/199/191 f 200/186/178 216/200/192 215/201/193 f 79/197/189 208/202/194 205/193/185 f 209/203/195 79/197/189 206/195/187 f 205/193/185 210/204/196 203/205/197 f 211/206/198 206/195/187 204/196/188 f 210/204/196 201/190/182 203/205/197 f 211/206/198 202/191/183 213/207/199 f 201/190/182 214/208/200 199/185/177 f 215/201/193 202/191/183 200/186/178 f 212/209/201 208/202/194 207/210/202 f 213/207/199 209/203/195 211/206/198 f 207/210/202 214/208/200 212/209/201 f 215/201/193 207/210/202 213/207/199 f 147/194/186 172/103/95 185/158/150 f 173/106/98 147/194/186 185/158/150 f 148/189/181 219/211/203 172/103/95 f 220/212/204 149/192/184 173/106/98 f 152/179/171 219/211/203 150/184/176 f 153/188/180 220/212/204 222/213/205 f 195/178/170 221/214/206 152/179/171 f 196/180/172 222/213/205 175/175/167 f 217/215/207 174/172/164 89/97/89 f 218/216/208 175/175/167 222/213/205 f 223/217/209 221/214/206 217/215/207 f 224/218/210 222/213/205 220/212/204 f 87/101/93 219/211/203 223/217/209 f 220/212/204 88/105/97 224/218/210 f 138/183/175 230/219/211 139/171/163 f 138/183/175 231/220/212 80/221/213 f 141/169/161 230/219/211 228/222/214 f 231/220/212 142/170/162 229/223/215 f 143/167/159 228/222/214 226/224/216 f 229/223/215 144/168/160 227/225/217 f 145/226/218 226/224/216 225/227/219 f 227/225/217 145/226/218 225/227/219 f 226/224/216 239/228/220 225/227/219 f 227/225/217 239/228/220 238/229/221 f 226/224/216 235/230/222 237/231/223 f 236/232/224 227/225/217 238/229/221 f 228/222/214 233/233/225 235/230/222 f 234/234/226 229/223/215 236/232/224 f 80/221/213 233/233/225 230/219/211 f 80/221/213 234/234/226 232/235/227 f 232/235/227 237/231/223 233/233/225 f 238/229/221 232/235/227 234/234/226 f 233/233/225 237/231/223 235/230/222 f 236/232/224 238/229/221 234/234/226 f 191/163/155 242/236/228 240/237/229 f 243/238/230 192/164/156 241/239/231 f 120/160/152 240/237/229 262/240/232 f 241/239/231 121/161/153 263/241/233 f 120/160/152 264/242/234 118/141/133 f 121/161/153 265/243/235 263/241/233 f 122/156/148 242/236/228 189/165/157 f 123/159/151 243/238/230 261/244/236 f 122/156/148 258/245/237 260/246/238 f 259/247/239 123/159/151 261/244/236 f 124/154/146 256/248/240 258/245/237 f 257/249/241 125/157/149 259/247/239 f 126/152/144 254/250/242 256/248/240 f 255/251/243 127/155/147 257/249/241 f 128/150/142 252/252/244 254/250/242 f 253/253/245 129/153/145 255/251/243 f 132/146/138 252/252/244 130/148/140 f 133/149/141 253/253/245 251/254/246 f 134/144/136 250/255/247 132/146/138 f 135/147/139 251/254/246 249/256/248 f 134/144/136 244/257/249 248/258/250 f 245/259/251 135/147/139 249/256/248 f 187/143/135 246/260/252 244/257/249 f 247/261/253 188/145/137 245/259/251 f 136/140/132 264/242/234 246/260/252 f 265/243/235 137/142/134 247/261/253 f 264/242/234 284/262/254 246/260/252 f 265/243/235 285/263/255 267/264/256 f 244/257/249 284/262/254 286/265/257 f 285/263/255 245/259/251 287/266/258 f 244/257/249 282/267/259 248/258/250 f 245/259/251 283/268/260 287/266/258 f 248/258/250 280/269/261 250/255/247 f 249/256/248 281/270/262 283/268/260 f 252/252/244 280/269/261 278/271/263 f 281/270/262 253/253/245 279/272/264 f 252/252/244 276/273/265 254/250/242 f 253/253/245 277/274/266 279/272/264 f 256/248/240 276/273/265 274/275/267 f 277/274/266 257/249/241 275/276/268 f 256/248/240 272/277/269 258/245/237 f 257/249/241 273/278/270 275/276/268 f 258/245/237 270/279/271 260/246/238 f 259/247/239 271/280/272 273/278/270 f 242/236/228 270/279/271 288/281/273 f 271/280/272 243/238/230 289/282/274 f 264/242/234 268/283/275 266/284/276 f 269/285/277 265/243/235 267/264/256 f 262/240/232 290/286/278 268/283/275 f 291/287/279 263/241/233 269/285/277 f 240/237/229 288/281/273 290/286/278 f 289/282/274 241/239/231 291/287/279 f 75/137/129 292/288/280 81/289/281 f 293/290/282 75/137/129 81/289/281 f 116/133/125 294/291/283 292/288/280 f 295/292/284 117/135/127 293/290/282 f 112/130/122 294/291/283 114/129/121 f 113/293/285 295/292/284 297/294/286 f 110/122/114 296/295/287 112/130/122 f 111/124/116 297/294/286 299/296/288 f 108/118/110 298/297/289 110/122/114 f 109/120/112 299/296/288 301/298/290 f 108/118/110 302/299/291 300/300/292 f 303/301/293 109/120/112 301/298/290 f 104/115/107 302/299/291 106/114/106 f 105/302/294 303/301/293 305/303/295 f 104/115/107 306/304/296 304/305/297 f 307/306/298 105/302/294 305/303/295 f 102/107/99 308/307/299 306/304/296 f 309/308/300 103/109/101 307/306/298 f 317/309/301 346/310/302 316/311/303 f 317/312/301 347/313/304 337/314/305 f 316/311/303 344/315/306 315/316/307 f 316/317/303 345/318/308 347/313/304 f 315/316/307 348/319/309 314/320/310 f 315/321/307 349/322/311 345/318/308 f 97/83/75 314/320/310 348/319/309 f 314/320/310 98/89/81 349/322/311 f 95/87/79 348/319/309 342/323/312 f 349/322/311 96/92/84 343/324/313 f 93/90/82 342/323/312 338/325/314 f 343/324/313 94/91/83 339/326/315 f 91/93/85 338/325/314 340/327/316 f 339/326/315 92/96/88 341/328/317 f 338/325/314 346/310/302 340/327/316 f 347/313/304 339/326/315 341/328/317 f 342/323/312 344/315/306 338/325/314 f 343/324/313 345/318/308 349/322/311 f 340/327/316 336/329/318 334/330/319 f 341/328/317 337/314/305 347/313/304 f 89/97/89 340/327/316 334/330/319 f 341/328/317 90/100/92 335/331/320 f 350/332/321 223/217/209 217/215/207 f 351/333/322 224/218/210 353/334/323 f 334/330/319 217/215/207 89/97/89 f 335/331/320 218/216/208 351/333/322 f 223/217/209 354/335/324 87/101/93 f 224/218/210 355/336/325 353/334/323 f 354/335/324 100/108/100 87/101/93 f 355/336/325 101/337/326 309/308/300 f 332/338/327 312/339/328 85/340/329 f 333/341/330 312/342/328 361/343/331 f 360/344/332 86/345/333 312/339/328 f 361/343/331 86/346/333 359/347/334 f 86/345/333 356/348/335 313/349/336 f 357/350/337 86/346/333 313/351/336 f 313/349/336 336/329/318 317/309/301 f 337/314/305 313/351/336 317/312/301 f 336/329/318 350/332/321 334/330/319 f 337/314/305 351/333/322 357/350/337 f 304/305/297 326/352/338 318/353/339 f 327/354/340 305/303/295 319/355/341 f 324/356/342 85/340/329 84/357/343 f 325/358/344 85/359/329 333/341/330 f 366/360/345 311/361/346 310/362/347 f 367/363/348 311/364/346 365/365/349 f 311/361/346 362/366/350 83/367/351 f 363/368/352 311/364/346 83/369/351 f 83/367/351 324/356/342 84/357/343 f 325/358/344 83/369/351 84/370/343 f 300/371/292 370/372/353 372/373/354 f 371/374/355 301/375/290 373/376/356 f 372/373/354 376/377/357 374/378/358 f 377/379/359 373/376/356 375/380/360 f 374/378/358 378/381/361 380/382/362 f 379/383/363 375/380/360 381/384/364 f 380/382/362 384/385/365 382/386/366 f 385/387/367 381/384/364 383/388/368 f 386/389/369 384/385/365 322/390/370 f 387/391/371 385/387/367 383/388/368 f 324/356/342 382/386/366 386/389/369 f 383/388/368 325/358/344 387/391/371 f 362/366/350 380/382/362 382/386/366 f 381/384/364 363/368/352 383/388/368 f 364/392/372 374/378/358 380/382/362 f 375/380/360 365/365/349 381/384/364 f 366/360/345 372/373/354 374/378/358 f 373/376/356 367/363/348 375/380/360 f 300/371/292 368/393/373 298/394/289 f 301/375/290 369/395/374 373/376/356 f 368/393/373 310/362/347 82/396/375 f 369/395/374 310/397/347 367/363/348 f 292/398/280 296/399/287 298/394/289 f 297/400/286 293/401/282 299/402/288 f 292/398/280 368/393/373 82/396/375 f 369/395/374 293/401/282 82/403/375 f 81/404/281 292/398/280 82/396/375 f 82/403/375 293/401/282 81/405/281 f 304/305/297 370/372/353 302/299/291 f 305/303/295 371/374/355 319/355/341 f 318/353/339 376/377/357 370/372/353 f 377/379/359 319/355/341 371/374/355 f 320/406/376 378/381/361 376/377/357 f 379/383/363 321/407/377 377/379/359 f 384/385/365 390/408/378 322/390/370 f 385/387/367 391/409/379 379/383/363 f 358/410/380 392/411/381 356/348/335 f 359/347/334 393/412/382 395/413/383 f 392/411/381 328/414/384 326/352/338 f 393/412/382 329/415/385 395/413/383 f 306/304/296 392/411/381 326/352/338 f 393/412/382 307/306/298 327/354/340 f 308/307/299 350/332/321 392/411/381 f 351/333/322 309/308/300 393/412/382 f 350/332/321 356/348/335 392/411/381 f 393/412/382 357/350/337 351/333/322 f 308/307/299 354/335/324 352/416/386 f 353/334/323 355/336/325 309/308/300 f 330/417/387 386/389/369 322/390/370 f 331/418/388 387/391/371 389/419/389 f 386/389/369 332/338/327 324/356/342 f 387/391/371 333/341/330 389/419/389 f 394/420/390 330/417/387 328/414/384 f 395/413/383 331/418/388 389/419/389 f 360/344/332 394/420/390 358/410/380 f 361/343/331 395/413/383 389/419/389 f 332/338/327 388/421/391 360/344/332 f 361/343/331 389/419/389 333/341/330 f 396/422/392 410/423/393 408/424/394 f 397/425/395 411/426/396 423/427/397 f 408/424/394 412/428/398 406/429/399 f 413/430/400 409/431/401 407/432/402 f 412/428/398 404/433/403 406/429/399 f 413/430/400 405/434/404 415/435/405 f 414/436/406 402/437/407 404/433/403 f 415/435/405 403/438/408 417/439/409 f 416/440/410 400/441/411 402/437/407 f 417/439/409 401/442/412 419/443/413 f 400/441/411 420/444/414 398/445/415 f 421/446/416 401/442/412 399/447/417 f 418/448/418 426/449/419 420/444/414 f 427/450/420 419/443/413 421/446/416 f 416/440/410 428/451/421 418/448/418 f 429/452/422 417/439/409 419/443/413 f 432/453/423 416/440/410 414/436/406 f 433/454/424 417/439/409 431/455/425 f 434/456/426 414/436/406 412/428/398 f 435/457/427 415/435/405 433/454/424 f 436/458/428 412/428/398 410/423/393 f 437/459/429 413/430/400 435/457/427 f 410/423/393 424/460/430 436/458/428 f 425/461/431 411/426/396 437/459/429 f 328/414/384 450/462/432 326/352/338 f 329/415/385 451/463/433 453/464/434 f 398/445/415 452/465/435 328/466/384 f 399/447/417 453/467/434 421/446/416 f 318/353/339 450/462/432 320/406/376 f 451/463/433 319/355/341 321/407/377 f 390/468/378 422/469/436 396/422/392 f 423/427/397 391/470/379 397/425/395 f 420/444/414 448/471/437 452/465/435 f 449/472/438 421/446/416 453/467/434 f 454/473/439 448/471/437 446/474/440 f 455/475/441 449/472/438 453/467/434 f 442/476/442 446/474/440 444/477/443 f 447/478/444 443/479/445 445/480/446 f 456/481/447 442/476/442 440/482/448 f 457/483/449 443/479/445 455/475/441 f 456/481/447 458/484/450 438/485/451 f 457/483/449 459/486/452 441/487/453 f 438/485/451 424/460/430 422/469/436 f 439/488/454 425/461/431 459/486/452 f 320/406/376 438/489/451 390/408/378 f 439/490/454 321/407/377 391/409/379 f 450/462/432 456/491/447 320/406/376 f 451/463/433 457/492/449 455/493/441 f 450/462/432 452/494/435 454/495/439 f 455/493/441 453/464/434 451/463/433 f 424/460/430 460/496/455 484/497/456 f 461/498/457 425/461/431 485/499/458 f 440/482/448 460/496/455 458/484/450 f 441/487/453 461/498/457 471/500/459 f 440/482/448 468/501/460 470/502/461 f 469/503/462 441/487/453 471/500/459 f 444/477/443 468/501/460 442/476/442 f 445/480/446 469/503/462 467/504/463 f 446/474/440 466/505/464 444/477/443 f 447/478/444 467/504/463 465/506/465 f 446/474/440 462/507/466 464/508/467 f 463/509/468 447/478/444 465/506/465 f 448/471/437 482/510/469 462/507/466 f 483/511/470 449/472/438 463/509/468 f 436/458/428 484/497/456 472/512/471 f 485/499/458 437/459/429 473/513/472 f 434/456/426 472/512/471 474/514/473 f 473/513/472 435/457/427 475/515/474 f 432/453/423 474/514/473 476/516/475 f 475/515/474 433/454/424 477/517/476 f 432/453/423 478/518/477 430/519/478 f 433/454/424 479/520/479 477/517/476 f 430/519/478 480/521/480 428/451/421 f 431/455/425 481/522/481 479/520/479 f 428/451/421 482/510/469 426/449/419 f 429/452/422 483/511/470 481/522/481 f 464/508/467 486/523/482 466/505/464 f 465/506/465 487/524/483 489/525/484 f 488/526/485 492/527/486 486/523/482 f 489/525/484 493/528/487 491/529/488 f 492/527/486 496/530/489 494/531/490 f 497/532/491 493/528/487 495/533/492 f 496/530/489 500/534/493 494/531/490 f 497/532/491 501/535/494 499/536/495 f 472/512/471 494/531/490 500/534/493 f 495/533/492 473/513/472 501/535/494 f 492/527/486 484/497/456 460/496/455 f 493/528/487 485/499/458 495/533/492 f 470/502/461 492/527/486 460/496/455 f 471/500/459 493/528/487 487/524/483 f 466/505/464 470/502/461 468/501/460 f 471/500/459 467/504/463 469/503/462 f 482/510/469 464/508/467 462/507/466 f 483/511/470 465/506/465 489/525/484 f 480/521/480 488/526/485 482/510/469 f 489/525/484 481/522/481 483/511/470 f 496/530/489 480/521/480 478/518/477 f 497/532/491 481/522/481 491/529/488 f 498/537/496 478/518/477 476/516/475 f 499/536/495 479/520/479 497/532/491 f 474/514/473 498/537/496 476/516/475 f 499/536/495 475/515/474 477/517/476 f 472/512/471 500/534/493 474/514/473 f 475/515/474 501/535/494 473/513/472 f 400/441/411 512/538/497 510/539/498 f 513/540/499 401/442/412 511/541/500 f 402/437/407 510/539/498 508/542/501 f 511/541/500 403/438/408 509/543/502 f 402/437/407 506/544/503 404/433/403 f 403/438/408 507/545/504 509/543/502 f 404/433/403 504/546/505 406/547/399 f 405/434/404 505/548/506 507/545/504 f 406/547/399 502/549/507 408/550/394 f 407/551/402 503/552/508 505/548/506 f 408/550/394 514/553/509 396/554/392 f 409/555/401 515/556/510 503/552/508 f 510/539/498 514/553/509 502/549/507 f 511/541/500 515/556/510 513/540/499 f 502/549/507 508/542/501 510/539/498 f 509/543/502 503/552/508 511/541/500 f 504/546/505 506/544/503 508/542/501 f 509/543/502 507/545/504 505/548/506 f 390/408/378 514/557/509 322/390/370 f 391/558/379 515/556/510 397/559/395 f 322/560/370 512/538/497 330/561/387 f 513/540/499 323/562/511 331/563/388 f 328/466/384 512/538/497 398/445/415 f 513/540/499 329/564/385 399/447/417 f 55/15/7 9/27/19 11/16/8 f 12/18/10 10/30/22 56/19/11 f 53/17/9 11/16/8 13/21/13 f 14/23/15 12/18/10 54/20/12 f 11/16/8 17/28/20 15/25/17 f 16/26/18 18/29/21 12/18/10 f 9/27/19 19/31/23 17/28/20 f 18/29/21 20/34/26 10/30/22 f 19/31/23 21/40/32 23/32/24 f 24/33/25 22/41/33 20/34/26 f 17/28/20 23/32/24 25/35/27 f 26/36/28 24/33/25 18/29/21 f 29/37/29 27/46/38 25/35/27 f 30/38/30 24/33/25 26/36/28 f 21/40/32 31/42/34 29/37/29 f 30/38/30 32/45/37 22/41/33 f 31/42/34 33/53/45 35/43/35 f 36/44/36 34/68/60 32/45/37 f 35/43/35 37/49/41 27/46/38 f 36/44/36 30/38/30 28/39/31 f 41/48/40 39/59/51 37/49/41 f 42/50/42 36/44/36 38/47/39 f 43/52/44 41/48/40 35/43/35 f 44/54/46 34/68/60 36/44/36 f 45/55/47 47/58/50 41/48/40 f 46/56/48 44/54/46 42/50/42 f 47/58/50 49/61/53 39/59/51 f 48/57/49 42/50/42 40/51/43 f 53/17/9 51/22/14 49/61/53 f 54/20/12 48/57/49 50/60/52 f 55/15/7 53/17/9 47/58/50 f 56/19/11 46/56/48 48/57/49 f 45/55/47 59/65/57 57/62/54 f 46/56/48 56/19/11 58/63/55 f 43/52/44 61/67/59 59/65/57 f 44/54/46 46/56/48 60/64/56 f 33/53/45 63/70/62 61/67/59 f 34/68/60 44/54/46 62/66/58 f 31/42/34 65/73/65 63/70/62 f 32/45/37 34/68/60 64/69/61 f 31/42/34 21/40/32 67/72/64 f 68/74/66 22/41/33 32/45/37 f 21/40/32 19/31/23 71/75/67 f 72/76/68 20/34/26 22/41/33 f 19/31/23 9/27/19 73/77/69 f 74/78/70 10/30/22 20/34/26 f 9/27/19 55/15/7 57/62/54 f 58/63/55 56/19/11 10/30/22 f 182/81/73 184/85/77 99/82/74 f 183/84/76 98/89/81 99/82/74 f 180/86/78 182/81/73 97/83/75 f 181/88/80 96/92/84 98/89/81 f 93/90/82 178/94/86 180/86/78 f 181/88/80 179/95/87 94/91/83 f 91/93/85 176/98/90 178/94/86 f 179/95/87 177/99/91 92/96/88 f 89/97/89 174/172/164 176/98/90 f 177/99/91 175/175/167 90/100/92 f 87/101/93 100/108/100 154/102/94 f 155/104/96 101/337/326 88/105/97 f 102/107/99 156/112/104 154/102/94 f 103/109/101 101/337/326 155/104/96 f 102/107/99 104/115/107 158/111/103 f 159/113/105 105/302/294 103/109/101 f 106/114/106 160/119/111 158/111/103 f 107/116/108 105/302/294 159/113/105 f 108/118/110 162/123/115 160/119/111 f 109/120/112 107/116/108 161/117/109 f 110/122/114 164/127/119 162/123/115 f 111/124/116 109/120/112 163/121/113 f 110/122/114 112/130/122 166/126/118 f 167/128/120 113/293/285 111/124/116 f 114/129/121 168/134/126 166/126/118 f 115/131/123 113/293/285 167/128/120 f 116/133/125 170/138/130 168/134/126 f 117/135/127 115/131/123 169/132/124 f 75/137/129 76/139/131 170/138/130 f 75/137/129 117/135/127 171/136/128 f 136/140/132 168/134/126 170/138/130 f 137/142/134 119/565/512 171/136/128 f 136/140/132 187/143/135 166/126/118 f 167/128/120 188/145/137 137/142/134 f 164/127/119 166/126/118 187/143/135 f 165/125/117 135/147/139 188/145/137 f 162/123/115 164/127/119 134/144/136 f 163/121/113 133/149/141 135/147/139 f 160/119/111 162/123/115 132/146/138 f 161/117/109 131/151/143 133/149/141 f 158/111/103 160/119/111 130/148/140 f 159/113/105 129/153/145 131/151/143 f 156/112/104 158/111/103 128/150/142 f 157/110/102 127/155/147 129/153/145 f 154/102/94 156/112/104 126/152/144 f 155/104/96 125/157/149 127/155/147 f 172/103/95 154/102/94 124/154/146 f 173/106/98 123/159/151 125/157/149 f 122/156/148 189/165/157 185/158/150 f 185/158/150 190/166/158 123/159/151 f 170/138/130 76/139/131 120/160/152 f 171/136/128 119/565/512 121/161/153 f 120/160/152 76/139/131 186/162/154 f 186/162/154 76/139/131 121/161/153 f 189/165/157 191/163/155 186/162/154 f 190/166/158 185/158/150 186/162/154 f 143/167/159 145/226/218 184/85/77 f 184/85/77 145/226/218 144/168/160 f 141/169/161 143/167/159 182/81/73 f 183/84/76 144/168/160 142/170/162 f 141/169/161 180/86/78 178/94/86 f 142/170/162 140/176/168 179/95/87 f 174/172/164 195/178/170 193/173/165 f 194/174/166 196/180/172 175/175/167 f 139/171/163 178/94/86 176/98/90 f 177/99/91 179/95/87 140/176/168 f 198/177/169 197/181/173 195/178/170 f 198/177/169 153/188/180 196/180/172 f 195/178/170 197/181/173 77/182/174 f 196/180/172 194/174/166 77/182/174 f 139/171/163 193/173/165 77/182/174 f 140/176/168 138/183/175 77/182/174 f 150/184/176 201/190/182 199/185/177 f 200/186/178 202/191/183 151/187/179 f 148/189/181 203/205/197 201/190/182 f 202/191/183 204/196/188 149/192/184 f 205/193/185 203/205/197 148/189/181 f 206/195/187 147/194/186 149/192/184 f 79/197/189 205/193/185 147/194/186 f 79/197/189 146/198/190 147/194/186 f 152/179/171 199/185/177 78/199/191 f 153/188/180 198/177/169 78/199/191 f 199/185/177 214/208/200 216/200/192 f 200/186/178 78/199/191 216/200/192 f 79/197/189 207/210/202 208/202/194 f 209/203/195 207/210/202 79/197/189 f 205/193/185 208/202/194 210/204/196 f 211/206/198 209/203/195 206/195/187 f 210/204/196 212/209/201 201/190/182 f 211/206/198 204/196/188 202/191/183 f 201/190/182 212/209/201 214/208/200 f 215/201/193 213/207/199 202/191/183 f 212/209/201 210/204/196 208/202/194 f 213/207/199 207/210/202 209/203/195 f 207/210/202 216/200/192 214/208/200 f 215/201/193 216/200/192 207/210/202 f 147/194/186 148/189/181 172/103/95 f 173/106/98 149/192/184 147/194/186 f 148/189/181 150/184/176 219/211/203 f 220/212/204 151/187/179 149/192/184 f 152/179/171 221/214/206 219/211/203 f 153/188/180 151/187/179 220/212/204 f 195/178/170 174/172/164 221/214/206 f 196/180/172 153/188/180 222/213/205 f 217/215/207 221/214/206 174/172/164 f 218/216/208 90/100/92 175/175/167 f 223/217/209 219/211/203 221/214/206 f 224/218/210 218/216/208 222/213/205 f 87/101/93 172/103/95 219/211/203 f 220/212/204 173/106/98 88/105/97 f 138/183/175 80/221/213 230/219/211 f 138/183/175 140/176/168 231/220/212 f 141/169/161 139/171/163 230/219/211 f 231/220/212 140/176/168 142/170/162 f 143/167/159 141/169/161 228/222/214 f 229/223/215 142/170/162 144/168/160 f 145/226/218 143/167/159 226/224/216 f 227/225/217 144/168/160 145/226/218 f 226/224/216 237/231/223 239/228/220 f 227/225/217 225/227/219 239/228/220 f 226/224/216 228/222/214 235/230/222 f 236/232/224 229/223/215 227/225/217 f 228/222/214 230/219/211 233/233/225 f 234/234/226 231/220/212 229/223/215 f 80/221/213 232/235/227 233/233/225 f 80/221/213 231/220/212 234/234/226 f 232/235/227 239/228/220 237/231/223 f 238/229/221 239/228/220 232/235/227 f 191/163/155 189/165/157 242/236/228 f 243/238/230 190/166/158 192/164/156 f 120/160/152 191/163/155 240/237/229 f 241/239/231 192/164/156 121/161/153 f 120/160/152 262/240/232 264/242/234 f 121/161/153 119/565/512 265/243/235 f 122/156/148 260/246/238 242/236/228 f 123/159/151 190/166/158 243/238/230 f 122/156/148 124/154/146 258/245/237 f 259/247/239 125/157/149 123/159/151 f 124/154/146 126/152/144 256/248/240 f 257/249/241 127/155/147 125/157/149 f 126/152/144 128/150/142 254/250/242 f 255/251/243 129/153/145 127/155/147 f 128/150/142 130/148/140 252/252/244 f 253/253/245 131/151/143 129/153/145 f 132/146/138 250/255/247 252/252/244 f 133/149/141 131/151/143 253/253/245 f 134/144/136 248/258/250 250/255/247 f 135/147/139 133/149/141 251/254/246 f 134/144/136 187/143/135 244/257/249 f 245/259/251 188/145/137 135/147/139 f 187/143/135 136/140/132 246/260/252 f 247/261/253 137/142/134 188/145/137 f 136/140/132 118/141/133 264/242/234 f 265/243/235 119/565/512 137/142/134 f 264/242/234 266/284/276 284/262/254 f 265/243/235 247/261/253 285/263/255 f 244/257/249 246/260/252 284/262/254 f 285/263/255 247/261/253 245/259/251 f 244/257/249 286/265/257 282/267/259 f 245/259/251 249/256/248 283/268/260 f 248/258/250 282/267/259 280/269/261 f 249/256/248 251/254/246 281/270/262 f 252/252/244 250/255/247 280/269/261 f 281/270/262 251/254/246 253/253/245 f 252/252/244 278/271/263 276/273/265 f 253/253/245 255/251/243 277/274/266 f 256/248/240 254/250/242 276/273/265 f 277/274/266 255/251/243 257/249/241 f 256/248/240 274/275/267 272/277/269 f 257/249/241 259/247/239 273/278/270 f 258/245/237 272/277/269 270/279/271 f 259/247/239 261/244/236 271/280/272 f 242/236/228 260/246/238 270/279/271 f 271/280/272 261/244/236 243/238/230 f 264/242/234 262/240/232 268/283/275 f 269/285/277 263/241/233 265/243/235 f 262/240/232 240/237/229 290/286/278 f 291/287/279 241/239/231 263/241/233 f 240/237/229 242/236/228 288/281/273 f 289/282/274 243/238/230 241/239/231 f 75/137/129 116/133/125 292/288/280 f 293/290/282 117/135/127 75/137/129 f 116/133/125 114/129/121 294/291/283 f 295/292/284 115/131/123 117/135/127 f 112/130/122 296/295/287 294/291/283 f 113/293/285 115/131/123 295/292/284 f 110/122/114 298/297/289 296/295/287 f 111/124/116 113/293/285 297/294/286 f 108/118/110 300/300/292 298/297/289 f 109/120/112 111/124/116 299/296/288 f 108/118/110 106/114/106 302/299/291 f 303/301/293 107/116/108 109/120/112 f 104/115/107 304/305/297 302/299/291 f 105/302/294 107/116/108 303/301/293 f 104/115/107 102/107/99 306/304/296 f 307/306/298 103/109/101 105/302/294 f 102/107/99 100/108/100 308/307/299 f 309/308/300 101/337/326 103/109/101 f 317/309/301 336/329/318 346/310/302 f 317/312/301 316/317/303 347/313/304 f 316/311/303 346/310/302 344/315/306 f 316/317/303 315/321/307 345/318/308 f 315/316/307 344/315/306 348/319/309 f 315/321/307 314/320/310 349/322/311 f 97/83/75 99/82/74 314/320/310 f 314/320/310 99/82/74 98/89/81 f 95/87/79 97/83/75 348/319/309 f 349/322/311 98/89/81 96/92/84 f 93/90/82 95/87/79 342/323/312 f 343/324/313 96/92/84 94/91/83 f 91/93/85 93/90/82 338/325/314 f 339/326/315 94/91/83 92/96/88 f 338/325/314 344/315/306 346/310/302 f 347/313/304 345/318/308 339/326/315 f 342/323/312 348/319/309 344/315/306 f 343/324/313 339/326/315 345/318/308 f 340/327/316 346/310/302 336/329/318 f 341/328/317 335/331/320 337/314/305 f 89/97/89 91/93/85 340/327/316 f 341/328/317 92/96/88 90/100/92 f 350/332/321 352/416/386 223/217/209 f 351/333/322 218/216/208 224/218/210 f 334/330/319 350/332/321 217/215/207 f 335/331/320 90/100/92 218/216/208 f 223/217/209 352/416/386 354/335/324 f 224/218/210 88/105/97 355/336/325 f 354/335/324 308/307/299 100/108/100 f 355/336/325 88/105/97 101/337/326 f 332/338/327 360/344/332 312/339/328 f 333/341/330 85/359/329 312/342/328 f 360/344/332 358/410/380 86/345/333 f 361/343/331 312/342/328 86/346/333 f 86/345/333 358/410/380 356/348/335 f 357/350/337 359/347/334 86/346/333 f 313/349/336 356/348/335 336/329/318 f 337/314/305 357/350/337 313/351/336 f 336/329/318 356/348/335 350/332/321 f 337/314/305 335/331/320 351/333/322 f 304/305/297 306/304/296 326/352/338 f 327/354/340 307/306/298 305/303/295 f 324/356/342 332/338/327 85/340/329 f 325/358/344 84/370/343 85/359/329 f 366/360/345 364/392/372 311/361/346 f 367/363/348 310/397/347 311/364/346 f 311/361/346 364/392/372 362/366/350 f 363/368/352 365/365/349 311/364/346 f 83/367/351 362/366/350 324/356/342 f 325/358/344 363/368/352 83/369/351 f 300/371/292 302/299/291 370/372/353 f 371/374/355 303/301/293 301/375/290 f 372/373/354 370/372/353 376/377/357 f 377/379/359 371/374/355 373/376/356 f 374/378/358 376/377/357 378/381/361 f 379/383/363 377/379/359 375/380/360 f 380/382/362 378/381/361 384/385/365 f 385/387/367 379/383/363 381/384/364 f 386/389/369 382/386/366 384/385/365 f 387/391/371 323/566/511 385/387/367 f 324/356/342 362/366/350 382/386/366 f 383/388/368 363/368/352 325/358/344 f 362/366/350 364/392/372 380/382/362 f 381/384/364 365/365/349 363/368/352 f 364/392/372 366/360/345 374/378/358 f 375/380/360 367/363/348 365/365/349 f 366/360/345 368/393/373 372/373/354 f 373/376/356 369/395/374 367/363/348 f 300/371/292 372/373/354 368/393/373 f 301/375/290 299/402/288 369/395/374 f 368/393/373 366/360/345 310/362/347 f 369/395/374 82/403/375 310/397/347 f 292/398/280 294/567/283 296/399/287 f 297/400/286 295/568/284 293/401/282 f 292/398/280 298/394/289 368/393/373 f 369/395/374 299/402/288 293/401/282 f 304/305/297 318/353/339 370/372/353 f 305/303/295 303/301/293 371/374/355 f 318/353/339 320/406/376 376/377/357 f 377/379/359 321/407/377 319/355/341 f 320/406/376 390/408/378 378/381/361 f 379/383/363 391/409/379 321/407/377 f 384/385/365 378/381/361 390/408/378 f 385/387/367 323/566/511 391/409/379 f 358/410/380 394/420/390 392/411/381 f 359/347/334 357/350/337 393/412/382 f 392/411/381 394/420/390 328/414/384 f 393/412/382 327/354/340 329/415/385 f 306/304/296 308/307/299 392/411/381 f 393/412/382 309/308/300 307/306/298 f 308/307/299 352/416/386 350/332/321 f 351/333/322 353/334/323 309/308/300 f 330/417/387 388/421/391 386/389/369 f 331/418/388 323/566/511 387/391/371 f 386/389/369 388/421/391 332/338/327 f 387/391/371 325/358/344 333/341/330 f 394/420/390 388/421/391 330/417/387 f 395/413/383 329/415/385 331/418/388 f 360/344/332 388/421/391 394/420/390 f 361/343/331 359/347/334 395/413/383 f 396/422/392 422/469/436 410/423/393 f 397/425/395 409/431/401 411/426/396 f 408/424/394 410/423/393 412/428/398 f 413/430/400 411/426/396 409/431/401 f 412/428/398 414/436/406 404/433/403 f 413/430/400 407/432/402 405/434/404 f 414/436/406 416/440/410 402/437/407 f 415/435/405 405/434/404 403/438/408 f 416/440/410 418/448/418 400/441/411 f 417/439/409 403/438/408 401/442/412 f 400/441/411 418/448/418 420/444/414 f 421/446/416 419/443/413 401/442/412 f 418/448/418 428/451/421 426/449/419 f 427/450/420 429/452/422 419/443/413 f 416/440/410 430/519/478 428/451/421 f 429/452/422 431/455/425 417/439/409 f 432/453/423 430/519/478 416/440/410 f 433/454/424 415/435/405 417/439/409 f 434/456/426 432/453/423 414/436/406 f 435/457/427 413/430/400 415/435/405 f 436/458/428 434/456/426 412/428/398 f 437/459/429 411/426/396 413/430/400 f 410/423/393 422/469/436 424/460/430 f 425/461/431 423/427/397 411/426/396 f 328/414/384 452/494/435 450/462/432 f 329/415/385 327/354/340 451/463/433 f 398/445/415 420/444/414 452/465/435 f 399/447/417 329/564/385 453/467/434 f 318/353/339 326/352/338 450/462/432 f 451/463/433 327/354/340 319/355/341 f 390/468/378 438/485/451 422/469/436 f 423/427/397 439/488/454 391/470/379 f 420/444/414 426/449/419 448/471/437 f 449/472/438 427/450/420 421/446/416 f 454/473/439 452/465/435 448/471/437 f 455/475/441 447/478/444 449/472/438 f 442/476/442 454/473/439 446/474/440 f 447/478/444 455/475/441 443/479/445 f 456/481/447 454/473/439 442/476/442 f 457/483/449 441/487/453 443/479/445 f 456/481/447 440/482/448 458/484/450 f 457/483/449 439/488/454 459/486/452 f 438/485/451 458/484/450 424/460/430 f 439/488/454 423/427/397 425/461/431 f 320/406/376 456/491/447 438/489/451 f 439/490/454 457/492/449 321/407/377 f 450/462/432 454/495/439 456/491/447 f 451/463/433 321/407/377 457/492/449 f 424/460/430 458/484/450 460/496/455 f 461/498/457 459/486/452 425/461/431 f 440/482/448 470/502/461 460/496/455 f 441/487/453 459/486/452 461/498/457 f 440/482/448 442/476/442 468/501/460 f 469/503/462 443/479/445 441/487/453 f 444/477/443 466/505/464 468/501/460 f 445/480/446 443/479/445 469/503/462 f 446/474/440 464/508/467 466/505/464 f 447/478/444 445/480/446 467/504/463 f 446/474/440 448/471/437 462/507/466 f 463/509/468 449/472/438 447/478/444 f 448/471/437 426/449/419 482/510/469 f 483/511/470 427/450/420 449/472/438 f 436/458/428 424/460/430 484/497/456 f 485/499/458 425/461/431 437/459/429 f 434/456/426 436/458/428 472/512/471 f 473/513/472 437/459/429 435/457/427 f 432/453/423 434/456/426 474/514/473 f 475/515/474 435/457/427 433/454/424 f 432/453/423 476/516/475 478/518/477 f 433/454/424 431/455/425 479/520/479 f 430/519/478 478/518/477 480/521/480 f 431/455/425 429/452/422 481/522/481 f 428/451/421 480/521/480 482/510/469 f 429/452/422 427/450/420 483/511/470 f 464/508/467 488/526/485 486/523/482 f 465/506/465 467/504/463 487/524/483 f 488/526/485 490/569/513 492/527/486 f 489/525/484 487/524/483 493/528/487 f 492/527/486 490/569/513 496/530/489 f 497/532/491 491/529/488 493/528/487 f 496/530/489 498/537/496 500/534/493 f 497/532/491 495/533/492 501/535/494 f 472/512/471 484/497/456 494/531/490 f 495/533/492 485/499/458 473/513/472 f 492/527/486 494/531/490 484/497/456 f 493/528/487 461/498/457 485/499/458 f 470/502/461 486/523/482 492/527/486 f 471/500/459 461/498/457 493/528/487 f 466/505/464 486/523/482 470/502/461 f 471/500/459 487/524/483 467/504/463 f 482/510/469 488/526/485 464/508/467 f 483/511/470 463/509/468 465/506/465 f 480/521/480 490/569/513 488/526/485 f 489/525/484 491/529/488 481/522/481 f 496/530/489 490/569/513 480/521/480 f 497/532/491 479/520/479 481/522/481 f 498/537/496 496/530/489 478/518/477 f 499/536/495 477/517/476 479/520/479 f 474/514/473 500/534/493 498/537/496 f 499/536/495 501/535/494 475/515/474 f 400/441/411 398/445/415 512/538/497 f 513/540/499 399/447/417 401/442/412 f 402/437/407 400/441/411 510/539/498 f 511/541/500 401/442/412 403/438/408 f 402/437/407 508/542/501 506/544/503 f 403/438/408 405/434/404 507/545/504 f 404/433/403 506/544/503 504/546/505 f 405/434/404 407/551/402 505/548/506 f 406/547/399 504/546/505 502/549/507 f 407/551/402 409/555/401 503/552/508 f 408/550/394 502/549/507 514/553/509 f 409/555/401 397/559/395 515/556/510 f 510/539/498 512/538/497 514/553/509 f 511/541/500 503/552/508 515/556/510 f 502/549/507 504/546/505 508/542/501 f 509/543/502 505/548/506 503/552/508 f 390/408/378 396/570/392 514/557/509 f 391/558/379 323/562/511 515/556/510 f 322/560/370 514/553/509 512/538/497 f 513/540/499 515/556/510 323/562/511 f 328/466/384 330/561/387 512/538/497 f 513/540/499 331/563/388 329/564/385 ================================================ FILE: examples/features/src/ray_scene/mod.rs ================================================ use crate::utils; use bytemuck::{Pod, Zeroable}; use glam::{Mat4, Quat, Vec3}; use std::f32::consts::PI; use std::ops::IndexMut; use std::{borrow::Cow, iter, mem, ops::Range}; use wgpu::util::DeviceExt; // from cube #[repr(C)] #[derive(Debug, Clone, Copy, Pod, Zeroable, Default)] struct Vertex { pos: [f32; 3], _p0: [u32; 1], normal: [f32; 3], _p1: [u32; 1], uv: [f32; 2], _p2: [u32; 2], } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Uniforms { view_inverse: Mat4, proj_inverse: Mat4, } #[derive(Debug, Clone, Default)] struct RawSceneComponents { vertices: Vec, indices: Vec, geometries: Vec<(Range, Material)>, // index range, material instances: Vec<(Range, Range)>, // vertex range, geometry range } struct SceneComponents { vertices: wgpu::Buffer, indices: wgpu::Buffer, geometries: wgpu::Buffer, instances: wgpu::Buffer, bottom_level_acceleration_structures: Vec, } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct InstanceEntry { first_vertex: u32, first_geometry: u32, last_geometry: u32, _pad: u32, } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable, Default)] struct GeometryEntry { first_index: u32, _p0: [u32; 3], material: Material, } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable, Default, Debug)] struct Material { roughness_exponent: f32, metalness: f32, specularity: f32, _p0: [u32; 1], albedo: [f32; 3], _p1: [u32; 1], } fn load_model(scene: &mut RawSceneComponents, path: &str) { let path = env!("CARGO_MANIFEST_DIR").to_string() + "/src" + path; println!("{path}"); let mut object = obj::Obj::load(path).unwrap(); object.load_mtls().unwrap(); let data = object.data; let start_vertex_index = scene.vertices.len(); let start_geometry_index = scene.geometries.len(); let mut mapping = std::collections::HashMap::<(usize, Option, usize), usize>::new(); let mut next_index = 0; for object in data.objects { for group in object.groups { let start_index_index = scene.indices.len(); for poly in group.polys { for end_index in 2..poly.0.len() { for &index in &[0, end_index - 1, end_index] { let obj::IndexTuple(position_id, texture_id, normal_id) = poly.0[index]; let uv = texture_id .map(|texture_id| data.texture[texture_id]) .unwrap_or_default(); let normal_id = normal_id.expect("normals required"); let index = *mapping .entry((position_id, texture_id, normal_id)) .or_insert(next_index); if index == next_index { next_index += 1; scene.vertices.push(Vertex { pos: data.position[position_id], uv, normal: data.normal[normal_id], ..Default::default() }) } scene.indices.push(index as u32); } } } let mut material: Material = Default::default(); if let Some(obj::ObjMaterial::Mtl(mat)) = group.material { if let Some(kd) = mat.kd { material.albedo = kd; } if let Some(ns) = mat.ns { material.roughness_exponent = ns; } if let Some(ka) = mat.ka { material.metalness = ka[0]; } if let Some(ks) = mat.ks { material.specularity = ks[0]; } } scene .geometries .push((start_index_index..scene.indices.len(), material)); } } scene.instances.push(( start_vertex_index..scene.vertices.len(), start_geometry_index..scene.geometries.len(), )); // dbg!(scene.vertices.len()); // dbg!(scene.indices.len()); // dbg!(&scene.geometries); // dbg!(&scene.instances); } fn upload_scene_components( device: &wgpu::Device, queue: &wgpu::Queue, scene: &RawSceneComponents, ) -> SceneComponents { let geometry_buffer_content = scene .geometries .iter() .map(|(index_range, material)| GeometryEntry { first_index: index_range.start as u32, material: *material, ..Default::default() }) .collect::>(); let instance_buffer_content = scene .instances .iter() .map(|geometry| InstanceEntry { first_vertex: geometry.0.start as u32, first_geometry: geometry.1.start as u32, last_geometry: geometry.1.end as u32, _pad: 1, }) .collect::>(); let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertices"), contents: bytemuck::cast_slice(&scene.vertices), usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::BLAS_INPUT, }); let indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Indices"), contents: bytemuck::cast_slice(&scene.indices), usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::BLAS_INPUT, }); let geometries = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Geometries"), contents: bytemuck::cast_slice(&geometry_buffer_content), usage: wgpu::BufferUsages::STORAGE, }); let instances = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Instances"), contents: bytemuck::cast_slice(&instance_buffer_content), usage: wgpu::BufferUsages::STORAGE, }); let (size_descriptors, bottom_level_acceleration_structures): (Vec<_>, Vec<_>) = scene .instances .iter() .map(|(vertex_range, geometry_range)| { let size_desc: Vec = (*geometry_range) .clone() .map(|i| wgpu::BlasTriangleGeometrySizeDescriptor { vertex_format: wgpu::VertexFormat::Float32x3, vertex_count: vertex_range.end as u32 - vertex_range.start as u32, index_format: Some(wgpu::IndexFormat::Uint32), index_count: Some( scene.geometries[i].0.end as u32 - scene.geometries[i].0.start as u32, ), flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE, }) .collect(); let blas = device.create_blas( &wgpu::CreateBlasDescriptor { label: None, flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, update_mode: wgpu::AccelerationStructureUpdateMode::Build, }, wgpu::BlasGeometrySizeDescriptors::Triangles { descriptors: size_desc.clone(), }, ); (size_desc, blas) }) .unzip(); let build_entries: Vec<_> = scene .instances .iter() .zip(size_descriptors.iter()) .zip(bottom_level_acceleration_structures.iter()) .map(|(((vertex_range, geometry_range), size_desc), blas)| { let triangle_geometries: Vec<_> = size_desc .iter() .zip(geometry_range.clone()) .map(|(size, i)| wgpu::BlasTriangleGeometry { size, vertex_buffer: &vertices, first_vertex: vertex_range.start as u32, vertex_stride: mem::size_of::() as u64, index_buffer: Some(&indices), first_index: Some(scene.geometries[i].0.start as u32), transform_buffer: None, transform_buffer_offset: None, }) .collect(); wgpu::BlasBuildEntry { blas, geometry: wgpu::BlasGeometries::TriangleGeometries(triangle_geometries), } }) .collect(); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); encoder.build_acceleration_structures(build_entries.iter(), iter::empty()); queue.submit(Some(encoder.finish())); SceneComponents { vertices, indices, geometries, instances, bottom_level_acceleration_structures, } } fn load_scene(device: &wgpu::Device, queue: &wgpu::Queue) -> SceneComponents { let mut scene = RawSceneComponents::default(); load_model(&mut scene, "/skybox/models/rustacean-3d.obj"); load_model(&mut scene, "/ray_scene/cube.obj"); upload_scene_components(device, queue, &scene) } struct Example { uniforms: Uniforms, uniform_buf: wgpu::Buffer, tlas: wgpu::Tlas, pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, scene_components: SceneComponents, animation_timer: utils::AnimationTimer, } impl crate::framework::Example for Example { fn required_features() -> wgpu::Features { wgpu::Features::EXPERIMENTAL_RAY_QUERY } fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { wgpu::DownlevelCapabilities { flags: wgpu::DownlevelFlags::COMPUTE_SHADERS, ..Default::default() } } fn required_limits() -> wgpu::Limits { wgpu::Limits::default().using_minimum_supported_acceleration_structure_values() } fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { let side_count = 8; let scene_components = load_scene(device, queue); let uniforms = { let view = Mat4::look_at_rh(Vec3::new(0.0, 0.0, 2.5), Vec3::ZERO, Vec3::Y); let proj = Mat4::perspective_rh( 59.0_f32.to_radians(), config.width as f32 / config.height as f32, 0.001, 1000.0, ); Uniforms { view_inverse: view.inverse(), proj_inverse: proj.inverse(), } }; let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Uniform Buffer"), contents: bytemuck::cast_slice(&[uniforms]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); let tlas = device.create_tlas(&wgpu::CreateTlasDescriptor { label: None, flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, update_mode: wgpu::AccelerationStructureUpdateMode::Build, max_instances: side_count * side_count, }); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: None, vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(config.format.into())], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let bind_group_layout = pipeline.get_bind_group_layout(0); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: uniform_buf.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 5, resource: tlas.as_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: scene_components.vertices.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 2, resource: scene_components.indices.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 3, resource: scene_components.geometries.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 4, resource: scene_components.instances.as_entire_binding(), }, ], }); Example { uniforms, uniform_buf, tlas, pipeline, bind_group, scene_components, animation_timer: utils::AnimationTimer::default(), } } fn update(&mut self, _event: winit::event::WindowEvent) {} fn resize( &mut self, config: &wgpu::SurfaceConfiguration, _device: &wgpu::Device, queue: &wgpu::Queue, ) { let proj = Mat4::perspective_rh( 59.0_f32.to_radians(), config.width as f32 / config.height as f32, 0.001, 1000.0, ); self.uniforms.proj_inverse = proj.inverse(); queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(&[self.uniforms])); } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { // scene update { let dist = 3.5; let side_count = 2; let anim_time = self.animation_timer.time(); for x in 0..side_count { for y in 0..side_count { let instance = self.tlas.index_mut(x + y * side_count); let blas_index = (x + y) % self .scene_components .bottom_level_acceleration_structures .len(); let x = x as f32 / (side_count - 1) as f32; let y = y as f32 / (side_count - 1) as f32; let x = x * 2.0 - 1.0; let y = y * 2.0 - 1.0; let transform = Mat4::from_rotation_translation( Quat::from_euler( glam::EulerRot::XYZ, anim_time * 0.5 * 0.342, anim_time * 0.5 * 0.254, anim_time * 0.5 * 0.832 + PI, ), Vec3 { x: x * dist, y: y * dist, z: -14.0, }, ); let transform = transform.transpose().to_cols_array()[..12] .try_into() .unwrap(); *instance = Some(wgpu::TlasInstance::new( &self.scene_components.bottom_level_acceleration_structures[blas_index], transform, blas_index as u32, 0xff, )); } } } let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); encoder.build_acceleration_structures(iter::empty(), iter::once(&self.tlas)); { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, Some(&self.bind_group), &[]); rpass.draw(0..3, 0..1); } queue.submit(Some(encoder.finish())); } } pub fn main() { crate::framework::run::("ray_scene"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "ray_scene", image_path: "/examples/features/src/ray_scene/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default() // https://github.com/gfx-rs/wgpu/issues/9100 .expect_fail(wgpu_test::FailureCase::backend(wgpu::Backends::METAL)), comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/ray_scene/shader.wgsl ================================================ enable wgpu_ray_query; struct VertexOutput { @builtin(position) position: vec4, @location(0) tex_coords: vec2, }; @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { var result: VertexOutput; let x = i32(vertex_index) / 2; let y = i32(vertex_index) & 1; let tc = vec2( f32(x) * 2.0, f32(y) * 2.0 ); result.position = vec4( tc.x * 2.0 - 1.0, 1.0 - tc.y * 2.0, 0.0, 1.0 ); result.tex_coords = tc; return result; } /* The contents of the RayQuery struct are roughly as follows let RAY_FLAG_NONE = 0x00u; let RAY_FLAG_OPAQUE = 0x01u; let RAY_FLAG_NO_OPAQUE = 0x02u; let RAY_FLAG_TERMINATE_ON_FIRST_HIT = 0x04u; let RAY_FLAG_SKIP_CLOSEST_HIT_SHADER = 0x08u; let RAY_FLAG_CULL_BACK_FACING = 0x10u; let RAY_FLAG_CULL_FRONT_FACING = 0x20u; let RAY_FLAG_CULL_OPAQUE = 0x40u; let RAY_FLAG_CULL_NO_OPAQUE = 0x80u; let RAY_FLAG_SKIP_TRIANGLES = 0x100u; let RAY_FLAG_SKIP_AABBS = 0x200u; let RAY_QUERY_INTERSECTION_NONE = 0u; let RAY_QUERY_INTERSECTION_TRIANGLE = 1u; let RAY_QUERY_INTERSECTION_GENERATED = 2u; let RAY_QUERY_INTERSECTION_AABB = 3u; struct RayDesc { flags: u32, cull_mask: u32, t_min: f32, t_max: f32, origin: vec3, dir: vec3, } struct RayIntersection { kind: u32, t: f32, instance_custom_data: u32, instance_index: u32, sbt_record_offset: u32, geometry_index: u32, primitive_index: u32, barycentrics: vec2, front_face: bool, object_to_world: mat4x3, world_to_object: mat4x3, } */ struct Uniforms { view_inv: mat4x4, proj_inv: mat4x4, }; struct Vertex { pos: vec3, normal: vec3, uv: vec2, }; struct Instance { first_vertex: u32, first_geometry: u32, last_geometry: u32, _pad: u32 }; struct Material{ roughness_exponent: f32, metalness: f32, specularity: f32, albedo: vec3 } struct Geometry { first_index: u32, material: Material, }; @group(0) @binding(0) var uniforms: Uniforms; @group(0) @binding(1) var vertices: array; @group(0) @binding(2) var indices: array; @group(0) @binding(3) var geometries: array; @group(0) @binding(4) var instances: array; @group(0) @binding(5) var acc_struct: acceleration_structure; @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { var color = vec4(vertex.tex_coords, 0.0, 1.0); let d = vertex.tex_coords * 2.0 - 1.0; let origin = (uniforms.view_inv * vec4(0.0,0.0,0.0,1.0)).xyz; let temp = uniforms.proj_inv * vec4(d.x, d.y, 1.0, 1.0); let direction = (uniforms.view_inv * vec4(normalize(temp.xyz), 0.0)).xyz; var rq: ray_query; rayQueryInitialize(&rq, acc_struct, RayDesc(0u, 0xFFu, 0.1, 200.0, origin, direction)); rayQueryProceed(&rq); let intersection = rayQueryGetCommittedIntersection(&rq); if (intersection.kind != RAY_QUERY_INTERSECTION_NONE) { let instance = instances[intersection.instance_custom_data]; let geometry = geometries[intersection.geometry_index + instance.first_geometry]; let index_offset = geometry.first_index; let vertex_offset = instance.first_vertex; let first_index_index = intersection.primitive_index * 3u + index_offset; let v_0 = vertices[vertex_offset+indices[first_index_index+0u]]; let v_1 = vertices[vertex_offset+indices[first_index_index+1u]]; let v_2 = vertices[vertex_offset+indices[first_index_index+2u]]; let bary = vec3(1.0 - intersection.barycentrics.x - intersection.barycentrics.y, intersection.barycentrics); let pos = v_0.pos * bary.x + v_1.pos * bary.y + v_2.pos * bary.z; let normal_raw = v_0.normal * bary.x + v_1.normal * bary.y + v_2.normal * bary.z; let uv = v_0.uv * bary.x + v_1.uv * bary.y + v_2.uv * bary.z; let normal = normalize(normal_raw); let material = geometry.material; color = vec4(material.albedo, 1.0); if(intersection.instance_custom_data == 1u){ color = vec4(normal, 1.0); } } return color; } ================================================ FILE: examples/features/src/ray_shadows/README.md ================================================ # ray-shadows This example renders a ray traced shadow with hardware acceleration. ## To Run ``` cargo run --bin wgpu-examples ray_shadows ``` ## Screenshots ![Shadow example](screenshot.png) ================================================ FILE: examples/features/src/ray_shadows/mod.rs ================================================ use std::{borrow::Cow, iter, mem}; use bytemuck::{Pod, Zeroable}; use glam::{Mat4, Vec3}; use wgpu::util::DeviceExt; use wgpu::{vertex_attr_array, IndexFormat, VertexBufferLayout}; use crate::utils; // from cube #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Vertex { _pos: [f32; 3], _normal: [f32; 3], } fn vertex(pos: [f32; 3], normal: [f32; 3]) -> Vertex { Vertex { _pos: pos, _normal: normal, } } fn create_vertices() -> (Vec, Vec) { let vertex_data = [ // base vertex([-1.0, 0.0, -1.0], [0.0, 1.0, 0.0]), vertex([-1.0, 0.0, 1.0], [0.0, 1.0, 0.0]), vertex([1.0, 0.0, -1.0], [0.0, 1.0, 0.0]), vertex([1.0, 0.0, 1.0], [0.0, 1.0, 0.0]), //shadow caster vertex([-(1.0 / 3.0), 0.0, 1.0], [0.0, 0.0, 1.0]), vertex([-(1.0 / 3.0), 2.0 / 3.0, 1.0], [0.0, 0.0, 1.0]), vertex([1.0 / 3.0, 0.0, 1.0], [0.0, 0.0, 1.0]), vertex([1.0 / 3.0, 2.0 / 3.0, 1.0], [0.0, 0.0, 1.0]), ]; let index_data: &[u16] = &[ 0, 1, 2, 2, 3, 1, //base 4, 5, 6, 6, 7, 5, ]; (vertex_data.to_vec(), index_data.to_vec()) } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Uniforms { view_inverse: Mat4, proj_inverse: Mat4, vertex: Mat4, } struct Example { uniforms: Uniforms, uniform_buf: wgpu::Buffer, vertex_buf: wgpu::Buffer, index_buf: wgpu::Buffer, pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, animation_timer: utils::AnimationTimer, } const CAM_LOOK_AT: Vec3 = Vec3::new(0.0, 1.0, -1.5); fn create_matrix(config: &wgpu::SurfaceConfiguration) -> Uniforms { let view = Mat4::look_at_rh(CAM_LOOK_AT, Vec3::ZERO, Vec3::Y); let proj = Mat4::perspective_rh( 59.0_f32.to_radians(), config.width as f32 / config.height as f32, 0.1, 1000.0, ); Uniforms { view_inverse: view.inverse(), proj_inverse: proj.inverse(), vertex: (proj * view), } } impl crate::framework::Example for Example { fn required_features() -> wgpu::Features { wgpu::Features::EXPERIMENTAL_RAY_QUERY | wgpu::Features::IMMEDIATES } fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { wgpu::DownlevelCapabilities { flags: wgpu::DownlevelFlags::COMPUTE_SHADERS, ..Default::default() } } fn required_limits() -> wgpu::Limits { wgpu::Limits { max_immediate_size: 16, ..wgpu::Limits::default() } .using_minimum_supported_acceleration_structure_values() } fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { let uniforms = create_matrix(config); let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Uniform Buffer"), contents: bytemuck::cast_slice(&[uniforms]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); let (vertex_data, index_data) = create_vertices(); let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::cast_slice(&vertex_data), usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::BLAS_INPUT, }); let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Index Buffer"), contents: bytemuck::cast_slice(&index_data), usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::BLAS_INPUT, }); let blas_geo_size_desc = wgpu::BlasTriangleGeometrySizeDescriptor { vertex_format: wgpu::VertexFormat::Float32x3, vertex_count: vertex_data.len() as u32, index_format: Some(wgpu::IndexFormat::Uint16), index_count: Some(index_data.len() as u32), flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE, }; let blas = device.create_blas( &wgpu::CreateBlasDescriptor { label: None, flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, update_mode: wgpu::AccelerationStructureUpdateMode::Build, }, wgpu::BlasGeometrySizeDescriptors::Triangles { descriptors: vec![blas_geo_size_desc.clone()], }, ); let mut tlas = device.create_tlas(&wgpu::CreateTlasDescriptor { label: None, flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE, update_mode: wgpu::AccelerationStructureUpdateMode::Build, max_instances: 1, }); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), }); let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::AccelerationStructure { vertex_return: false, }, count: None, }, ], }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 16, }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[VertexBufferLayout { array_stride: mem::size_of::() as wgpu::BufferAddress, step_mode: Default::default(), attributes: &vertex_attr_array![0 => Float32x3, 1 => Float32x3], }], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(config.format.into())], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); tlas[0] = Some(wgpu::TlasInstance::new( &blas, [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], 0, 0xFF, )); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); encoder.build_acceleration_structures( iter::once(&wgpu::BlasBuildEntry { blas: &blas, geometry: wgpu::BlasGeometries::TriangleGeometries(vec![ wgpu::BlasTriangleGeometry { size: &blas_geo_size_desc, vertex_buffer: &vertex_buf, first_vertex: 0, vertex_stride: mem::size_of::() as u64, index_buffer: Some(&index_buf), first_index: Some(0), transform_buffer: None, transform_buffer_offset: None, }, ]), }), iter::once(&tlas), ); queue.submit(Some(encoder.finish())); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: uniform_buf.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: tlas.as_binding(), }, ], }); let animation_timer = utils::AnimationTimer::default(); Example { uniforms, uniform_buf, vertex_buf, index_buf, pipeline, bind_group, animation_timer, } } fn update(&mut self, _event: winit::event::WindowEvent) {} fn resize( &mut self, config: &wgpu::SurfaceConfiguration, _device: &wgpu::Device, queue: &wgpu::Queue, ) { self.uniforms = create_matrix(config); queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(&[self.uniforms])); queue.submit(None); } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { //device.push_error_scope(wgpu::ErrorFilter::Validation); const LIGHT_DISTANCE: f32 = 5.0; const TIME_SCALE: f32 = -0.2; const INITIAL_TIME: f32 = 1.0; let time = self.animation_timer.time(); let cos = (time * TIME_SCALE + INITIAL_TIME).cos() * LIGHT_DISTANCE; let sin = (time * TIME_SCALE + INITIAL_TIME).sin() * LIGHT_DISTANCE; let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.1, g: 0.1, b: 0.1, a: 1.0, }), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, Some(&self.bind_group), &[]); rpass.set_immediates(0, &0.0_f32.to_ne_bytes()); rpass.set_immediates(4, &cos.to_ne_bytes()); rpass.set_immediates(8, &sin.to_ne_bytes()); rpass.set_vertex_buffer(0, self.vertex_buf.slice(..)); rpass.set_index_buffer(self.index_buf.slice(..), IndexFormat::Uint16); rpass.draw_indexed(0..12, 0, 0..1); } queue.submit(Some(encoder.finish())); device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); } } pub fn main() { crate::framework::run::("ray-shadows"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "ray_shadows", image_path: "/examples/features/src/ray_shadows/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default() // https://github.com/gfx-rs/wgpu/issues/9100 .expect_fail(wgpu_test::FailureCase::backend(wgpu::Backends::METAL)), comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/ray_shadows/shader.wgsl ================================================ enable wgpu_ray_query; struct VertexOutput { @builtin(position) position: vec4, @location(0) tex_coords: vec2, @location(1) normal: vec3, @location(2) world_position: vec3, }; @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32, @location(0) position: vec3, @location(1) normal: vec3,) -> VertexOutput { var result: VertexOutput; let x = i32(vertex_index) / 2; let y = i32(vertex_index) & 1; let tc = vec2( f32(x) * 2.0, f32(y) * 2.0 ); result.tex_coords = tc; result.position = uniforms.vertex * vec4(position, 1.0); result.normal = normal; result.world_position = position; return result; } struct Uniforms { view_inv: mat4x4, proj_inv: mat4x4, vertex: mat4x4, }; @group(0) @binding(0) var uniforms: Uniforms; @group(0) @binding(1) var acc_struct: acceleration_structure; struct ImmediateData { light: vec3, padding: f32, } var pc: ImmediateData; const SURFACE_BRIGHTNESS = 0.5; @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { let camera = (uniforms.view_inv * vec4(0.0,0.0,0.0,1.0)).xyz; var color = vec4(vertex.tex_coords, 0.0, 1.0); let d = vertex.tex_coords * 2.0 - 1.0; let origin = vertex.world_position; let direction = normalize(pc.light - vertex.world_position); var normal: vec3; let dir_cam = normalize(camera - vertex.world_position); if (dot(dir_cam, vertex.normal) < 0.0) { normal = -vertex.normal; } else { normal = vertex.normal; } var rq: ray_query; rayQueryInitialize(&rq, acc_struct, RayDesc(0u, 0xFFu, 0.0001, 200.0, origin, direction)); rayQueryProceed(&rq); let intersection = rayQueryGetCommittedIntersection(&rq); if (intersection.kind != RAY_QUERY_INTERSECTION_NONE) { color = vec4(vec3(0.1) * SURFACE_BRIGHTNESS, 1.0); } else { color = vec4(vec3(max(dot(direction, normal), 0.1)) * SURFACE_BRIGHTNESS, 1.0); } return color; } ================================================ FILE: examples/features/src/ray_traced_triangle/README.md ================================================ # ray-traced-triangle This example renders three triangles with hardware acceleration. This is the same scene set-up as hal ray-traced triangle ## To Run ``` cargo run --bin wgpu-examples ray_traced_triangle ``` ## Screenshots ![Triangle example](screenshot.png) ================================================ FILE: examples/features/src/ray_traced_triangle/blit.wgsl ================================================ // same as ray_cube_compute/blit.wgsl struct VertexOutput { @builtin(position) position: vec4, @location(0) tex_coords: vec2, }; // meant to be called with 3 vertex indices: 0, 1, 2 // draws one large triangle over the clip space like this: // (the asterisks represent the clip space bounds) //-1,1 1,1 // --------------------------------- // | * . // | * . // | * . // | * . // | * . // | * . // |*************** // | . 1,-1 // | . // | . // | . // | . // |. @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { var result: VertexOutput; let x = i32(vertex_index) / 2; let y = i32(vertex_index) & 1; let tc = vec2( f32(x) * 2.0, f32(y) * 2.0 ); result.position = vec4( tc.x * 2.0 - 1.0, 1.0 - tc.y * 2.0, 0.0, 1.0 ); result.tex_coords = tc; return result; } @group(0) @binding(0) var r_color: texture_2d; @group(0) @binding(1) var r_sampler: sampler; @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { return textureSample(r_color, r_sampler, vertex.tex_coords); } ================================================ FILE: examples/features/src/ray_traced_triangle/mod.rs ================================================ use glam::{Mat4, Vec3}; use std::mem; use wgpu::util::{BufferInitDescriptor, DeviceExt}; use wgpu::{include_wgsl, BufferUsages, IndexFormat, SamplerDescriptor}; use wgpu::{ AccelerationStructureFlags, AccelerationStructureUpdateMode, BlasBuildEntry, BlasGeometries, BlasGeometrySizeDescriptors, BlasTriangleGeometry, BlasTriangleGeometrySizeDescriptor, CreateBlasDescriptor, CreateTlasDescriptor, Tlas, TlasInstance, }; use crate::utils; struct Example { tlas: Tlas, compute_pipeline: wgpu::ComputePipeline, blit_pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, blit_bind_group: wgpu::BindGroup, storage_texture: wgpu::Texture, animation_timer: utils::AnimationTimer, } #[repr(C)] #[derive(bytemuck::Pod, bytemuck::Zeroable, Clone, Copy, Debug)] struct Uniforms { view_inverse: Mat4, proj_inverse: Mat4, } impl crate::framework::Example for Example { fn required_features() -> wgpu::Features { wgpu::Features::EXPERIMENTAL_RAY_QUERY } fn required_limits() -> wgpu::Limits { wgpu::Limits::default().using_minimum_supported_acceleration_structure_values() } fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { wgpu::DownlevelCapabilities { flags: wgpu::DownlevelFlags::COMPUTE_SHADERS, ..Default::default() } } fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { let shader = device.create_shader_module(include_wgsl!("shader.wgsl")); let blit_shader = device.create_shader_module(include_wgsl!("blit.wgsl")); let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("bgl for shader.wgsl"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::StorageTexture { access: wgpu::StorageTextureAccess::WriteOnly, format: wgpu::TextureFormat::Rgba8Unorm, view_dimension: wgpu::TextureViewDimension::D2, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::AccelerationStructure { vertex_return: false, }, count: None, }, ], }); let blit_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("bgl for blit.wgsl"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: false }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), count: None, }, ], }); let vertices: [f32; 9] = [1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 0.0, -1.0, 0.0]; let indices: [u32; 3] = [0, 1, 2]; let vertex_buffer = device.create_buffer_init(&BufferInitDescriptor { label: Some("vertex buffer"), contents: bytemuck::cast_slice(&vertices), usage: BufferUsages::BLAS_INPUT, }); let index_buffer = device.create_buffer_init(&BufferInitDescriptor { label: Some("index buffer"), contents: bytemuck::cast_slice(&indices), usage: BufferUsages::BLAS_INPUT, }); let blas_size_desc = BlasTriangleGeometrySizeDescriptor { vertex_format: wgpu::VertexFormat::Float32x3, // 3 coordinates per vertex vertex_count: (vertices.len() / 3) as u32, index_format: Some(IndexFormat::Uint32), index_count: Some(indices.len() as u32), flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE, }; let blas = device.create_blas( &CreateBlasDescriptor { label: None, flags: AccelerationStructureFlags::PREFER_FAST_TRACE, update_mode: AccelerationStructureUpdateMode::Build, }, BlasGeometrySizeDescriptors::Triangles { descriptors: vec![blas_size_desc.clone()], }, ); let mut tlas = device.create_tlas(&CreateTlasDescriptor { label: None, max_instances: 3, flags: AccelerationStructureFlags::PREFER_FAST_TRACE, update_mode: AccelerationStructureUpdateMode::Build, }); tlas[0] = Some(TlasInstance::new( &blas, Mat4::from_translation(Vec3 { x: 0.0, y: 0.0, z: 0.0, }) .transpose() .to_cols_array()[..12] .try_into() .unwrap(), 0, 0xff, )); tlas[1] = Some(TlasInstance::new( &blas, Mat4::from_translation(Vec3 { x: -1.0, y: -1.0, z: -2.0, }) .transpose() .to_cols_array()[..12] .try_into() .unwrap(), 0, 0xff, )); tlas[2] = Some(TlasInstance::new( &blas, Mat4::from_translation(Vec3 { x: 1.0, y: -1.0, z: -2.0, }) .transpose() .to_cols_array()[..12] .try_into() .unwrap(), 0, 0xff, )); let uniforms = { let view = Mat4::look_at_rh(Vec3::new(0.0, 0.0, 2.5), Vec3::ZERO, Vec3::Y); let proj = Mat4::perspective_rh(59.0_f32.to_radians(), 1.0, 0.001, 1000.0); Uniforms { view_inverse: view.inverse(), proj_inverse: proj.inverse(), } }; let uniform_buffer = device.create_buffer_init(&BufferInitDescriptor { label: None, contents: bytemuck::cast_slice(&[uniforms]), usage: BufferUsages::UNIFORM, }); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); encoder.build_acceleration_structures( Some(&BlasBuildEntry { blas: &blas, geometry: BlasGeometries::TriangleGeometries(vec![BlasTriangleGeometry { size: &blas_size_desc, vertex_buffer: &vertex_buffer, first_vertex: 0, vertex_stride: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, // in this case since one triangle gets no compression from an index buffer `index_buffer` and `first_index` could be `None`. index_buffer: Some(&index_buffer), first_index: Some(0), transform_buffer: None, transform_buffer_offset: None, }]), }), Some(&tlas), ); queue.submit(Some(encoder.finish())); let storage_tex = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: config.width, height: config.height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); let sampler = device.create_sampler(&SamplerDescriptor { label: None, address_mode_u: Default::default(), address_mode_v: Default::default(), address_mode_w: Default::default(), mag_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::MipmapFilterMode::Nearest, lod_min_clamp: 1.0, lod_max_clamp: 1.0, compare: None, anisotropy_clamp: 1, border_color: None, }); let compute_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("pipeline layout for shader.wgsl"), bind_group_layouts: &[Some(&bgl)], immediate_size: 0, }); let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some("pipeline for shader.wgsl"), layout: Some(&compute_pipeline_layout), module: &shader, entry_point: None, compilation_options: Default::default(), cache: None, }); let blit_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("pipeline layout for blit.wgsl"), bind_group_layouts: &[Some(&blit_bgl)], immediate_size: 0, }); let blit_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("pipeline for blit.wgsl"), layout: Some(&blit_pipeline_layout), vertex: wgpu::VertexState { module: &blit_shader, entry_point: None, compilation_options: Default::default(), buffers: &[], }, primitive: Default::default(), depth_stencil: None, multisample: Default::default(), fragment: Some(wgpu::FragmentState { module: &blit_shader, entry_point: None, compilation_options: Default::default(), targets: &[Some(wgpu::ColorTargetState { format: config.format, blend: None, write_mask: Default::default(), })], }), multiview_mask: None, cache: None, }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("bind group for shader.wgsl"), layout: &bgl, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: uniform_buffer.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView( &storage_tex.create_view(&wgpu::TextureViewDescriptor::default()), ), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::AccelerationStructure(&tlas), }, ], }); let blit_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("bind group for blit.wgsl"), layout: &blit_bgl, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView( &storage_tex.create_view(&wgpu::TextureViewDescriptor::default()), ), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler), }, ], }); Self { tlas, compute_pipeline, blit_pipeline, bind_group, blit_bind_group, storage_texture: storage_tex, animation_timer: utils::AnimationTimer::default(), } } fn resize( &mut self, _config: &wgpu::SurfaceConfiguration, _device: &wgpu::Device, _queue: &wgpu::Queue, ) { } fn update(&mut self, _event: winit::event::WindowEvent) {} fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { self.tlas[0].as_mut().unwrap().transform = Mat4::from_rotation_y(self.animation_timer.time()) .transpose() .to_cols_array()[..12] .try_into() .unwrap(); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); encoder.build_acceleration_structures(None, Some(&self.tlas)); { let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); cpass.set_pipeline(&self.compute_pipeline); cpass.set_bind_group(0, Some(&self.bind_group), &[]); cpass.dispatch_workgroups( self.storage_texture.width() / 8, self.storage_texture.height() / 8, 1, ); } { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.blit_pipeline); rpass.set_bind_group(0, Some(&self.blit_bind_group), &[]); rpass.draw(0..3, 0..1); } queue.submit(Some(encoder.finish())); } } pub fn main() { crate::framework::run::("ray-traced-triangle"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "ray_traced_triangle", image_path: "/examples/features/src/ray_traced_triangle/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default() // https://github.com/gfx-rs/wgpu/issues/9100 .expect_fail(wgpu_test::FailureCase::backend(wgpu::Backends::METAL)), comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/ray_traced_triangle/shader.wgsl ================================================ // duplicate of hal's ray-traced triangle shader enable wgpu_ray_query; struct Uniforms { view_inv: mat4x4, proj_inv: mat4x4, }; @group(0) @binding(0) var uniforms: Uniforms; @group(0) @binding(1) var output: texture_storage_2d; @group(0) @binding(2) var acc_struct: acceleration_structure; @compute @workgroup_size(8, 8) fn main(@builtin(global_invocation_id) global_id: vec3) { let target_size = textureDimensions(output); let pixel_center = vec2(global_id.xy) + vec2(0.5); let in_uv = pixel_center / vec2(target_size.xy); let d = in_uv * 2.0 - 1.0; let origin = (uniforms.view_inv * vec4(0.0, 0.0, 0.0, 1.0)).xyz; let temp = uniforms.proj_inv * vec4(d.x, d.y, 1.0, 1.0); let direction = (uniforms.view_inv * vec4(normalize(temp.xyz), 0.0)).xyz; var rq: ray_query; rayQueryInitialize(&rq, acc_struct, RayDesc(0u, 0xFFu, 0.1, 200.0, origin, direction)); rayQueryProceed(&rq); var color = vec4(0.0, 0.0, 0.0, 1.0); let intersection = rayQueryGetCommittedIntersection(&rq); if intersection.kind != RAY_QUERY_INTERSECTION_NONE { color = vec4(intersection.barycentrics, 1.0 - intersection.barycentrics.x - intersection.barycentrics.y, 1.0); } textureStore(output, global_id.xy, color); } ================================================ FILE: examples/features/src/render_to_texture/README.md ================================================ # render_to_texture Similar to hello-triangle but instead of rendering to a window or canvas, renders to a texture that is then output as an image like the storage-texture example. If all goes well, the end result should look familiarly like hello-triangle with its red triangle on a green background. ## To Run ``` cargo run --bin wgpu-examples render_to_texture ``` ================================================ FILE: examples/features/src/render_to_texture/mod.rs ================================================ #[cfg(not(target_arch = "wasm32"))] use crate::utils::output_image_native; #[cfg(target_arch = "wasm32")] use crate::utils::output_image_wasm; const TEXTURE_DIMS: (usize, usize) = (512, 512); async fn run(_path: Option) { // This will later store the raw pixel value data locally. We'll create it now as // a convenient size reference. let mut texture_data = Vec::::with_capacity(TEXTURE_DIMS.0 * TEXTURE_DIMS.1 * 4); let instance = wgpu::Instance::default(); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions::default()) .await .unwrap(); let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) .await .unwrap(); let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let render_target = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: TEXTURE_DIMS.0 as u32, height: TEXTURE_DIMS.1 as u32, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb], }); let output_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: texture_data.capacity() as u64, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false, }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: None, vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(wgpu::TextureFormat::Rgba8UnormSrgb.into())], }), primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); log::info!("Wgpu context set up."); //----------------------------------------------- let texture_view = render_target.create_view(&wgpu::TextureViewDescriptor::default()); let mut command_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); { let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &texture_view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, occlusion_query_set: None, timestamp_writes: None, multiview_mask: None, }); render_pass.set_pipeline(&pipeline); render_pass.draw(0..3, 0..1); } // The texture now contains our rendered image command_encoder.copy_texture_to_buffer( wgpu::TexelCopyTextureInfo { texture: &render_target, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All, }, wgpu::TexelCopyBufferInfo { buffer: &output_staging_buffer, layout: wgpu::TexelCopyBufferLayout { offset: 0, // This needs to be a multiple of 256. Normally we would need to pad // it but we here know it will work out anyways. bytes_per_row: Some((TEXTURE_DIMS.0 * 4) as u32), rows_per_image: Some(TEXTURE_DIMS.1 as u32), }, }, wgpu::Extent3d { width: TEXTURE_DIMS.0 as u32, height: TEXTURE_DIMS.1 as u32, depth_or_array_layers: 1, }, ); queue.submit(Some(command_encoder.finish())); log::info!("Commands submitted."); //----------------------------------------------- // Time to get our image. let buffer_slice = output_staging_buffer.slice(..); let (sender, receiver) = flume::bounded(1); buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap()); device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); receiver.recv_async().await.unwrap().unwrap(); log::info!("Output buffer mapped."); { let view = buffer_slice.get_mapped_range(); texture_data.extend_from_slice(&view[..]); } log::info!("Image data copied to local."); output_staging_buffer.unmap(); #[cfg(not(target_arch = "wasm32"))] output_image_native(texture_data.to_vec(), TEXTURE_DIMS, _path.unwrap()); #[cfg(target_arch = "wasm32")] output_image_wasm(texture_data.to_vec(), TEXTURE_DIMS); log::info!("Done."); } pub fn main() { #[cfg(not(target_arch = "wasm32"))] { env_logger::builder() .filter_level(log::LevelFilter::Info) .format_timestamp_nanos() .init(); let path = std::env::args() .nth(2) .unwrap_or_else(|| "please_don't_git_push_me.png".to_string()); pollster::block_on(run(Some(path))); } #[cfg(target_arch = "wasm32")] { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); console_log::init_with_level(log::Level::Info).expect("could not initialize logger"); wasm_bindgen_futures::spawn_local(run(None)); } } ================================================ FILE: examples/features/src/render_to_texture/shader.wgsl ================================================ @vertex fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { var vertices = array, 3>( vec4(0.0, 1.0, 0.0, 1.0), vec4(-1.0, -1.0, 0.0, 1.0), vec4(1.0, -1.0, 0.0, 1.0) ); return vertices[in_vertex_index]; } @fragment fn fs_main() -> @location(0) vec4 { return vec4(1.0, 0.0, 0.0, 1.0); } ================================================ FILE: examples/features/src/render_with_compute/mod.rs ================================================ //! This renders to the screen with compute shaders. Note that due to limitations in Firefox, //! the wait will cause FPS to be capped at 10 when running on webgpu on Firefox. It is //! therefore not recommended to use this code, at least until //! (and possibly further work) is resolved. use std::time::Instant; #[derive(bytemuck::Pod, bytemuck::Zeroable, Clone, Copy, Debug)] #[repr(C)] #[repr(align(16))] struct GlobalParams { time: f32, frame: f32, _padding: [u8; 8], } pub struct Example { pipeline: wgpu::ComputePipeline, texture_view: wgpu::TextureView, global_params: wgpu::Buffer, bg: wgpu::BindGroup, bgl: wgpu::BindGroupLayout, blitter: wgpu::util::TextureBlitter, frame_count: u32, start_time: Option, } impl crate::framework::Example for Example { fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { let sm = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::StorageTexture { access: wgpu::StorageTextureAccess::WriteOnly, format: wgpu::TextureFormat::Rgba8Unorm, view_dimension: wgpu::TextureViewDimension::D2, }, count: None, }, ], }); let ppl = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bgl)], immediate_size: 0, }); let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: Some(&ppl), module: &sm, entry_point: None, compilation_options: Default::default(), cache: None, }); let blitter = wgpu::util::TextureBlitter::new(device, config.format); let global_params = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: size_of::() as u64, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let (texture_view, bg) = create_tv_and_bg(device, &bgl, &global_params, config.width, config.height); Self { pipeline, texture_view, global_params, bg, bgl, blitter, frame_count: 0, start_time: None, } } fn required_limits() -> wgpu::Limits { wgpu::Limits { max_storage_textures_per_shader_stage: 1, ..Default::default() } } fn resize( &mut self, config: &wgpu::SurfaceConfiguration, device: &wgpu::Device, _queue: &wgpu::Queue, ) { let (texture_view, bg) = create_tv_and_bg( device, &self.bgl, &self.global_params, config.width, config.height, ); self.bg = bg; self.texture_view = texture_view; } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let now = Instant::now(); let start = *self.start_time.get_or_insert(now); let time_since_start = (now - start).as_secs_f32(); queue.write_buffer( &self.global_params, 0, bytemuck::bytes_of(&GlobalParams { time: time_since_start, frame: self.frame_count as f32, _padding: [0; 8], }), ); let mut encoder = device.create_command_encoder(&Default::default()); { let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, &self.bg, &[]); const SHADER_WORKGROUP_DIM: u32 = 16; pass.dispatch_workgroups( self.texture_view .texture() .width() .div_ceil(SHADER_WORKGROUP_DIM), self.texture_view .texture() .height() .div_ceil(SHADER_WORKGROUP_DIM), 1, ); } self.blitter .copy(device, &mut encoder, &self.texture_view, view); queue.submit([encoder.finish()]); device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); self.frame_count += 1; } fn update(&mut self, _event: winit::event::WindowEvent) {} } fn create_tv_and_bg( device: &wgpu::Device, bgl: &wgpu::BindGroupLayout, global_params: &wgpu::Buffer, width: u32, height: u32, ) -> (wgpu::TextureView, wgpu::BindGroup) { let texture = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width, height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); let view = texture.create_view(&Default::default()); let bg = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: bgl, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(global_params.as_entire_buffer_binding()), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&view), }, ], }); (view, bg) } pub fn main() { crate::framework::run::("render-with-compute"); } ================================================ FILE: examples/features/src/render_with_compute/shader.wgsl ================================================ // This shader has gone through several stages of derived work: // // 1. Originally authored by a user named `dynamite`, distributed as a [Shadertoy page] under the // [CC BY-NC-SA 3.0 license]. // // [Shadertoy page]: https://www.shadertoy.com/view/XdlSDs // [CC BY-NC-SA 3.0 license]: https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en // // 2. Ported to Slang for the Slang Playground project as [the `circle.slang` demo]. // // [the `circle.slang` demo]: https://github.com/shader-slang/slang-playground/blob/60f0ca29d9952d3cb598936511288f00451ced34/public/demos/circle.slang // // 3. Compiled to WGSL via Slang 2026.4 on the Slang playground. // // For convenience, both the ported shader and the WGSL output can be found at: // @binding(1) @group(0) var outputTexture_0 : texture_storage_2d; struct GlobalParams_std140_0 { @align(16) time_0 : f32, @align(4) frame_0 : f32, }; @binding(0) @group(0) var globalParams_0 : GlobalParams_std140_0; struct imageMain_slang_Lambda_imageMain_1_0 { dispatchThreadID_0 : vec2, }; fn imageMain_slang_Lambda_imageMain_1_x24init_0( dispatchThreadID_1 : vec2) -> imageMain_slang_Lambda_imageMain_1_0 { var _S1 : imageMain_slang_Lambda_imageMain_1_0; _S1.dispatchThreadID_0 = dispatchThreadID_1; return _S1; } fn float_getPi_0() -> f32 { return 3.14159274101257324f; } fn imageMain_slang_Lambda_imageMain_1_x28x29_0( this_0 : imageMain_slang_Lambda_imageMain_1_0, screenSize_0 : vec2) -> vec4 { var _S2 : vec2 = vec2(2.0f); var p_0 : vec2 = (vec2(this_0.dispatchThreadID_0.xy) * _S2 - vec2(screenSize_0.xy)) / vec2(f32(screenSize_0.y)); var tau_0 : f32 = float_getPi_0() * 2.0f; var _S3 : f32 = atan2(p_0.x, p_0.y) / tau_0; var uv_0 : vec2 = vec2(_S3, length(p_0) * 0.75f); var t_0 : f32 = globalParams_0.frame_0 / 60.0f; var xCol_0 : f32 = ((((abs((_S3 - t_0 / 3.0f) * 3.0f))) % ((3.0f)))); var horColour_0 : vec3 = vec3(0.25f, 0.25f, 0.25f); if(xCol_0 < 1.0f) { horColour_0[i32(0)] = horColour_0[i32(0)] + (1.0f - xCol_0); horColour_0[i32(1)] = horColour_0[i32(1)] + xCol_0; } else { if(xCol_0 < 2.0f) { var xCol_1 : f32 = xCol_0 - 1.0f; horColour_0[i32(1)] = horColour_0[i32(1)] + (1.0f - xCol_1); horColour_0[i32(2)] = horColour_0[i32(2)] + xCol_1; } else { var xCol_2 : f32 = xCol_0 - 2.0f; horColour_0[i32(2)] = horColour_0[i32(2)] + (1.0f - xCol_2); horColour_0[i32(0)] = horColour_0[i32(0)] + xCol_2; } } var uv_1 : vec2 = _S2 * uv_0 - vec2(1.0f); return vec4(vec3(((0.69999998807907104f + 0.5f * cos(uv_1.x * 10.0f * tau_0 * 0.15000000596046448f * clamp(floor(5.0f + 10.0f * cos(t_0)), 0.0f, 10.0f))) * abs(1.0f / (30.0f * uv_1.y)))) * horColour_0, 1.0f); } fn imageMain_slang_Lambda_imageMain_1_x24_syn_x28x29_0( this_1 : imageMain_slang_Lambda_imageMain_1_0, _S4 : vec2) -> vec4 { return imageMain_slang_Lambda_imageMain_1_x28x29_0(this_1, _S4); } fn drawPixel_0( location_0 : vec2, _S5 : imageMain_slang_Lambda_imageMain_1_0) { var width_0 : u32 = u32(0); var height_0 : u32 = u32(0); {var dim = textureDimensions((outputTexture_0));((width_0)) = dim.x;((height_0)) = dim.y;}; var color_0 : vec4 = imageMain_slang_Lambda_imageMain_1_x24_syn_x28x29_0(_S5, vec2(i32(width_0), i32(height_0))); var _S6 : bool; if((location_0.x) >= width_0) { _S6 = true; } else { _S6 = (location_0.y) >= height_0; } if(_S6) { return; } textureStore((outputTexture_0), (location_0), (color_0)); return; } @compute @workgroup_size(16, 16, 1) fn imageMain(@builtin(global_invocation_id) dispatchThreadID_2 : vec3) { var dispatchThreadID_3 : vec2 = dispatchThreadID_2.xy; drawPixel_0(dispatchThreadID_3, imageMain_slang_Lambda_imageMain_1_x24init_0(dispatchThreadID_3)); return; } ================================================ FILE: examples/features/src/repeated_compute/README.md ================================================ # repeated_compute Repeatedly performs the Collatz calculation used in `hello-compute` on sets of random numbers. ## To Run ``` cargo run --bin wgpu-examples repeated_compute ``` ## Sample Randomly generated input: ``` [61917, 53957, 5717, 40520, 41020, 5120, 44281, 19584, 2975, 5310, 4162, 38159, 25343, 16551, 40532, 31464, 64505, 55815, 34793, 24285, 62190, 10530, 49321, 57494, 18473, 18291, 9067, 2665, 53877, 6754, 37616, 51136, 54990, 31159, 38648, 24127, 49640, 12095, 4529, 56275, 18200, 24423, 14065, 17512, 31421, 19612, 63709, 47666, 21805, 13608, 63529, 17809, 6737, 55362, 24647, 30348, 44906, 46325, 503, 52776, 63112, 20785, 63338, 28904, 55772, 56851, 53870, 65503, 30290, 57374, 61244, 39866, 625, 2353, 54901, 25511, 64046, 47882, 22723, 54917, 19563, 24130, 54374, 41964, 3999, 2805, 918, 32932, 6717, 46311, 4818, 28843, 37972, 50981, 31555, 39064, 42814, 37957, 17963, 22678, 3048, 18823, 7293, 63312, 29086, 45580, 5347, 1761, 19090, 41520, 35919, 38705, 51378, 29090, 31100, 55324, 26807, 26017, 24295, 62389, 51934, 27026, 1795, 14965, 51274, 10875, 21396, 22828, 37077, 49922, 46486, 55817, 58928, 64455, 47269, 53484, 6602, 52270, 24417, 6525, 60485, 6389, 10336, 62651, 15721, 8793, 37174, 11962, 768, 21426, 9919, 14295, 55401, 33099, 2221, 9021, 793, 27731, 58923, 28847, 56634, 20447, 33108, 11355, 32437, 15594, 26951, 62607, 28151, 46173, 53140, 48397, 64164, 12279, 54591, 36440, 42712, 3495, 28316, 4674, 35028, 50809, 17289, 3355, 6840, 38134, 29806, 53215, 12076, 55685, 31314, 33548, 51846, 29484, 36845, 12242, 11836, 5449, 11549, 12626, 23699, 52777, 350, 19344, 6380, 63964, 49649, 42487, 26543, 60198, 43868, 38280, 12917, 33574, 44104, 24176, 1348, 47752, 34890, 1471, 34329, 59348, 25115, 148, 62147, 12340, 23654, 26821, 3695, 41075, 15125, 56593, 44273, 34180, 35209, 26294, 48642, 19007, 40617, 46831, 9988, 522, 36478, 64700, 31220, 41376, 43870, 6053, 56665, 56475, 475, 60238, 38170, 53613, 23654, 26273] ``` Resulting output: ``` ["148", "78", "36", "75", "150", "15", "163", "43", "48", "54", "64", "80", "201", "120", "36", "147", "192", "65", "129", "157", "60", "42", "189", "73", "92", "66", "47", "53", "91", "36", "62", "78", "215", "54", "124", "144", "158", "94", "64", "83", "22", "100", "58", "35", "85", "105", "254", "101", "56", "63", "78", "97", "181", "228", "219", "72", "132", "57", "66", "34", "104", "149", "148", "121", "60", "104", "91", "130", "165", "78", "86", "106", "25", "32", "122", "113", "47", "96", "82", "60", "79", "51", "184", "88", "188", "84", "129", "147", "88", "114", "121", "165", "80", "83", "103", "75", "194", "155", "48", "131", "110", "61", "163", "55", "165", "70", "116", "104", "79", "106", "93", "75", "52", "134", "54", "91", "108", "126", "188", "148", "109", "38", "68", "133", "127", "117", "48", "30", "36", "52", "114", "184", "135", "161", "83", "52", "137", "109", "69", "137", "86", "124", "104", "179", "84", "127", "62", "50", "15", "30", "148", "102", "78", "160", "32", "140", "77", "90", "135", "165", "104", "180", "129", "161", "160", "146", "183", "148", "108", "145", "109", "70", "104", "125", "78", "62", "49", "56", "103", "59", "36", "202", "110", "92", "57", "54", "165", "171", "68", "109", "85", "67", "171", "46", "124", "174", "99", "160", "130", "156", "100", "83", "81", "61", "75", "55", "158", "101", "77", "91", "119", "75", "76", "129", "101", "95", "114", "96", "142", "171", "111", "122", "64", "23", "179", "37", "82", "46", "206", "150", "40", "104", "101", "129", "155", "64", "65", "154", "212", "132", "91", "30", "67", "148", "178", "106", "163", "67", "60", "135", "27", "117", "106", "109", "82", "201"] ``` ================================================ FILE: examples/features/src/repeated_compute/mod.rs ================================================ //! See hello-compute example main.rs for more details //! as similar items here are not explained. //! //! This example does elaborate on some things though that the //! hello-compute example does not such as mapping buffers //! and why use the async channels. use nanorand::Rng; const OVERFLOW: u32 = 0xffffffff; async fn run() { let mut numbers = [0u32; 256]; let context = WgpuContext::new(size_of_val(&numbers)).await; let mut rand = nanorand::WyRand::new(); for _ in 0..10 { for p in numbers.iter_mut() { *p = rand.generate::() as u32; } compute(&mut numbers, &context).await; let printed_numbers = numbers .iter() .map(|n| match n { &OVERFLOW => "(overflow)".to_string(), n => n.to_string(), }) .collect::>(); log::info!("Results: {printed_numbers:?}"); } } async fn compute(local_buffer: &mut [u32], context: &WgpuContext) { log::info!("Beginning GPU compute on data {local_buffer:?}."); // Local buffer contents -> GPU storage buffer // Adds a write buffer command to the queue. This command is more complicated // than it appears. context.queue.write_buffer( &context.storage_buffer, 0, bytemuck::cast_slice(local_buffer), ); log::info!("Wrote to buffer."); let mut command_encoder = context .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut compute_pass = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); compute_pass.set_pipeline(&context.pipeline); compute_pass.set_bind_group(0, &context.bind_group, &[]); compute_pass.dispatch_workgroups(local_buffer.len() as u32, 1, 1); } // We finish the compute pass by dropping it. // Entire storage buffer -> staging buffer. command_encoder.copy_buffer_to_buffer( &context.storage_buffer, 0, &context.output_staging_buffer, 0, context.storage_buffer.size(), ); // Finalize the command encoder, add the contained commands to the queue and flush. context.queue.submit(Some(command_encoder.finish())); log::info!("Submitted commands."); // Finally time to get our results. // First we get a buffer slice which represents a chunk of the buffer (which we // can't access yet). // We want the whole thing so use unbounded range. let buffer_slice = context.output_staging_buffer.slice(..); // Now things get complicated. WebGPU, for safety reasons, only allows either the GPU // or CPU to access a buffer's contents at a time. We need to "map" the buffer which means // flipping ownership of the buffer over to the CPU and making access legal. We do this // with `BufferSlice::map_async`. // // The problem is that map_async is not an async function so we can't await it. What // we need to do instead is pass in a closure that will be executed when the slice is // either mapped or the mapping has failed. // // The problem with this is that we don't have a reliable way to wait in the main // code for the buffer to be mapped and even worse, calling get_mapped_range or // get_mapped_range_mut prematurely will cause a panic, not return an error. // // Using channels solves this as awaiting the receiving of a message from // the passed closure will force the outside code to wait. It also doesn't hurt // if the closure finishes before the outside code catches up as the message is // buffered and receiving will just pick that up. // // It may also be worth noting that although on native, the usage of asynchronous // channels is wholly unnecessary, for the sake of portability to WASM (std channels // don't work on WASM,) we'll use async channels that work on both native and WASM. let (sender, receiver) = flume::bounded(1); buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap()); // In order for the mapping to be completed, one of three things must happen. // One of those can be calling `Device::poll`. This isn't necessary on the web as devices // are polled automatically but natively, we need to make sure this happens manually. // `PollType::Wait` will cause the thread to wait on native but not on WebGpu. context .device .poll(wgpu::PollType::wait_indefinitely()) .unwrap(); log::info!("Device polled."); // Now we await the receiving and panic if anything went wrong because we're lazy. receiver.recv_async().await.unwrap().unwrap(); log::info!("Result received."); // NOW we can call get_mapped_range. { let view = buffer_slice.get_mapped_range(); local_buffer.copy_from_slice(bytemuck::cast_slice(&view)); } log::info!("Results written to local buffer."); // We need to make sure all `BufferView`'s are dropped before we do what we're about // to do. // Unmap so that we can copy to the staging buffer in the next iteration. context.output_staging_buffer.unmap(); } pub fn main() { #[cfg(not(target_arch = "wasm32"))] { env_logger::builder() .filter_level(log::LevelFilter::Info) .format_timestamp_nanos() .init(); pollster::block_on(run()); } #[cfg(target_arch = "wasm32")] { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); console_log::init_with_level(log::Level::Info).expect("could not initialize logger"); crate::utils::add_web_nothing_to_see_msg(); wasm_bindgen_futures::spawn_local(run()); } } /// A convenient way to hold together all the useful wgpu stuff together. struct WgpuContext { device: wgpu::Device, queue: wgpu::Queue, pipeline: wgpu::ComputePipeline, bind_group: wgpu::BindGroup, storage_buffer: wgpu::Buffer, output_staging_buffer: wgpu::Buffer, } impl WgpuContext { async fn new(buffer_size: usize) -> WgpuContext { let instance = wgpu::Instance::default(); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions::default()) .await .unwrap(); let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::Performance, trace: wgpu::Trace::Off, }) .await .unwrap(); // Our shader, kindly compiled with Naga. let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); // This is where the GPU will read from and write to. let storage_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: buffer_size as wgpu::BufferAddress, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: false, }); // For portability reasons, WebGPU draws a distinction between memory that is // accessible by the CPU and memory that is accessible by the GPU. Only // buffers accessible by the CPU can be mapped and accessed by the CPU and // only buffers visible to the GPU can be used in shaders. In order to get // data from the GPU, we need to use CommandEncoder::copy_buffer_to_buffer // (which we will later) to copy the buffer modified by the GPU into a // mappable, CPU-accessible buffer which we'll create here. let output_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: buffer_size as wgpu::BufferAddress, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false, }); // This can be though of as the function signature for our CPU-GPU function. let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, // Going to have this be None just to be safe. min_binding_size: None, }, count: None, }], }); // This ties actual resources stored in the GPU to our metaphorical function // through the binding slots we defined above. let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: storage_buffer.as_entire_binding(), }], }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: Some(&pipeline_layout), module: &shader, entry_point: Some("main"), compilation_options: Default::default(), cache: None, }); WgpuContext { device, queue, pipeline, bind_group, storage_buffer, output_staging_buffer, } } } ================================================ FILE: examples/features/src/repeated_compute/shader.wgsl ================================================ @group(0) @binding(0) var v_indices: array; // this is used as both input and output for convenience // The Collatz Conjecture states that for any integer n: // If n is even, n = n/2 // If n is odd, n = 3n+1 // And repeat this process for each new n, you will always eventually reach 1. // Though the conjecture has not been proven, no counterexample has ever been found. // This function returns how many times this recurrence needs to be applied to reach 1. fn collatz_iterations(n_base: u32) -> u32{ var n: u32 = n_base; var i: u32 = 0u; loop { if (n <= 1u) { break; } if (n % 2u == 0u) { n = n / 2u; } else { // Overflow? (i.e. 3*n + 1 > 0xffffffffu?) if (n >= 1431655765u) { // 0x55555555u return 4294967295u; // 0xffffffffu } n = 3u * n + 1u; } i = i + 1u; } return i; } @compute @workgroup_size(1) fn main(@builtin(global_invocation_id) global_id: vec3) { v_indices[global_id.x] = collatz_iterations(v_indices[global_id.x]); } ================================================ FILE: examples/features/src/shadow/README.md ================================================ # shadow This animated example demonstrates shadow mapping. ## To Run ``` cargo run --bin wgpu-examples shadow ``` ## Screenshots ![Shadow mapping](./screenshot.png) ================================================ FILE: examples/features/src/shadow/mod.rs ================================================ use std::{f32::consts, iter, ops::Range}; use bytemuck::{Pod, Zeroable}; use wgpu::util::{align_to, DeviceExt}; #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Vertex { _pos: [i8; 4], _normal: [i8; 4], } fn vertex(pos: [i8; 3], nor: [i8; 3]) -> Vertex { Vertex { _pos: [pos[0], pos[1], pos[2], 1], _normal: [nor[0], nor[1], nor[2], 0], } } fn create_cube() -> (Vec, Vec) { let vertex_data = [ // top (0, 0, 1) vertex([-1, -1, 1], [0, 0, 1]), vertex([1, -1, 1], [0, 0, 1]), vertex([1, 1, 1], [0, 0, 1]), vertex([-1, 1, 1], [0, 0, 1]), // bottom (0, 0, -1) vertex([-1, 1, -1], [0, 0, -1]), vertex([1, 1, -1], [0, 0, -1]), vertex([1, -1, -1], [0, 0, -1]), vertex([-1, -1, -1], [0, 0, -1]), // right (1, 0, 0) vertex([1, -1, -1], [1, 0, 0]), vertex([1, 1, -1], [1, 0, 0]), vertex([1, 1, 1], [1, 0, 0]), vertex([1, -1, 1], [1, 0, 0]), // left (-1, 0, 0) vertex([-1, -1, 1], [-1, 0, 0]), vertex([-1, 1, 1], [-1, 0, 0]), vertex([-1, 1, -1], [-1, 0, 0]), vertex([-1, -1, -1], [-1, 0, 0]), // front (0, 1, 0) vertex([1, 1, -1], [0, 1, 0]), vertex([-1, 1, -1], [0, 1, 0]), vertex([-1, 1, 1], [0, 1, 0]), vertex([1, 1, 1], [0, 1, 0]), // back (0, -1, 0) vertex([1, -1, 1], [0, -1, 0]), vertex([-1, -1, 1], [0, -1, 0]), vertex([-1, -1, -1], [0, -1, 0]), vertex([1, -1, -1], [0, -1, 0]), ]; let index_data: &[u16] = &[ 0, 1, 2, 2, 3, 0, // top 4, 5, 6, 6, 7, 4, // bottom 8, 9, 10, 10, 11, 8, // right 12, 13, 14, 14, 15, 12, // left 16, 17, 18, 18, 19, 16, // front 20, 21, 22, 22, 23, 20, // back ]; (vertex_data.to_vec(), index_data.to_vec()) } fn create_plane(size: i8) -> (Vec, Vec) { let vertex_data = [ vertex([size, -size, 0], [0, 0, 1]), vertex([size, size, 0], [0, 0, 1]), vertex([-size, -size, 0], [0, 0, 1]), vertex([-size, size, 0], [0, 0, 1]), ]; let index_data: &[u16] = &[0, 1, 2, 2, 1, 3]; (vertex_data.to_vec(), index_data.to_vec()) } struct Entity { mx_world: glam::Mat4, rotation_speed: f32, color: wgpu::Color, vertex_buf: wgpu::Buffer, index_buf: wgpu::Buffer, index_format: wgpu::IndexFormat, index_count: usize, uniform_offset: wgpu::DynamicOffset, } struct Light { pos: glam::Vec3, color: wgpu::Color, fov: f32, depth: Range, target_view: wgpu::TextureView, } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct LightRaw { proj: [[f32; 4]; 4], pos: [f32; 4], color: [f32; 4], } impl Light { fn to_raw(&self) -> LightRaw { let view = glam::Mat4::look_at_rh(self.pos, glam::Vec3::ZERO, glam::Vec3::Z); let projection = glam::Mat4::perspective_rh( self.fov * consts::PI / 180., 1.0, self.depth.start, self.depth.end, ); let view_proj = projection * view; LightRaw { proj: view_proj.to_cols_array_2d(), pos: [self.pos.x, self.pos.y, self.pos.z, 1.0], color: [ self.color.r as f32, self.color.g as f32, self.color.b as f32, 1.0, ], } } } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct GlobalUniforms { proj: [[f32; 4]; 4], num_lights: [u32; 4], } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct EntityUniforms { model: [[f32; 4]; 4], color: [f32; 4], } struct Pass { pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, uniform_buf: wgpu::Buffer, } struct Example { entities: Vec, lights: Vec, lights_are_dirty: bool, shadow_pass: Pass, forward_pass: Pass, forward_depth: wgpu::TextureView, entity_bind_group: wgpu::BindGroup, light_storage_buf: wgpu::Buffer, entity_uniform_buf: wgpu::Buffer, } impl Example { const MAX_LIGHTS: usize = 10; const SHADOW_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; const SHADOW_SIZE: wgpu::Extent3d = wgpu::Extent3d { width: 512, height: 512, depth_or_array_layers: Self::MAX_LIGHTS as u32, }; const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; fn generate_matrix(aspect_ratio: f32) -> glam::Mat4 { let projection = glam::Mat4::perspective_rh(consts::FRAC_PI_4, aspect_ratio, 1.0, 20.0); let view = glam::Mat4::look_at_rh( glam::Vec3::new(3.0f32, -10.0, 6.0), glam::Vec3::new(0f32, 0.0, 0.0), glam::Vec3::Z, ); projection * view } fn create_depth_texture( config: &wgpu::SurfaceConfiguration, device: &wgpu::Device, ) -> wgpu::TextureView { let depth_texture = device.create_texture(&wgpu::TextureDescriptor { size: wgpu::Extent3d { width: config.width, height: config.height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: Self::DEPTH_FORMAT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TRANSIENT, label: None, view_formats: &[], }); depth_texture.create_view(&wgpu::TextureViewDescriptor::default()) } } impl crate::framework::Example for Example { fn optional_features() -> wgpu::Features { wgpu::Features::DEPTH_CLIP_CONTROL } fn init( config: &wgpu::SurfaceConfiguration, adapter: &wgpu::Adapter, device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { let supports_storage_resources = adapter .get_downlevel_capabilities() .flags .contains(wgpu::DownlevelFlags::VERTEX_STORAGE) && device.limits().max_storage_buffers_per_shader_stage > 0; // Create the vertex and index buffers let vertex_size = size_of::(); let (cube_vertex_data, cube_index_data) = create_cube(); let cube_vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Cubes Vertex Buffer"), contents: bytemuck::cast_slice(&cube_vertex_data), usage: wgpu::BufferUsages::VERTEX, }); let cube_index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Cubes Index Buffer"), contents: bytemuck::cast_slice(&cube_index_data), usage: wgpu::BufferUsages::INDEX, }); let (plane_vertex_data, plane_index_data) = create_plane(7); let plane_vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Plane Vertex Buffer"), contents: bytemuck::cast_slice(&plane_vertex_data), usage: wgpu::BufferUsages::VERTEX, }); let plane_index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Plane Index Buffer"), contents: bytemuck::cast_slice(&plane_index_data), usage: wgpu::BufferUsages::INDEX, }); struct CubeDesc { offset: glam::Vec3, angle: f32, scale: f32, rotation: f32, } let cube_descs = [ CubeDesc { offset: glam::Vec3::new(-2.0, -2.0, 2.0), angle: 10.0, scale: 0.7, rotation: 0.1, }, CubeDesc { offset: glam::Vec3::new(2.0, -2.0, 2.0), angle: 50.0, scale: 1.3, rotation: 0.2, }, CubeDesc { offset: glam::Vec3::new(-2.0, 2.0, 2.0), angle: 140.0, scale: 1.1, rotation: 0.3, }, CubeDesc { offset: glam::Vec3::new(2.0, 2.0, 2.0), angle: 210.0, scale: 0.9, rotation: 0.4, }, ]; let entity_uniform_size = size_of::() as wgpu::BufferAddress; let num_entities = 1 + cube_descs.len() as wgpu::BufferAddress; // Make the `uniform_alignment` >= `entity_uniform_size` and aligned to `min_uniform_buffer_offset_alignment`. let uniform_alignment = { let alignment = device.limits().min_uniform_buffer_offset_alignment as wgpu::BufferAddress; align_to(entity_uniform_size, alignment) }; // Note: dynamic uniform offsets also have to be aligned to `Limits::min_uniform_buffer_offset_alignment`. let entity_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: num_entities * uniform_alignment, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let index_format = wgpu::IndexFormat::Uint16; let mut entities = vec![{ Entity { mx_world: glam::Mat4::IDENTITY, rotation_speed: 0.0, color: wgpu::Color::WHITE, vertex_buf: plane_vertex_buf, index_buf: plane_index_buf, index_format, index_count: plane_index_data.len(), uniform_offset: 0, } }]; for (i, cube) in cube_descs.iter().enumerate() { let mx_world = glam::Mat4::from_scale_rotation_translation( glam::Vec3::splat(cube.scale), glam::Quat::from_axis_angle( cube.offset.normalize(), cube.angle * consts::PI / 180., ), cube.offset, ); entities.push(Entity { mx_world, rotation_speed: cube.rotation, color: wgpu::Color::GREEN, vertex_buf: cube_vertex_buf.clone(), index_buf: cube_index_buf.clone(), index_format, index_count: cube_index_data.len(), uniform_offset: ((i + 1) * uniform_alignment as usize) as _, }); } let local_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: wgpu::BufferSize::new(entity_uniform_size), }, count: None, }], label: None, }); let entity_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &local_bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &entity_uniform_buf, offset: 0, size: wgpu::BufferSize::new(entity_uniform_size), }), }], label: None, }); // Create other resources let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("shadow"), address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, mipmap_filter: wgpu::MipmapFilterMode::Nearest, compare: Some(wgpu::CompareFunction::LessEqual), ..Default::default() }); let shadow_texture = device.create_texture(&wgpu::TextureDescriptor { size: Self::SHADOW_SIZE, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: Self::SHADOW_FORMAT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, label: None, view_formats: &[], }); let shadow_view = shadow_texture.create_view(&wgpu::TextureViewDescriptor::default()); let mut shadow_target_views = (0..2) .map(|i| { Some(shadow_texture.create_view(&wgpu::TextureViewDescriptor { label: Some("shadow"), format: None, dimension: Some(wgpu::TextureViewDimension::D2), usage: None, aspect: wgpu::TextureAspect::All, base_mip_level: 0, mip_level_count: None, base_array_layer: i as u32, array_layer_count: Some(1), })) }) .collect::>(); let lights = vec![ Light { pos: glam::Vec3::new(7.0, -5.0, 10.0), color: wgpu::Color { r: 0.5, g: 1.0, b: 0.5, a: 1.0, }, fov: 60.0, depth: 1.0..20.0, target_view: shadow_target_views[0].take().unwrap(), }, Light { pos: glam::Vec3::new(-5.0, 7.0, 10.0), color: wgpu::Color { r: 1.0, g: 0.5, b: 0.5, a: 1.0, }, fov: 45.0, depth: 1.0..20.0, target_view: shadow_target_views[1].take().unwrap(), }, ]; let light_uniform_size = (Self::MAX_LIGHTS * size_of::()) as wgpu::BufferAddress; let light_storage_buf = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: light_uniform_size, usage: if supports_storage_resources { wgpu::BufferUsages::STORAGE } else { wgpu::BufferUsages::UNIFORM } | wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let vertex_attr = wgpu::vertex_attr_array![0 => Sint8x4, 1 => Sint8x4]; let vb_desc = wgpu::VertexBufferLayout { array_stride: vertex_size as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &vertex_attr, }; let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let shadow_pass = { let uniform_size = size_of::() as wgpu::BufferAddress; // Create pipeline layout let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[wgpu::BindGroupLayoutEntry { binding: 0, // global visibility: wgpu::ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new(uniform_size), }, count: None, }], }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("shadow"), bind_group_layouts: &[Some(&bind_group_layout), Some(&local_bind_group_layout)], immediate_size: 0, }); let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: uniform_size, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); // Create bind group let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: uniform_buf.as_entire_binding(), }], label: None, }); // Create the render pipeline let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("shadow"), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_bake"), compilation_options: Default::default(), buffers: std::slice::from_ref(&vb_desc), }, fragment: None, primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, front_face: wgpu::FrontFace::Ccw, cull_mode: Some(wgpu::Face::Back), unclipped_depth: device .features() .contains(wgpu::Features::DEPTH_CLIP_CONTROL), ..Default::default() }, depth_stencil: Some(wgpu::DepthStencilState { format: Self::SHADOW_FORMAT, depth_write_enabled: Some(true), depth_compare: Some(wgpu::CompareFunction::LessEqual), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState { constant: 2, // corresponds to bilinear filtering slope_scale: 2.0, clamp: 0.0, }, }), multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); Pass { pipeline, bind_group, uniform_buf, } }; let forward_pass = { // Create pipeline layout let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, // global visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new( size_of::() as _, ), }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, // lights visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: if supports_storage_resources { wgpu::BufferBindingType::Storage { read_only: true } } else { wgpu::BufferBindingType::Uniform }, has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new(light_uniform_size), }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { multisampled: false, sample_type: wgpu::TextureSampleType::Depth, view_dimension: wgpu::TextureViewDimension::D2Array, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 3, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison), count: None, }, ], label: None, }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("main"), bind_group_layouts: &[Some(&bind_group_layout), Some(&local_bind_group_layout)], immediate_size: 0, }); let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32); let forward_uniforms = GlobalUniforms { proj: mx_total.to_cols_array_2d(), num_lights: [lights.len() as u32, 0, 0, 0], }; let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Uniform Buffer"), contents: bytemuck::bytes_of(&forward_uniforms), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); // Create bind group let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: uniform_buf.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: light_storage_buf.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&shadow_view), }, wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&shadow_sampler), }, ], label: None, }); // Create the render pipeline let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("main"), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[vb_desc], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some(if supports_storage_resources { "fs_main" } else { "fs_main_without_storage" }), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), primitive: wgpu::PrimitiveState { front_face: wgpu::FrontFace::Ccw, cull_mode: Some(wgpu::Face::Back), ..Default::default() }, depth_stencil: Some(wgpu::DepthStencilState { format: Self::DEPTH_FORMAT, depth_write_enabled: Some(true), depth_compare: Some(wgpu::CompareFunction::Less), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }), multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); Pass { pipeline, bind_group, uniform_buf, } }; let forward_depth = Self::create_depth_texture(config, device); Example { entities, lights, lights_are_dirty: true, shadow_pass, forward_pass, forward_depth, light_storage_buf, entity_uniform_buf, entity_bind_group, } } fn update(&mut self, _event: winit::event::WindowEvent) { //empty } fn resize( &mut self, config: &wgpu::SurfaceConfiguration, device: &wgpu::Device, queue: &wgpu::Queue, ) { // update view-projection matrix let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32); let mx_ref: &[f32; 16] = mx_total.as_ref(); queue.write_buffer( &self.forward_pass.uniform_buf, 0, bytemuck::cast_slice(mx_ref), ); self.forward_depth = Self::create_depth_texture(config, device); } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { // update uniforms for entity in self.entities.iter_mut() { if entity.rotation_speed != 0.0 { let rotation = glam::Mat4::from_rotation_x(entity.rotation_speed * consts::PI / 180.); entity.mx_world *= rotation; } let data = EntityUniforms { model: entity.mx_world.to_cols_array_2d(), color: [ entity.color.r as f32, entity.color.g as f32, entity.color.b as f32, entity.color.a as f32, ], }; queue.write_buffer( &self.entity_uniform_buf, entity.uniform_offset as wgpu::BufferAddress, bytemuck::bytes_of(&data), ); } if self.lights_are_dirty { self.lights_are_dirty = false; for (i, light) in self.lights.iter().enumerate() { queue.write_buffer( &self.light_storage_buf, (i * size_of::()) as wgpu::BufferAddress, bytemuck::bytes_of(&light.to_raw()), ); } } let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); encoder.push_debug_group("shadow passes"); for (i, light) in self.lights.iter().enumerate() { encoder.push_debug_group(&format!( "shadow pass {} (light at position {:?})", i, light.pos )); // The light uniform buffer already has the projection, // let's just copy it over to the shadow uniform buffer. encoder.copy_buffer_to_buffer( &self.light_storage_buf, (i * size_of::()) as wgpu::BufferAddress, &self.shadow_pass.uniform_buf, 0, 64, ); encoder.insert_debug_marker("render entities"); { let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &light.target_view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), store: wgpu::StoreOp::Store, }), stencil_ops: None, }), timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); pass.set_pipeline(&self.shadow_pass.pipeline); pass.set_bind_group(0, &self.shadow_pass.bind_group, &[]); for entity in &self.entities { pass.set_bind_group(1, &self.entity_bind_group, &[entity.uniform_offset]); pass.set_index_buffer(entity.index_buf.slice(..), entity.index_format); pass.set_vertex_buffer(0, entity.vertex_buf.slice(..)); pass.draw_indexed(0..entity.index_count as u32, 0, 0..1); } } encoder.pop_debug_group(); } encoder.pop_debug_group(); // forward pass encoder.push_debug_group("forward rendering pass"); { let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.1, g: 0.2, b: 0.3, a: 1.0, }), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &self.forward_depth, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), store: wgpu::StoreOp::Discard, }), stencil_ops: None, }), timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); pass.set_pipeline(&self.forward_pass.pipeline); pass.set_bind_group(0, &self.forward_pass.bind_group, &[]); for entity in &self.entities { pass.set_bind_group(1, &self.entity_bind_group, &[entity.uniform_offset]); pass.set_index_buffer(entity.index_buf.slice(..), entity.index_format); pass.set_vertex_buffer(0, entity.vertex_buf.slice(..)); pass.draw_indexed(0..entity.index_count as u32, 0, 0..1); } } encoder.pop_debug_group(); queue.submit(iter::once(encoder.finish())); } } pub fn main() { crate::framework::run::("shadow"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "shadow", image_path: "/examples/features/src/shadow/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default() .downlevel_flags(wgpu::DownlevelFlags::COMPARISON_SAMPLERS) // rpi4 on VK doesn't work: https://gitlab.freedesktop.org/mesa/mesa/-/issues/3916 .expect_fail(wgpu_test::FailureCase::backend_adapter( wgpu::Backends::VULKAN, "V3D", )), comparisons: &[wgpu_test::ComparisonType::Mean(0.026)], // Bounded by Apple A9 _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/shadow/shader.wgsl ================================================ struct Globals { view_proj: mat4x4, num_lights: vec4, }; @group(0) @binding(0) var u_globals: Globals; struct Entity { world: mat4x4, color: vec4, }; @group(1) @binding(0) var u_entity: Entity; @vertex fn vs_bake(@location(0) position: vec4) -> @builtin(position) vec4 { return u_globals.view_proj * u_entity.world * vec4(position); } struct VertexOutput { @builtin(position) proj_position: vec4, @location(0) world_normal: vec3, @location(1) world_position: vec4 }; @vertex fn vs_main( @location(0) position: vec4, @location(1) normal: vec4, ) -> VertexOutput { let w = u_entity.world; let world_pos = u_entity.world * vec4(position); var result: VertexOutput; result.world_normal = mat3x3(w[0].xyz, w[1].xyz, w[2].xyz) * vec3(normal.xyz); result.world_position = world_pos; result.proj_position = u_globals.view_proj * world_pos; return result; } // fragment shader struct Light { proj: mat4x4, pos: vec4, color: vec4, }; @group(0) @binding(1) var s_lights: array; @group(0) @binding(1) var u_lights: array; // Used when storage types are not supported @group(0) @binding(2) var t_shadow: texture_depth_2d_array; @group(0) @binding(3) var sampler_shadow: sampler_comparison; fn fetch_shadow(light_id: u32, homogeneous_coords: vec4) -> f32 { if (homogeneous_coords.w <= 0.0) { return 1.0; } // compensate for the Y-flip difference between the NDC and texture coordinates let flip_correction = vec2(0.5, -0.5); // compute texture coordinates for shadow lookup let proj_correction = 1.0 / homogeneous_coords.w; let light_local = homogeneous_coords.xy * flip_correction * proj_correction + vec2(0.5, 0.5); // do the lookup, using HW PCF and comparison return textureSampleCompareLevel(t_shadow, sampler_shadow, light_local, i32(light_id), homogeneous_coords.z * proj_correction); } const c_ambient: vec3 = vec3(0.05, 0.05, 0.05); const c_max_lights: u32 = 10u; @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { let normal = normalize(vertex.world_normal); // accumulate color var color: vec3 = c_ambient; for(var i = 0u; i < min(u_globals.num_lights.x, c_max_lights); i += 1u) { let light = s_lights[i]; // project into the light space let shadow = fetch_shadow(i, light.proj * vertex.world_position); // compute Lambertian diffuse term let light_dir = normalize(light.pos.xyz - vertex.world_position.xyz); let diffuse = max(0.0, dot(normal, light_dir)); // add light contribution color += shadow * diffuse * light.color.xyz; } // multiply the light by material color return vec4(color, 1.0) * u_entity.color; } // The fragment entrypoint used when storage buffers are not available for the lights @fragment fn fs_main_without_storage(vertex: VertexOutput) -> @location(0) vec4 { let normal = normalize(vertex.world_normal); var color: vec3 = c_ambient; for(var i = 0u; i < min(u_globals.num_lights.x, c_max_lights); i += 1u) { // This line is the only difference from the entrypoint above. It uses the lights // uniform instead of the lights storage buffer let light = u_lights[i]; let shadow = fetch_shadow(i, light.proj * vertex.world_position); let light_dir = normalize(light.pos.xyz - vertex.world_position.xyz); let diffuse = max(0.0, dot(normal, light_dir)); color += shadow * diffuse * light.color.xyz; } return vec4(color, 1.0) * u_entity.color; } ================================================ FILE: examples/features/src/skybox/README.md ================================================ # skybox This animated example demonstrates loading a Wavefront OBJ model, and rendering it with skybox and simple reflections. It hooks up `winit` mouse controls for camera rotation around the model at the center. ## To Run ``` cargo run --bin wgpu-examples skybox ``` ## Screenshots ![Skybox](./screenshot.png) ================================================ FILE: examples/features/src/skybox/images/generation.bash ================================================ # Needs montage from ImageMagick in PATH # Needs compressonatorcli.exe from https://github.com/GPUOpen-Tools/compressonator in PATH # Needs PVRTexToolCLI.exe from https://developer.imaginationtech.com/pvrtextool/ in PATH # Generate a skybox image from 6 jpeg in the folder in first argument. # The images must be named right.jpg, left.jpg, top.jpg, bottom.jpg, back.jpg, front.jpg # # Must be called from the root of the project. # # bash examples/src/skybox/images/generation.bash ./path/to/images/folder SCRIPT_DIRECTORY=examples/src/skybox/images CHUNK_SIZE="256x256" set -e # ensure the script is called from the root of the project if [ ! -f "$SCRIPT_DIRECTORY/generation.bash" ]; then echo "The script must be called from the root of the project!" exit 1 fi # ensure an argument is passed if [ $# -eq 0 ]; then echo "No arguments supplied!" echo echo "Usage: bash examples/src/skybox/images/generation.bash ./path/to/images/folder" exit 1 fi TEMP=examples/src/skybox/images/tmp mkdir -p $TEMP # resize images to 256x256 magick mogrify -path $TEMP -resize 256x256 -format png $1/*.jpg # create an uncompressed ktx2 cubemap file PVRTexToolCLI.exe -i $TEMP/right.png,$TEMP/left.png,$TEMP/top.png,$TEMP/bottom.png,$TEMP/front.png,$TEMP/back.png -ics SRGB -cube -m -f r8g8b8a8,UBN,SRGB -o $SCRIPT_DIRECTORY/rgba8.ktx2 # create the bc7 compressed ktx2 cubemap files using compressonator compressonatorcli.exe -fd BC7 $SCRIPT_DIRECTORY/rgba8.ktx2 $SCRIPT_DIRECTORY/bc7.ktx2 # create the etc2 and astc compressed ktx2 cubemap file using PVRTexTool # # compressonator has support for etc2, but the result looks terrible. PVRTexToolCLI.exe -i $SCRIPT_DIRECTORY/rgba8.ktx2 -ics srgb -m -f ETC2_RGB_A1,UBN,SRGB -q etcslow -o $SCRIPT_DIRECTORY/etc2.ktx2 PVRTexToolCLI.exe -i $SCRIPT_DIRECTORY/rgba8.ktx2 -ics srgb -m -f ASTC_4X4,UBN,SRGB -q astcexhaustive -o $SCRIPT_DIRECTORY/astc.ktx2 rm -r $TEMP ================================================ FILE: examples/features/src/skybox/mod.rs ================================================ use bytemuck::{Pod, Zeroable}; use std::f32::consts; use wgpu::{util::DeviceExt, AstcBlock, AstcChannel}; const IMAGE_SIZE: u32 = 256; #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] struct Vertex { pos: [f32; 3], normal: [f32; 3], } struct Entity { vertex_count: u32, vertex_buf: wgpu::Buffer, } // Note: we use the Y=up coordinate space in this example. struct Camera { screen_size: (u32, u32), angle_y: f32, angle_xz: f32, dist: f32, } const MODEL_CENTER_Y: f32 = 2.0; impl Camera { fn to_uniform_data(&self) -> [f32; 16 * 3 + 4] { let aspect = self.screen_size.0 as f32 / self.screen_size.1 as f32; let proj = glam::Mat4::perspective_rh(consts::FRAC_PI_4, aspect, 1.0, 50.0); let cam_pos = glam::Vec3::new( self.angle_xz.cos() * self.angle_y.sin() * self.dist, self.angle_xz.sin() * self.dist + MODEL_CENTER_Y, self.angle_xz.cos() * self.angle_y.cos() * self.dist, ); let view = glam::Mat4::look_at_rh( cam_pos, glam::Vec3::new(0f32, MODEL_CENTER_Y, 0.0), glam::Vec3::Y, ); let proj_inv = proj.inverse(); let mut raw = [0f32; 16 * 3 + 4]; raw[..16].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj)[..]); raw[16..32].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj_inv)[..]); raw[32..48].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view)[..]); raw[48..51].copy_from_slice(AsRef::<[f32; 3]>::as_ref(&cam_pos)); raw[51] = 1.0; raw } } pub struct Example { camera: Camera, sky_pipeline: wgpu::RenderPipeline, entity_pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, uniform_buf: wgpu::Buffer, entities: Vec, depth_view: wgpu::TextureView, staging_belt: wgpu::util::StagingBelt, } impl Example { const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth24Plus; fn create_depth_texture( config: &wgpu::SurfaceConfiguration, device: &wgpu::Device, ) -> wgpu::TextureView { let depth_texture = device.create_texture(&wgpu::TextureDescriptor { size: wgpu::Extent3d { width: config.width, height: config.height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: Self::DEPTH_FORMAT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TRANSIENT, label: None, view_formats: &[], }); depth_texture.create_view(&wgpu::TextureViewDescriptor::default()) } } impl crate::framework::Example for Example { fn optional_features() -> wgpu::Features { wgpu::Features::TEXTURE_COMPRESSION_ASTC | wgpu::Features::TEXTURE_COMPRESSION_ETC2 | wgpu::Features::TEXTURE_COMPRESSION_BC } fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { let mut entities = Vec::new(); { let source = include_bytes!("models/rustacean-3d.obj"); let data = obj::ObjData::load_buf(&source[..]).unwrap(); let mut vertices = Vec::new(); for object in data.objects { for group in object.groups { vertices.clear(); for poly in group.polys { for end_index in 2..poly.0.len() { for &index in &[0, end_index - 1, end_index] { let obj::IndexTuple(position_id, _texture_id, normal_id) = poly.0[index]; let [x, y, z] = data.position[position_id]; vertices.push(Vertex { pos: [y, z, x], // model is rotated to face down, so need to rotate it normal: data.normal[normal_id.unwrap()], }) } } } let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex"), contents: bytemuck::cast_slice(&vertices), usage: wgpu::BufferUsages::VERTEX, }); entities.push(Entity { vertex_count: vertices.len() as u32, vertex_buf, }); } } } let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, multisampled: false, view_dimension: wgpu::TextureViewDimension::Cube, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, ], }); // Create the render pipeline let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let camera = Camera { screen_size: (config.width, config.height), angle_xz: 0.2, angle_y: 0.2, dist: 20.0, }; let raw_uniforms = camera.to_uniform_data(); let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Buffer"), contents: bytemuck::cast_slice(&raw_uniforms), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); // Create the render pipelines let sky_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Sky"), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_sky"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_sky"), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), primitive: wgpu::PrimitiveState { front_face: wgpu::FrontFace::Cw, ..Default::default() }, depth_stencil: Some(wgpu::DepthStencilState { format: Self::DEPTH_FORMAT, depth_write_enabled: Some(false), depth_compare: Some(wgpu::CompareFunction::LessEqual), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }), multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let entity_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Entity"), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_entity"), compilation_options: Default::default(), buffers: &[wgpu::VertexBufferLayout { array_stride: size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3], }], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_entity"), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), primitive: wgpu::PrimitiveState { front_face: wgpu::FrontFace::Cw, ..Default::default() }, depth_stencil: Some(wgpu::DepthStencilState { format: Self::DEPTH_FORMAT, depth_write_enabled: Some(true), depth_compare: Some(wgpu::CompareFunction::LessEqual), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }), multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: None, address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, mipmap_filter: wgpu::MipmapFilterMode::Linear, ..Default::default() }); let device_features = device.features(); let skybox_format = if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC) { log::info!("Using astc"); wgpu::TextureFormat::Astc { block: AstcBlock::B4x4, channel: AstcChannel::UnormSrgb, } } else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2) { log::info!("Using etc2"); wgpu::TextureFormat::Etc2Rgb8A1UnormSrgb } else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) { log::info!("Using bc7"); wgpu::TextureFormat::Bc7RgbaUnormSrgb } else { log::info!("Using rgba8"); wgpu::TextureFormat::Rgba8UnormSrgb }; let size = wgpu::Extent3d { width: IMAGE_SIZE, height: IMAGE_SIZE, depth_or_array_layers: 6, }; let layer_size = wgpu::Extent3d { depth_or_array_layers: 1, ..size }; let max_mips = layer_size.max_mips(wgpu::TextureDimension::D2); log::debug!( "Copying {skybox_format:?} skybox images of size {IMAGE_SIZE}, {IMAGE_SIZE}, 6 with {max_mips} mips to gpu", ); let bytes = match skybox_format { wgpu::TextureFormat::Astc { block: AstcBlock::B4x4, channel: AstcChannel::UnormSrgb, } => &include_bytes!("images/astc.ktx2")[..], wgpu::TextureFormat::Etc2Rgb8A1UnormSrgb => &include_bytes!("images/etc2.ktx2")[..], wgpu::TextureFormat::Bc7RgbaUnormSrgb => &include_bytes!("images/bc7.ktx2")[..], wgpu::TextureFormat::Rgba8UnormSrgb => &include_bytes!("images/rgba8.ktx2")[..], _ => unreachable!(), }; let reader = ktx2::Reader::new(bytes).unwrap(); let header = reader.header(); let mut image = Vec::with_capacity(reader.data().len()); for level in reader.levels() { image.extend_from_slice(level.data); } let texture = device.create_texture_with_data( queue, &wgpu::TextureDescriptor { size, mip_level_count: header.level_count, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: skybox_format, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, label: None, view_formats: &[], }, // KTX2 stores mip levels in mip major order. wgpu::util::TextureDataOrder::MipMajor, &image, ); let texture_view = texture.create_view(&wgpu::TextureViewDescriptor { label: None, dimension: Some(wgpu::TextureViewDimension::Cube), ..wgpu::TextureViewDescriptor::default() }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: uniform_buf.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&texture_view), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&sampler), }, ], label: None, }); let depth_view = Self::create_depth_texture(config, device); Example { camera, sky_pipeline, entity_pipeline, bind_group, uniform_buf, entities, depth_view, staging_belt: wgpu::util::StagingBelt::new(device.clone(), 0x100), } } #[expect(clippy::single_match)] fn update(&mut self, event: winit::event::WindowEvent) { match event { winit::event::WindowEvent::CursorMoved { position, .. } => { let norm_x = position.x as f32 / self.camera.screen_size.0 as f32 - 0.5; let norm_y = position.y as f32 / self.camera.screen_size.1 as f32 - 0.5; self.camera.angle_y = norm_x * 5.0; self.camera.angle_xz = norm_y; } _ => {} } } fn resize( &mut self, config: &wgpu::SurfaceConfiguration, device: &wgpu::Device, _queue: &wgpu::Queue, ) { self.depth_view = Self::create_depth_texture(config, device); self.camera.screen_size = (config.width, config.height); } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); // update rotation let raw_uniforms = self.camera.to_uniform_data(); self.staging_belt .write_buffer( &mut encoder, &self.uniform_buf, 0, wgpu::BufferSize::new((raw_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(), ) .copy_from_slice(bytemuck::cast_slice(&raw_uniforms)); self.staging_belt.finish(); { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.1, g: 0.2, b: 0.3, a: 1.0, }), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &self.depth_view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), store: wgpu::StoreOp::Discard, }), stencil_ops: None, }), timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_bind_group(0, &self.bind_group, &[]); rpass.set_pipeline(&self.entity_pipeline); for entity in self.entities.iter() { rpass.set_vertex_buffer(0, entity.vertex_buf.slice(..)); rpass.draw(0..entity.vertex_count, 0..1); } rpass.set_pipeline(&self.sky_pipeline); rpass.draw(0..3, 0..1); } queue.submit(std::iter::once(encoder.finish())); self.staging_belt.recall(); } } pub fn main() { crate::framework::run::("skybox"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "skybox", image_path: "/examples/features/src/skybox/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default().expect_fail( wgpu_test::FailureCase::backend_adapter(wgpu::Backends::GL, "ANGLE"), ), comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], _phantom: std::marker::PhantomData::, }; #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST_BCN: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "skybox-bc7", image_path: "/examples/features/src/skybox/screenshot_bc7.png", width: 1024, height: 768, optional_features: wgpu::Features::TEXTURE_COMPRESSION_BC, base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], _phantom: std::marker::PhantomData::, }; #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST_ETC2: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "skybox-etc2", image_path: "/examples/features/src/skybox/screenshot_etc2.png", width: 1024, height: 768, optional_features: wgpu::Features::TEXTURE_COMPRESSION_ETC2, base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[wgpu_test::ComparisonType::Mean(0.016)], // Bounded by Apple A9 _phantom: std::marker::PhantomData::, }; #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST_ASTC: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "skybox-astc", image_path: "/examples/features/src/skybox/screenshot_astc.png", width: 1024, height: 768, optional_features: wgpu::Features::TEXTURE_COMPRESSION_ASTC, base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[wgpu_test::ComparisonType::Mean(0.017)], // Bounded by Apple A9 _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/skybox/models/rustacean-3d.mtl ================================================ # Blender MTL File: 'ferris.blend' # Material Count: 4 newmtl ferris_belly Ns 96.078431 Ka 1.000000 1.000000 1.000000 Kd 0.800000 0.347323 0.172688 Ks 0.500000 0.500000 0.500000 Ke 0.000000 0.000000 0.000000 Ni 1.000000 d 1.000000 illum 2 newmtl ferris_black Ns 96.078431 Ka 1.000000 1.000000 1.000000 Kd 0.000000 0.000000 0.000000 Ks 0.500000 0.500000 0.500000 Ke 0.000000 0.000000 0.000000 Ni 1.000000 d 1.000000 illum 2 newmtl ferris_orange Ns 96.078431 Ka 1.000000 1.000000 1.000000 Kd 0.639282 0.129624 0.012797 Ks 0.500000 0.500000 0.500000 Ke 0.000000 0.000000 0.000000 Ni 1.000000 d 1.000000 illum 2 newmtl ferris_white Ns 96.078431 Ka 1.000000 1.000000 1.000000 Kd 0.640000 0.640000 0.640000 Ks 0.500000 0.500000 0.500000 Ke 0.000000 0.000000 0.000000 Ni 1.000000 d 1.000000 illum 2 ================================================ FILE: examples/features/src/skybox/models/rustacean-3d.obj ================================================ # Blender v2.79 (sub 0) OBJ File: 'ferris.blend' # www.blender.org mtllib rustacean-3d.mtl o ferris_Circle.020 v 0.343562 -0.115370 1.395143 v 0.655653 0.451674 1.201330 v 0.343562 0.271006 1.361339 v 0.655654 -0.321079 1.268937 v 0.966308 -0.140679 1.105856 v 0.966308 0.245697 1.072053 v 0.819567 0.095934 1.585309 v 1.085411 0.410798 0.734117 v 1.085411 -0.361954 0.801724 v 1.200292 -0.174713 0.435707 v 1.200292 0.190101 0.436587 v 1.449873 0.035984 0.900069 v 1.025759 -0.028440 0.163706 v 0.479206 0.105580 0.016986 v -0.001648 0.144262 -0.001157 v 0.213908 -0.947237 0.554960 v 0.421748 -0.853499 1.003199 v 0.213908 -0.920817 0.856942 v 0.421748 -0.906339 0.399235 v 0.628899 -0.836463 0.545269 v 0.628899 -0.810043 0.847251 v 0.449768 -0.990436 0.637856 v 0.449768 -0.977754 0.782807 v 0.174526 -0.936381 0.466145 v 0.193919 -0.922133 0.406498 v 0.562379 -0.681502 1.154380 v 0.872660 -0.634649 0.799880 v 0.872660 -0.660002 0.510098 v 0.482496 -1.188729 0.133281 v 0.800427 -1.097982 0.162852 v 1.039682 -1.340016 0.310857 v 1.172861 -0.943232 0.440154 v 1.040352 -0.542321 0.477912 v 0.705973 -1.040242 0.326957 v 0.907219 -0.612008 0.321213 v 0.963359 -0.808547 0.257168 v 0.483649 -0.982584 0.200456 v 0.809329 -1.676895 -0.025794 v 1.278937 -1.448384 0.048669 v 1.382362 -0.944251 0.212947 v 1.173484 -0.455032 0.372365 v 1.092953 -1.047636 -0.047617 v 1.172861 -0.809566 0.029961 v 1.040352 -0.490720 0.215446 v 0.705973 -0.954643 0.064271 v 0.519263 -0.507354 0.204743 v 1.013693 -0.227480 0.227631 v 1.287415 -0.313834 0.237237 v 1.364960 -0.076581 0.181727 v 1.176592 -0.219118 0.004002 v 1.130867 -0.336723 0.081494 v 1.417569 -0.652758 -0.005180 v 1.102248 0.869963 0.434285 v 0.937434 0.564140 0.085652 v 1.311133 0.279575 0.271811 v 1.358940 0.590368 0.201054 v 1.159818 0.370578 -0.024071 v 1.183834 0.161861 0.052418 v 1.453527 -0.084113 -0.018462 v 0.617534 1.140735 0.567823 v 0.876999 0.810416 0.904753 v 0.399680 0.911546 1.095147 v 0.461937 0.837762 0.242420 v -0.000000 0.464342 1.346132 v 0.000000 -0.308410 1.413738 v -0.343562 -0.115371 1.395143 v 0.000000 0.111769 1.766311 v -0.655654 0.451673 1.201330 v -0.343562 0.271005 1.361339 v -0.655654 -0.321079 1.268937 v -0.966308 -0.140680 1.105857 v -0.966308 0.245696 1.072053 v -0.819567 0.095933 1.585309 v -1.085411 0.410798 0.734117 v -1.085411 -0.361955 0.801724 v -1.200292 -0.174713 0.435707 v -1.200292 0.190100 0.436587 v -1.449873 0.035983 0.900069 v -1.025759 -0.028441 0.163706 v -0.479206 0.105580 0.016986 v 0.001648 0.144262 -0.001157 v -0.213907 -0.947237 0.554960 v -0.421747 -0.853500 1.003199 v -0.213907 -0.920817 0.856942 v -0.421747 -0.906340 0.399235 v -0.628899 -0.836464 0.545269 v -0.628899 -0.810044 0.847251 v -0.449767 -0.990436 0.637856 v -0.449767 -0.977755 0.782807 v 0.000000 -0.977554 0.440547 v 0.000000 -0.952702 0.318504 v -0.174526 -0.936381 0.466145 v -0.193918 -0.922133 0.406498 v 0.000000 -1.007741 0.540521 v 0.000000 -0.979008 0.868941 v 0.000000 -0.867202 1.034359 v 0.000000 -0.963683 0.250442 v 0.000000 -0.667980 1.308930 v -0.562378 -0.681502 1.154380 v -0.872660 -0.634650 0.799880 v -0.872660 -0.660003 0.510098 v -0.482495 -1.188729 0.133281 v -0.800426 -1.097982 0.162852 v -1.039681 -1.340016 0.310857 v -1.172860 -0.943233 0.440154 v -1.040351 -0.542322 0.477912 v -0.705972 -1.040243 0.326957 v -0.907219 -0.612009 0.321213 v -0.963359 -0.808547 0.257168 v -0.483649 -0.982585 0.200456 v -0.809328 -1.676896 -0.025794 v -1.278936 -1.448385 0.048670 v -1.382361 -0.944252 0.212947 v -1.173484 -0.455033 0.372365 v -1.092953 -1.047637 -0.047617 v -1.172860 -0.809567 0.029961 v -1.040351 -0.490721 0.215446 v -0.705972 -0.954643 0.064271 v 0.000000 -0.730753 0.146421 v -0.519262 -0.507354 0.204743 v -1.013693 -0.227481 0.227631 v -1.287415 -0.313835 0.237238 v -1.364960 -0.076582 0.181727 v -1.176592 -0.219119 0.004002 v -1.130866 -0.336723 0.081494 v -1.417568 -0.652759 -0.005180 v -1.102249 0.869962 0.434285 v -0.937435 0.564139 0.085652 v -1.311133 0.279574 0.271811 v -1.358940 0.590367 0.201055 v -1.159819 0.370577 -0.024071 v -1.183834 0.161860 0.052418 v -1.453527 -0.084114 -0.018462 v -0.617535 1.140735 0.567823 v -0.000000 1.293851 0.592225 v -0.876999 0.810416 0.904753 v -0.399681 0.911546 1.095147 v -0.000000 0.948039 1.089680 v -0.461938 0.837762 0.242420 v -0.000000 0.935008 0.273219 v 0.421748 -0.906339 0.399235 v 0.213908 -0.947237 0.554960 v 0.213908 -0.920817 0.856942 v 0.421748 -0.853499 1.003199 v 0.628899 -0.836463 0.545269 v 0.628899 -0.810043 0.847251 v 0.193919 -0.922133 0.406498 v 0.000000 -0.952702 0.318504 v 1.102248 0.869963 0.434285 v 0.617534 1.140735 0.567823 v -0.000000 1.293851 0.592225 v -0.421747 -0.906340 0.399235 v -0.213907 -0.947237 0.554960 v -0.213907 -0.920817 0.856942 v -0.421747 -0.853500 1.003199 v -0.628899 -0.836464 0.545269 v -0.628899 -0.810044 0.847251 v -0.193918 -0.922133 0.406498 v -1.102249 0.869962 0.434285 v -0.617535 1.140735 0.567823 vn 0.0000 -0.4368 0.8995 vn 0.2051 -0.3077 0.9291 vn 0.0000 0.0871 0.9962 vn 0.1833 0.4441 0.8770 vn 0.0000 0.5455 0.8381 vn 0.4226 0.0790 0.9028 vn 0.4060 -0.4949 0.7682 vn 0.6783 -0.3555 0.6430 vn 0.6960 0.3841 0.6066 vn 0.3899 0.5340 0.7501 vn 0.9401 0.0277 0.3397 vn 0.7461 -0.5687 0.3462 vn 0.9235 -0.3715 0.0959 vn 0.9946 0.1040 0.0008 vn 0.8265 0.4248 0.3693 vn 0.0429 -0.0050 -0.9991 vn 0.8653 0.4533 -0.2139 vn -0.1990 0.2234 -0.9542 vn 0.2076 -0.9735 -0.0956 vn 0.1864 -0.9608 -0.2051 vn 0.0753 -0.9969 0.0243 vn 0.3425 -0.6712 -0.6573 vn 0.0000 -0.9443 0.3291 vn 0.1771 -0.9405 0.2899 vn 0.0000 -0.8293 0.5588 vn 0.0000 -0.9954 -0.0960 vn 0.0000 -0.9745 -0.2243 vn 0.0000 -0.9858 0.1677 vn 0.0000 -0.8603 -0.5097 vn 0.2563 -0.8081 0.5303 vn 0.4194 -0.5813 0.6972 vn 0.0000 -0.5390 0.8423 vn 0.6633 -0.6696 0.3340 vn 0.5694 -0.7664 0.2973 vn 0.5665 -0.7135 -0.4121 vn 0.4860 -0.8572 -0.1704 vn 0.1453 -0.1681 0.9750 vn -0.9087 0.3069 0.2830 vn -0.4718 -0.8813 0.0279 vn -0.0512 -0.4465 0.8933 vn -0.4985 -0.8124 -0.3026 vn 0.6489 -0.5432 0.5328 vn 0.9988 0.0452 0.0185 vn 0.9877 0.1307 0.0858 vn -0.4748 -0.7197 -0.5065 vn 0.7671 -0.5939 -0.2425 vn 0.0303 -0.2539 0.9667 vn -0.6417 -0.7291 -0.2376 vn -0.8813 0.4492 0.1464 vn 0.1052 0.1086 -0.9885 vn 0.2404 0.1329 -0.9615 vn 0.2497 0.4772 -0.8425 vn -0.3275 0.3544 -0.8758 vn -0.0395 -0.4896 -0.8710 vn 0.0000 -0.3718 -0.9283 vn 0.2081 -0.3972 -0.8938 vn 0.3307 -0.5788 0.7454 vn -0.7608 -0.6104 -0.2203 vn 0.3931 -0.8722 -0.2910 vn 0.0359 0.4048 -0.9137 vn 0.1688 0.6839 -0.7098 vn 0.8393 0.5311 -0.1162 vn -0.0417 0.1765 -0.9834 vn -0.7999 0.3768 0.4670 vn -0.8393 0.5311 -0.1162 vn -0.8000 -0.2840 0.5286 vn -0.3594 -0.7498 -0.5555 vn 0.8000 -0.2840 0.5286 vn 0.4446 -0.8453 -0.2963 vn 0.6152 0.5343 0.5796 vn 0.7999 0.3768 0.4670 vn 0.2460 0.6330 0.7340 vn 0.0000 0.8563 0.5164 vn 0.0000 0.6708 0.7416 vn 0.3408 0.8184 0.4627 vn 0.0000 0.6829 -0.7305 vn 0.1480 0.6828 -0.7154 vn 0.1015 0.5061 -0.8565 vn 0.0000 0.5337 -0.8456 vn -0.2051 -0.3077 0.9291 vn -0.1833 0.4441 0.8770 vn -0.4226 0.0790 0.9028 vn -0.4060 -0.4949 0.7682 vn -0.6783 -0.3555 0.6430 vn -0.6960 0.3841 0.6066 vn -0.3899 0.5340 0.7501 vn -0.9401 0.0277 0.3397 vn -0.7461 -0.5687 0.3462 vn -0.9235 -0.3715 0.0959 vn -0.9946 0.1040 0.0008 vn -0.8265 0.4248 0.3693 vn -0.0429 -0.0050 -0.9991 vn 0.1990 0.2234 -0.9542 vn -0.8653 0.4533 -0.2139 vn -0.2076 -0.9735 -0.0956 vn -0.3425 -0.6712 -0.6573 vn -0.0753 -0.9969 0.0243 vn -0.1864 -0.9608 -0.2051 vn -0.1771 -0.9405 0.2899 vn -0.2563 -0.8081 0.5303 vn -0.4194 -0.5813 0.6972 vn -0.6633 -0.6696 0.3340 vn -0.5694 -0.7664 0.2973 vn -0.5665 -0.7135 -0.4121 vn -0.4860 -0.8572 -0.1704 vn -0.1453 -0.1681 0.9750 vn 0.0512 -0.4465 0.8933 vn 0.4718 -0.8813 0.0279 vn 0.9087 0.3069 0.2830 vn 0.4985 -0.8124 -0.3026 vn -0.6489 -0.5432 0.5328 vn -0.9877 0.1307 0.0858 vn -0.9988 0.0452 0.0185 vn 0.4748 -0.7197 -0.5065 vn -0.7671 -0.5939 -0.2425 vn -0.0303 -0.2539 0.9667 vn 0.6417 -0.7291 -0.2376 vn 0.8813 0.4492 0.1464 vn 0.3275 0.3544 -0.8758 vn -0.2497 0.4772 -0.8425 vn -0.1052 0.1086 -0.9885 vn -0.2404 0.1329 -0.9615 vn 0.0395 -0.4896 -0.8710 vn -0.2081 -0.3972 -0.8938 vn -0.3307 -0.5788 0.7454 vn 0.7608 -0.6104 -0.2203 vn -0.3931 -0.8722 -0.2910 vn -0.0359 0.4048 -0.9137 vn 0.0417 0.1765 -0.9834 vn -0.1688 0.6839 -0.7098 vn 0.3594 -0.7498 -0.5555 vn -0.4446 -0.8453 -0.2963 vn -0.6152 0.5343 0.5796 vn -0.2460 0.6330 0.7340 vn -0.3408 0.8184 0.4627 vn -0.1015 0.5061 -0.8565 vn -0.1480 0.6828 -0.7154 vn -0.1442 -0.9514 0.2718 vn -0.1442 -0.9842 -0.1025 vn 0.2552 -0.9633 -0.0827 vn 0.2552 -0.9343 0.2487 vn 0.6107 -0.7499 0.2542 vn 0.6107 -0.7827 -0.1201 vn 0.2342 -0.9079 -0.3475 vn 0.2342 -0.8338 0.4999 vn 0.0000 -0.9827 -0.1850 vn 0.2410 -0.9537 -0.1795 vn 0.1442 -0.9514 0.2718 vn -0.2552 -0.9343 0.2487 vn -0.2552 -0.9633 -0.0827 vn 0.1442 -0.9842 -0.1025 vn -0.6107 -0.7499 0.2542 vn -0.6107 -0.7827 -0.1201 vn -0.2342 -0.8338 0.4999 vn -0.2342 -0.9079 -0.3475 vn -0.2410 -0.9537 -0.1795 vn 0.0887 -0.0077 -0.9960 vn 0.0738 0.0631 -0.9953 vn -0.0887 -0.0077 -0.9960 vn -0.0738 0.0631 -0.9953 usemtl ferris_orange s 1 f 65//1 1//2 67//3 f 1//2 3//4 67//3 f 3//4 64//5 67//3 f 3//4 1//2 7//6 f 1//2 4//7 7//6 f 4//7 5//8 7//6 f 5//8 6//9 7//6 f 6//9 2//10 7//6 f 2//10 3//4 7//6 f 6//9 5//8 12//11 f 5//8 9//12 12//11 f 9//12 10//13 12//11 f 10//13 11//14 12//11 f 11//14 8//15 12//11 f 8//15 6//9 12//11 f 13//16 11//14 49//17 50//18 f 16//19 24//20 25//21 19//22 f 95//23 18//24 96//25 f 18//24 95//23 94//26 16//19 f 90//27 24//20 16//19 94//26 f 91//28 97//29 19//22 25//21 f 18//24 17//30 96//25 f 26//31 98//32 96//25 17//30 f 4//7 1//2 65//1 98//32 26//31 f 5//8 4//7 26//31 27//33 9//12 f 21//34 27//33 26//31 17//30 f 20//35 28//36 27//33 21//34 f 32//37 36//38 30//39 31//40 f 31//40 30//39 38//41 f 33//42 32//37 40//43 41//44 f 35//45 36//38 32//37 33//42 f 40//43 32//37 31//40 39//46 f 36//38 34//47 30//39 f 30//39 34//47 29//48 f 29//48 34//47 37//49 f 37//49 34//47 36//38 f 31//40 38//41 39//46 f 33//42 28//36 35//45 f 10//13 9//12 33//42 f 9//12 27//33 33//42 f 27//33 28//36 33//42 f 41//44 10//13 33//42 f 42//50 38//41 30//39 f 44//51 41//44 40//43 43//52 f 35//45 44//51 43//52 36//38 f 40//43 39//46 42//50 43//52 f 30//39 29//48 45//53 f 29//48 37//49 45//53 f 37//49 36//38 45//53 f 42//50 39//46 38//41 f 10//13 41//44 44//51 47//54 f 119//55 46//56 19//22 97//29 f 19//22 46//56 28//36 20//35 f 28//36 46//56 35//45 f 46//56 13//16 47//54 f 35//45 46//56 47//54 44//51 f 11//14 10//13 48//57 49//17 f 10//13 47//54 51//58 48//57 f 47//54 13//16 50//18 51//58 f 51//58 50//18 52//59 f 49//17 48//57 52//59 f 48//57 51//58 52//59 f 50//18 49//17 52//59 f 54//60 149//61 56//62 57//63 f 127//64 130//65 129//66 f 11//14 13//16 58//67 55//68 f 58//67 57//63 59//69 f 56//62 55//68 59//69 f 55//68 58//67 59//69 f 57//63 56//62 59//69 f 61//70 8//15 53//71 f 8//15 11//14 53//71 f 2//10 6//9 8//15 61//70 f 62//72 2//10 61//70 f 151//73 138//74 62//72 150//75 f 150//75 61//70 53//71 f 150//75 62//72 61//70 f 64//5 3//4 2//10 62//72 138//74 f 135//76 60//77 63//78 140//79 f 54//60 63//78 60//77 149//61 f 65//1 67//3 66//80 f 66//80 67//3 69//81 f 69//81 67//3 64//5 f 69//81 73//82 66//80 f 66//80 73//82 70//83 f 70//83 73//82 71//84 f 71//84 73//82 72//85 f 72//85 73//82 68//86 f 68//86 73//82 69//81 f 72//85 78//87 71//84 f 71//84 78//87 75//88 f 75//88 78//87 76//89 f 76//89 78//87 77//90 f 77//90 78//87 74//91 f 74//91 78//87 72//85 f 79//92 124//93 123//94 77//90 f 82//95 85//96 93//97 92//98 f 95//23 96//25 84//99 f 84//99 82//95 94//26 95//23 f 90//27 94//26 82//95 92//98 f 91//28 93//97 85//96 97//29 f 84//99 96//25 83//100 f 99//101 83//100 96//25 98//32 f 70//83 99//101 98//32 65//1 66//80 f 71//84 75//88 100//102 99//101 70//83 f 87//103 83//100 99//101 100//102 f 86//104 87//103 100//102 101//105 f 105//106 104//107 103//108 109//109 f 104//107 111//110 103//108 f 106//111 114//112 113//113 105//106 f 108//114 106//111 105//106 109//109 f 113//113 112//115 104//107 105//106 f 109//109 103//108 107//116 f 103//108 102//117 107//116 f 102//117 110//118 107//116 f 110//118 109//109 107//116 f 104//107 112//115 111//110 f 106//111 108//114 101//105 f 76//89 106//111 75//88 f 75//88 106//111 100//102 f 100//102 106//111 101//105 f 118//119 116//120 109//109 f 114//112 106//111 76//89 f 115//121 103//108 111//110 f 117//122 116//120 113//113 114//112 f 108//114 109//109 116//120 117//122 f 113//113 116//120 115//121 112//115 f 103//108 115//121 118//119 f 103//108 118//119 102//117 f 102//117 118//119 110//118 f 110//118 118//119 109//109 f 115//121 111//110 112//115 f 76//89 121//123 117//122 114//112 f 119//55 97//29 85//96 120//124 f 85//96 86//104 101//105 120//124 f 101//105 108//114 120//124 f 120//124 121//123 79//92 f 108//114 117//122 121//123 120//124 f 77//90 123//94 122//125 76//89 f 76//89 122//125 125//126 121//123 f 121//123 125//126 124//93 79//92 f 125//126 126//127 124//93 f 123//94 126//127 122//125 f 122//125 126//127 125//126 f 124//93 126//127 123//94 f 128//128 131//129 130//65 159//130 f 77//90 129//66 132//131 79//92 f 132//131 133//132 131//129 f 130//65 133//132 129//66 f 129//66 133//132 132//131 f 131//129 133//132 130//65 f 136//133 127//64 74//91 f 74//91 127//64 77//90 f 68//86 136//133 74//91 72//85 f 137//134 136//133 68//86 f 151//73 160//135 137//134 138//74 f 160//135 127//64 136//133 f 160//135 136//133 137//134 f 64//5 138//74 137//134 68//86 69//81 f 135//76 140//79 139//136 134//137 f 128//128 159//130 134//137 139//136 f 56//62 53//71 55//68 f 77//90 127//64 129//66 f 53//71 11//14 55//68 f 118//119 115//121 116//120 f 36//38 43//52 45//53 f 43//52 42//50 45//53 f 30//39 45//53 42//50 usemtl ferris_black f 143//138 142//139 22//140 23//141 f 146//142 23//141 22//140 145//143 f 141//144 22//140 142//139 f 144//145 23//141 146//142 f 145//143 22//140 141//144 f 148//146 147//147 24//20 90//27 f 154//148 89//149 88//150 153//151 f 157//152 156//153 88//150 89//149 f 154//148 155//154 89//149 f 152//155 153//151 88//150 f 156//153 152//155 88//150 f 148//146 90//27 92//98 158//156 usemtl ferris_white f 143//138 23//141 144//145 f 155//154 157//152 89//149 usemtl ferris_belly f 14//157 13//16 46//56 f 15//158 14//157 46//56 119//55 f 13//16 54//60 57//63 58//67 f 14//157 63//78 54//60 13//16 f 15//158 140//79 63//78 14//157 f 80//159 120//124 79//92 f 81//160 119//55 120//124 80//159 f 79//92 132//131 131//129 128//128 f 80//159 79//92 128//128 139//136 f 81//160 80//159 139//136 140//79 ================================================ FILE: examples/features/src/skybox/shader.wgsl ================================================ struct SkyOutput { @builtin(position) position: vec4, @location(0) uv: vec3, }; struct Data { // from camera to screen proj: mat4x4, // from screen to camera proj_inv: mat4x4, // from world to camera view: mat4x4, // camera position cam_pos: vec4, }; @group(0) @binding(0) var r_data: Data; @vertex fn vs_sky(@builtin(vertex_index) vertex_index: u32) -> SkyOutput { // hacky way to draw a large triangle let tmp1 = i32(vertex_index) / 2; let tmp2 = i32(vertex_index) & 1; let pos = vec4( f32(tmp1) * 4.0 - 1.0, f32(tmp2) * 4.0 - 1.0, 1.0, 1.0 ); // transposition = inversion for this orthonormal matrix let inv_model_view = transpose(mat3x3(r_data.view[0].xyz, r_data.view[1].xyz, r_data.view[2].xyz)); let unprojected = r_data.proj_inv * pos; var result: SkyOutput; result.uv = inv_model_view * unprojected.xyz; result.position = pos; return result; } struct EntityOutput { @builtin(position) position: vec4, @location(1) normal: vec3, @location(3) view: vec3, }; @vertex fn vs_entity( @location(0) pos: vec3, @location(1) normal: vec3, ) -> EntityOutput { var result: EntityOutput; result.normal = normal; result.view = pos - r_data.cam_pos.xyz; result.position = r_data.proj * r_data.view * vec4(pos, 1.0); return result; } @group(0) @binding(1) var r_texture: texture_cube; @group(0) @binding(2) var r_sampler: sampler; @fragment fn fs_sky(vertex: SkyOutput) -> @location(0) vec4 { return textureSample(r_texture, r_sampler, vertex.uv); } @fragment fn fs_entity(vertex: EntityOutput) -> @location(0) vec4 { let incident = normalize(vertex.view); let normal = normalize(vertex.normal); let reflected = incident - 2.0 * dot(normal, incident) * normal; let reflected_color = textureSample(r_texture, r_sampler, reflected).rgb; return vec4(vec3(0.1) + 0.5 * reflected_color, 1.0); } ================================================ FILE: examples/features/src/srgb_blend/README.md ================================================ # srgb_blend This example shows blending in sRGB or linear space. ## To Run ``` cargo run --bin wgpu-examples srgb_blend linear ``` ``` cargo run --bin wgpu-examples srgb_blend ``` ## Screenshots Blending in linear space: ![sRGB blend example](./screenshot-linear.png) Blending in sRGB space: ![sRGB blend example](./screenshot-srgb.png) ================================================ FILE: examples/features/src/srgb_blend/mod.rs ================================================ use bytemuck::{Pod, Zeroable}; use wgpu::util::DeviceExt; #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Vertex { _pos: [f32; 4], _color: [f32; 4], } fn vertex(pos: [i8; 2], _color: [f32; 4], offset: f32) -> Vertex { let scale = 0.5; Vertex { _pos: [ (pos[0] as f32 + offset) * scale, (pos[1] as f32 + offset) * scale, 0.0, 1.0, ], _color, } } fn quad(vertices: &mut Vec, indices: &mut Vec, color: [f32; 4], offset: f32) { let base = vertices.len() as u16; vertices.extend_from_slice(&[ vertex([-1, -1], color, offset), vertex([1, -1], color, offset), vertex([1, 1], color, offset), vertex([-1, 1], color, offset), ]); indices.extend([0, 1, 2, 2, 3, 0].iter().map(|i| base + *i)); } fn create_vertices() -> (Vec, Vec) { let mut vertices = Vec::new(); let mut indices = Vec::new(); let red = [1.0, 0.0, 0.0, 0.5]; let blue = [0.0, 0.0, 1.0, 0.5]; quad(&mut vertices, &mut indices, red, 0.5); quad(&mut vertices, &mut indices, blue, -0.5); (vertices, indices) } struct Example { vertex_buf: wgpu::Buffer, index_buf: wgpu::Buffer, index_count: usize, bind_group: wgpu::BindGroup, pipeline: wgpu::RenderPipeline, } impl crate::framework::Example for Example { const SRGB: bool = SRGB; fn optional_features() -> wgpu::Features { wgpu::Features::POLYGON_MODE_LINE } fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { // Create the vertex and index buffers let vertex_size = size_of::(); let (vertex_data, index_data) = create_vertices(); let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::cast_slice(&vertex_data), usage: wgpu::BufferUsages::VERTEX, }); let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Index Buffer"), contents: bytemuck::cast_slice(&index_data), usage: wgpu::BufferUsages::INDEX, }); // Create pipeline layout let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[], }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); // Create bind group let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &bind_group_layout, entries: &[], label: None, }); let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let vertex_buffers = [wgpu::VertexBufferLayout { array_stride: vertex_size as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &[ wgpu::VertexAttribute { format: wgpu::VertexFormat::Float32x4, offset: 0, shader_location: 0, }, wgpu::VertexAttribute { format: wgpu::VertexFormat::Float32x4, offset: 4 * 4, shader_location: 1, }, ], }]; let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &vertex_buffers, }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(wgpu::ColorTargetState { format: config.view_formats[0], blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], }), primitive: wgpu::PrimitiveState { cull_mode: Some(wgpu::Face::Back), ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); // Done Example { vertex_buf, index_buf, index_count: index_data.len(), bind_group, pipeline, } } fn update(&mut self, _event: winit::event::WindowEvent) { //empty } fn resize( &mut self, _config: &wgpu::SurfaceConfiguration, _device: &wgpu::Device, _queue: &wgpu::Queue, ) { } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0, }), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.push_debug_group("Prepare data for draw."); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, &self.bind_group, &[]); rpass.set_index_buffer(self.index_buf.slice(..), wgpu::IndexFormat::Uint16); rpass.set_vertex_buffer(0, self.vertex_buf.slice(..)); rpass.pop_debug_group(); rpass.insert_debug_marker("Draw!"); rpass.draw_indexed(0..self.index_count as u32, 0, 0..1); } queue.submit(Some(encoder.finish())); } } pub fn main() { let mut args = std::env::args(); args.next(); if Some("linear") == args.nth(1).as_deref() { crate::framework::run::>("srgb-blend-linear"); } else { crate::framework::run::>("srgb-blend-srg"); } } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST_SRGB: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "srgb-blend-srg", // Generated on WARP/Windows image_path: "/examples/features/src/srgb_blend/screenshot-srgb.png", width: 192, height: 192, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[wgpu_test::ComparisonType::Mean(0.04)], _phantom: std::marker::PhantomData::>, }; #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST_LINEAR: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "srgb-blend-linear", // Generated on WARP/Windows image_path: "/examples/features/src/srgb_blend/screenshot-linear.png", width: 192, height: 192, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[wgpu_test::ComparisonType::Mean(0.04)], _phantom: std::marker::PhantomData::>, }; ================================================ FILE: examples/features/src/srgb_blend/shader.wgsl ================================================ struct VertexOutput { @location(0) color: vec4, @builtin(position) position: vec4, }; @vertex fn vs_main( @location(0) position: vec4, @location(1) color: vec4, ) -> VertexOutput { var result: VertexOutput; result.color = color; result.position = position; return result; } @group(0) @binding(1) var color: vec4; @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { return vertex.color; } ================================================ FILE: examples/features/src/stencil_triangles/README.md ================================================ # stencil_triangles This example renders two different sized triangles to display three same sized triangles, by demonstrating the use of stencil buffers. First it draws a small "mask" triangle, which sets the stencil buffer at every pixel to 1. Then, it draws a larger "outer" triangle which only touches pixels where the stencil buffer is less than 1. ## To Run ``` cargo run --bin wgpu-examples stencil_triangles ``` ## Screenshots ![Stencil Triangles window](./screenshot.png) ================================================ FILE: examples/features/src/stencil_triangles/mod.rs ================================================ use bytemuck::{Pod, Zeroable}; use wgpu::util::DeviceExt; #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Vertex { _pos: [f32; 4], } fn vertex(x: f32, y: f32) -> Vertex { Vertex { _pos: [x, y, 0.0, 1.0], } } struct Example { outer_vertex_buffer: wgpu::Buffer, mask_vertex_buffer: wgpu::Buffer, outer_pipeline: wgpu::RenderPipeline, mask_pipeline: wgpu::RenderPipeline, stencil_buffer: wgpu::Texture, } impl crate::framework::Example for Example { fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { // Create the vertex and index buffers let vertex_size = size_of::(); let outer_vertices = [vertex(-1.0, -1.0), vertex(1.0, -1.0), vertex(0.0, 1.0)]; let mask_vertices = [vertex(-0.5, 0.0), vertex(0.0, -1.0), vertex(0.5, 0.0)]; let outer_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Outer Vertex Buffer"), contents: bytemuck::cast_slice(&outer_vertices), usage: wgpu::BufferUsages::VERTEX, }); let mask_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Mask Vertex Buffer"), contents: bytemuck::cast_slice(&mask_vertices), usage: wgpu::BufferUsages::VERTEX, }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[], immediate_size: 0, }); let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let vertex_buffers = [wgpu::VertexBufferLayout { array_stride: vertex_size as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &[wgpu::VertexAttribute { format: wgpu::VertexFormat::Float32x4, offset: 0, shader_location: 0, }], }]; let mask_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &vertex_buffers, }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(wgpu::ColorTargetState { format: config.view_formats[0], blend: None, write_mask: wgpu::ColorWrites::empty(), })], }), primitive: Default::default(), depth_stencil: Some(wgpu::DepthStencilState::stencil( wgpu::TextureFormat::Stencil8, wgpu::StencilState { front: wgpu::StencilFaceState { compare: wgpu::CompareFunction::Always, pass_op: wgpu::StencilOperation::Replace, ..Default::default() }, back: wgpu::StencilFaceState::IGNORE, read_mask: !0, write_mask: !0, }, )), multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let outer_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &vertex_buffers, }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), primitive: Default::default(), depth_stencil: Some(wgpu::DepthStencilState::stencil( wgpu::TextureFormat::Stencil8, wgpu::StencilState { front: wgpu::StencilFaceState { compare: wgpu::CompareFunction::Greater, ..Default::default() }, back: wgpu::StencilFaceState::IGNORE, read_mask: !0, write_mask: !0, }, )), multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let stencil_buffer = device.create_texture(&wgpu::TextureDescriptor { label: Some("Stencil buffer"), size: wgpu::Extent3d { width: config.width, height: config.height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Stencil8, view_formats: &[], usage: wgpu::TextureUsages::RENDER_ATTACHMENT, }); // Done Example { outer_vertex_buffer, mask_vertex_buffer, outer_pipeline, mask_pipeline, stencil_buffer, } } fn update(&mut self, _event: winit::event::WindowEvent) { // empty } fn resize( &mut self, config: &wgpu::SurfaceConfiguration, device: &wgpu::Device, _queue: &wgpu::Queue, ) { self.stencil_buffer = device.create_texture(&wgpu::TextureDescriptor { label: Some("Stencil buffer"), size: wgpu::Extent3d { width: config.width, height: config.height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Stencil8, view_formats: &[], usage: wgpu::TextureUsages::RENDER_ATTACHMENT, }); } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let depth_view = self.stencil_buffer.create_view(&Default::default()); let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.1, g: 0.2, b: 0.3, a: 1.0, }), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &depth_view, depth_ops: None, stencil_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(0), store: wgpu::StoreOp::Store, }), }), timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_stencil_reference(1); rpass.set_pipeline(&self.mask_pipeline); rpass.set_vertex_buffer(0, self.mask_vertex_buffer.slice(..)); rpass.draw(0..3, 0..1); rpass.set_pipeline(&self.outer_pipeline); rpass.set_vertex_buffer(0, self.outer_vertex_buffer.slice(..)); rpass.draw(0..3, 0..1); } queue.submit(Some(encoder.finish())); } } pub fn main() { crate::framework::run::("stencil-triangles"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "stencil-triangles", image_path: "/examples/features/src/stencil_triangles/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[wgpu_test::ComparisonType::Mean(0.04)], // Bounded by Apple A9 _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/stencil_triangles/shader.wgsl ================================================ struct VertexOutput { @builtin(position) position: vec4, }; @vertex fn vs_main(@location(0) position: vec4) -> VertexOutput { var result: VertexOutput; result.position = position; return result; } @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { return vec4(0.97, 0.88, 0.21, 1.0); } ================================================ FILE: examples/features/src/storage_texture/README.md ================================================ # storage_texture A simple example that uses a storage texture to compute an image of the Mandelbrot set (https://en.wikipedia.org/wiki/Mandelbrot_set) and either saves it as an image or presents it to the browser screen in such a way that it can be saved as an image. ## To Run ``` cargo run --bin wgpu-examples storage_texture ``` ## Example Output ![Example output](./example.png) ================================================ FILE: examples/features/src/storage_texture/mod.rs ================================================ //! This example demonstrates the basic usage of storage textures for the purpose of //! creating a digital image of the Mandelbrot set //! (). //! //! Storage textures work like normal textures but they operate similar to storage buffers //! in that they can be written to. The issue is that as it stands, write-only is the //! only valid access mode for storage textures in WGSL and although there is a wgpu feature //! to allow for read-write access, this is unfortunately a native-only feature and thus //! we won't be using it here. If we needed a reference texture, we would need to add a //! second texture to act as a reference and attach that as well. Luckily, we don't need //! to read anything in our shader except the dimensions of our texture, which we can //! easily get via `textureDimensions`. //! //! A lot of things aren't explained here via comments. See hello-compute and //! repeated-compute for code that is more thoroughly commented. #[cfg(not(target_arch = "wasm32"))] use crate::utils::output_image_native; #[cfg(target_arch = "wasm32")] use crate::utils::output_image_wasm; const TEXTURE_DIMS: (usize, usize) = (512, 512); async fn run(_path: Option) { let mut texture_data = vec![0u8; TEXTURE_DIMS.0 * TEXTURE_DIMS.1 * 4]; let instance = wgpu::Instance::default(); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions::default()) .await .unwrap(); let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) .await .unwrap(); let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let storage_texture = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: TEXTURE_DIMS.0 as u32, height: TEXTURE_DIMS.1 as u32, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::COPY_SRC, view_formats: &[], }); let storage_texture_view = storage_texture.create_view(&wgpu::TextureViewDescriptor::default()); let output_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: size_of_val(&texture_data[..]) as u64, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false, }); let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::StorageTexture { access: wgpu::StorageTextureAccess::WriteOnly, format: wgpu::TextureFormat::Rgba8Unorm, view_dimension: wgpu::TextureViewDimension::D2, }, count: None, }], }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&storage_texture_view), }], }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: Some(&pipeline_layout), module: &shader, entry_point: Some("main"), compilation_options: Default::default(), cache: None, }); log::info!("Wgpu context set up."); //---------------------------------------- let mut command_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut compute_pass = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); compute_pass.set_bind_group(0, &bind_group, &[]); compute_pass.set_pipeline(&pipeline); compute_pass.dispatch_workgroups(TEXTURE_DIMS.0 as u32, TEXTURE_DIMS.1 as u32, 1); } command_encoder.copy_texture_to_buffer( wgpu::TexelCopyTextureInfo { texture: &storage_texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All, }, wgpu::TexelCopyBufferInfo { buffer: &output_staging_buffer, layout: wgpu::TexelCopyBufferLayout { offset: 0, // This needs to be padded to 256. bytes_per_row: Some((TEXTURE_DIMS.0 * 4) as u32), rows_per_image: Some(TEXTURE_DIMS.1 as u32), }, }, wgpu::Extent3d { width: TEXTURE_DIMS.0 as u32, height: TEXTURE_DIMS.1 as u32, depth_or_array_layers: 1, }, ); queue.submit(Some(command_encoder.finish())); let buffer_slice = output_staging_buffer.slice(..); let (sender, receiver) = flume::bounded(1); buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap()); device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); receiver.recv_async().await.unwrap().unwrap(); log::info!("Output buffer mapped"); { let view = buffer_slice.get_mapped_range(); texture_data.copy_from_slice(&view[..]); } log::info!("GPU data copied to local."); output_staging_buffer.unmap(); #[cfg(not(target_arch = "wasm32"))] output_image_native(texture_data.to_vec(), TEXTURE_DIMS, _path.unwrap()); #[cfg(target_arch = "wasm32")] output_image_wasm(texture_data.to_vec(), TEXTURE_DIMS); log::info!("Done.") } pub fn main() { #[cfg(not(target_arch = "wasm32"))] { env_logger::builder() .filter_level(log::LevelFilter::Info) .format_timestamp_nanos() .init(); let path = std::env::args() .nth(2) .unwrap_or_else(|| "please_don't_git_push_me.png".to_string()); pollster::block_on(run(Some(path))); } #[cfg(target_arch = "wasm32")] { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); console_log::init_with_level(log::Level::Info).expect("could not initialize logger"); wasm_bindgen_futures::spawn_local(run(None)); } } ================================================ FILE: examples/features/src/storage_texture/shader.wgsl ================================================ const MAX_ITERATIONS: u32 = 50u; @group(0) @binding(0) var texture: texture_storage_2d; @compute @workgroup_size(1) fn main(@builtin(global_invocation_id) id: vec3) { var final_iteration = MAX_ITERATIONS; var c = vec2( // Translated to put everything nicely in frame. (f32(id.x) / f32(textureDimensions(texture).x)) * 3.0 - 2.25, (f32(id.y) / f32(textureDimensions(texture).y)) * 3.0 - 1.5 ); var current_z = c; var next_z: vec2; for (var i = 0u; i < MAX_ITERATIONS; i++) { next_z.x = (current_z.x * current_z.x - current_z.y * current_z.y) + c.x; next_z.y = (2.0 * current_z.x * current_z.y) + c.y; current_z = next_z; if length(current_z) > 4.0 { final_iteration = i; break; } } let value = f32(final_iteration) / f32(MAX_ITERATIONS); textureStore(texture, vec2(i32(id.x), i32(id.y)), vec4(value, value, value, 1.0)); } ================================================ FILE: examples/features/src/texture_arrays/README.md ================================================ # texture_arrays ## To Run ``` cargo run --bin wgpu-examples texture_arrays ``` ## Example Output ![Example output](./screenshot.png) ================================================ FILE: examples/features/src/texture_arrays/indexing.wgsl ================================================ struct VertexInput { @location(0) position: vec2, @location(1) tex_coord: vec2, @location(2) index: i32, } struct VertexOutput { @builtin(position) position: vec4, @location(0) tex_coord: vec2, @location(1) index: i32, } @vertex fn vert_main(vertex: VertexInput) -> VertexOutput { var outval: VertexOutput; outval.position = vec4(vertex.position.x, vertex.position.y, 0.0, 1.0); outval.tex_coord = vertex.tex_coord; outval.index = vertex.index; return outval; } struct FragmentInput { @location(0) tex_coord: vec2, @location(1) index: i32, } @group(0) @binding(0) var texture_array_top: binding_array>; @group(0) @binding(1) var texture_array_bottom: binding_array>; @group(0) @binding(2) var sampler_array: binding_array; struct Uniforms { index: u32, } @group(1) @binding(0) var uniforms: Uniforms; @fragment fn uniform_main(fragment: FragmentInput) -> @location(0) vec4 { var outval: vec3; if fragment.tex_coord.y <= 0.5 { outval = textureSampleLevel( texture_array_top[uniforms.index], sampler_array[uniforms.index], fragment.tex_coord, 0.0 ).rgb; } else { outval = textureSampleLevel( texture_array_bottom[uniforms.index], sampler_array[uniforms.index], fragment.tex_coord, 0.0 ).rgb; } return vec4(outval.x, outval.y, outval.z, 1.0); } ================================================ FILE: examples/features/src/texture_arrays/mod.rs ================================================ use bytemuck::{Pod, Zeroable}; use std::num::{NonZeroU32, NonZeroU64}; use wgpu::util::DeviceExt; #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct Vertex { _pos: [f32; 2], _tex_coord: [f32; 2], _index: u32, } fn vertex(pos: [i8; 2], tc: [i8; 2], index: i8) -> Vertex { Vertex { _pos: [pos[0] as f32, pos[1] as f32], _tex_coord: [tc[0] as f32, tc[1] as f32], _index: index as u32, } } fn create_vertices() -> Vec { vec![ // left rectangle vertex([-1, -1], [0, 1], 0), vertex([-1, 1], [0, 0], 0), vertex([0, 1], [1, 0], 0), vertex([0, -1], [1, 1], 0), // right rectangle vertex([0, -1], [0, 1], 1), vertex([0, 1], [0, 0], 1), vertex([1, 1], [1, 0], 1), vertex([1, -1], [1, 1], 1), ] } fn create_indices() -> Vec { vec![ // Left rectangle 0, 1, 2, // 1st 2, 0, 3, // 2nd // Right rectangle 4, 5, 6, // 1st 6, 4, 7, // 2nd ] } #[derive(Copy, Clone)] enum Color { Red, Green, Blue, White, } fn create_texture_data(color: Color) -> [u8; 4] { match color { Color::Red => [255, 0, 0, 255], Color::Green => [0, 255, 0, 255], Color::Blue => [0, 0, 255, 255], Color::White => [255, 255, 255, 255], } } struct Example { pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, uniform_bind_group: wgpu::BindGroup, vertex_buffer: wgpu::Buffer, index_buffer: wgpu::Buffer, index_format: wgpu::IndexFormat, uniform_workaround: bool, } impl crate::framework::Example for Example { fn optional_features() -> wgpu::Features { wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING } fn required_features() -> wgpu::Features { wgpu::Features::TEXTURE_BINDING_ARRAY } fn required_limits() -> wgpu::Limits { wgpu::Limits { max_binding_array_elements_per_shader_stage: 6, max_binding_array_sampler_elements_per_shader_stage: 2, ..wgpu::Limits::downlevel_defaults() } } fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { let mut uniform_workaround = false; let base_shader_module = device.create_shader_module(wgpu::include_wgsl!("indexing.wgsl")); let env_override = match std::env::var("WGPU_TEXTURE_ARRAY_STYLE") { Ok(value) => match &*value.to_lowercase() { "nonuniform" | "non_uniform" => Some(true), "uniform" => Some(false), _ => None, }, Err(_) => None, }; let fragment_entry_point = match (device.features(), env_override) { (_, Some(false)) => { uniform_workaround = true; "uniform_main" } (_, Some(true)) => "non_uniform_main", (f, _) if f.contains( wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, ) => { "non_uniform_main" } _ => { uniform_workaround = true; "uniform_main" } }; let non_uniform_shader_module; // TODO: Because naga's capabilities are evaluated on validate, not on write, we cannot make a shader module with unsupported // capabilities even if we don't use it. So for now put it in a separate module. let fragment_shader_module = if !uniform_workaround { non_uniform_shader_module = device.create_shader_module(wgpu::include_wgsl!("non_uniform_indexing.wgsl")); &non_uniform_shader_module } else { &base_shader_module }; println!("Using fragment entry point '{fragment_entry_point}'"); let vertex_size = size_of::(); let vertex_data = create_vertices(); let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::cast_slice(&vertex_data), usage: wgpu::BufferUsages::VERTEX, }); let index_data = create_indices(); let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Index Buffer"), contents: bytemuck::cast_slice(&index_data), usage: wgpu::BufferUsages::INDEX, }); let mut texture_index_buffer_contents = vec![0u32; 128]; texture_index_buffer_contents[0] = 0; texture_index_buffer_contents[64] = 1; let texture_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Index Buffer"), contents: bytemuck::cast_slice(&texture_index_buffer_contents), usage: wgpu::BufferUsages::UNIFORM, }); let red_texture_data = create_texture_data(Color::Red); let green_texture_data = create_texture_data(Color::Green); let blue_texture_data = create_texture_data(Color::Blue); let white_texture_data = create_texture_data(Color::White); let texture_descriptor = wgpu::TextureDescriptor { size: wgpu::Extent3d::default(), mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, label: None, view_formats: &[], }; let red_texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("red"), view_formats: &[], ..texture_descriptor }); let green_texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("green"), view_formats: &[], ..texture_descriptor }); let blue_texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("blue"), view_formats: &[], ..texture_descriptor }); let white_texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("white"), view_formats: &[], ..texture_descriptor }); let red_texture_view = red_texture.create_view(&wgpu::TextureViewDescriptor::default()); let green_texture_view = green_texture.create_view(&wgpu::TextureViewDescriptor::default()); let blue_texture_view = blue_texture.create_view(&wgpu::TextureViewDescriptor::default()); let white_texture_view = white_texture.create_view(&wgpu::TextureViewDescriptor::default()); queue.write_texture( red_texture.as_image_copy(), &red_texture_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(4), rows_per_image: None, }, wgpu::Extent3d::default(), ); queue.write_texture( green_texture.as_image_copy(), &green_texture_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(4), rows_per_image: None, }, wgpu::Extent3d::default(), ); queue.write_texture( blue_texture.as_image_copy(), &blue_texture_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(4), rows_per_image: None, }, wgpu::Extent3d::default(), ); queue.write_texture( white_texture.as_image_copy(), &white_texture_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(4), rows_per_image: None, }, wgpu::Extent3d::default(), ); let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("bind group layout"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: NonZeroU32::new(2), }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: NonZeroU32::new(2), }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: NonZeroU32::new(2), }, ], }); let uniform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("uniform bind group layout"), entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: Some(NonZeroU64::new(4).unwrap()), }, count: None, }], }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureViewArray(&[ &red_texture_view, &green_texture_view, ]), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureViewArray(&[ &blue_texture_view, &white_texture_view, ]), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::SamplerArray(&[&sampler, &sampler]), }, ], layout: &bind_group_layout, label: Some("bind group"), }); let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &texture_index_buffer, offset: 0, size: Some(NonZeroU64::new(4).unwrap()), }), }], layout: &uniform_bind_group_layout, label: Some("uniform bind group"), }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("main"), bind_group_layouts: &[Some(&bind_group_layout), Some(&uniform_bind_group_layout)], immediate_size: 0, }); let index_format = wgpu::IndexFormat::Uint16; let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &base_shader_module, entry_point: Some("vert_main"), compilation_options: Default::default(), buffers: &[wgpu::VertexBufferLayout { array_stride: vertex_size as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Sint32], }], }, fragment: Some(wgpu::FragmentState { module: fragment_shader_module, entry_point: Some(fragment_entry_point), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), primitive: wgpu::PrimitiveState { front_face: wgpu::FrontFace::Ccw, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None }); Self { pipeline, bind_group, uniform_bind_group, vertex_buffer, index_buffer, index_format, uniform_workaround, } } fn resize( &mut self, _sc_desc: &wgpu::SurfaceConfiguration, _device: &wgpu::Device, _queue: &wgpu::Queue, ) { // noop } fn update(&mut self, _event: winit::event::WindowEvent) { // noop } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("primary"), }); let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.pipeline); rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); rpass.set_index_buffer(self.index_buffer.slice(..), self.index_format); if self.uniform_workaround { rpass.set_bind_group(0, &self.bind_group, &[]); rpass.set_bind_group(1, &self.uniform_bind_group, &[0]); rpass.draw_indexed(0..6, 0, 0..1); rpass.set_bind_group(1, &self.uniform_bind_group, &[256]); rpass.draw_indexed(6..12, 0, 0..1); } else { rpass.set_bind_group(0, &self.bind_group, &[]); rpass.set_bind_group(1, &self.uniform_bind_group, &[0]); rpass.draw_indexed(0..12, 0, 0..1); } drop(rpass); queue.submit(Some(encoder.finish())); } } pub fn main() { crate::framework::run::("texture-arrays"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "texture-arrays", image_path: "/examples/features/src/texture_arrays/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::empty(), base_test_parameters: wgpu_test::TestParameters::default() .instance_flags(wgpu::InstanceFlags::GPU_BASED_VALIDATION) // https://github.com/gfx-rs/wgpu/issues/9184 .expect_fail( wgpu_test::FailureCase::molten_vk() .validation_error("Shader library compile failed") .validation_error("could not be compiled into pipeline"), ), comparisons: &[wgpu_test::ComparisonType::Mean(0.0001)], _phantom: std::marker::PhantomData::, }; #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST_UNIFORM: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "texture-arrays-uniform", image_path: "/examples/features/src/texture_arrays/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::empty(), base_test_parameters: wgpu_test::TestParameters::default() .instance_flags(wgpu::InstanceFlags::GPU_BASED_VALIDATION) // https://github.com/gfx-rs/wgpu/issues/9184 .expect_fail( wgpu_test::FailureCase::molten_vk() .validation_error("Shader library compile failed") .validation_error("could not be compiled into pipeline"), ), comparisons: &[wgpu_test::ComparisonType::Mean(0.0001)], _phantom: std::marker::PhantomData::, }; #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST_NON_UNIFORM: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "texture-arrays-non-uniform", image_path: "/examples/features/src/texture_arrays/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, base_test_parameters: wgpu_test::TestParameters::default() .instance_flags(wgpu::InstanceFlags::GPU_BASED_VALIDATION) // https://github.com/gfx-rs/wgpu/issues/9184 .expect_fail( wgpu_test::FailureCase::molten_vk() .validation_error("Shader library compile failed") .validation_error("could not be compiled into pipeline"), ), comparisons: &[wgpu_test::ComparisonType::Mean(0.0001)], _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/texture_arrays/non_uniform_indexing.wgsl ================================================ struct FragmentInput { @location(0) tex_coord: vec2, @location(1) index: i32, } @group(0) @binding(0) var texture_array_top: binding_array>; @group(0) @binding(1) var texture_array_bottom: binding_array>; @group(0) @binding(2) var sampler_array: binding_array; @fragment fn non_uniform_main(fragment: FragmentInput) -> @location(0) vec4 { var outval: vec3; if fragment.tex_coord.y <= 0.5 { outval = textureSampleLevel( texture_array_top[fragment.index], sampler_array[fragment.index], fragment.tex_coord, 0.0 ).rgb; } else { outval = textureSampleLevel( texture_array_bottom[fragment.index], sampler_array[fragment.index], fragment.tex_coord, 0.0 ).rgb; } return vec4(outval.x, outval.y, outval.z, 1.0); } ================================================ FILE: examples/features/src/timestamp_queries/README.md ================================================ # timestamp_queries This example shows various ways of querying time when supported. ## To Run ``` cargo run --bin wgpu-examples timestamp_queries ``` ================================================ FILE: examples/features/src/timestamp_queries/mod.rs ================================================ //! Sample demonstrating different kinds of gpu timestamp queries. //! //! Timestamp queries are typically used to profile how long certain operations take on the GPU. //! wgpu has several ways of performing gpu timestamp queries: //! * passing `wgpu::RenderPassTimestampWrites`/`wgpu::ComputePassTimestampWrites` during render/compute pass creation. //! This writes timestamps for the beginning and end of a given pass. //! (enabled with wgpu::Features::TIMESTAMP_QUERY) //! * `wgpu::CommandEncoder::write_timestamp` writes a timestamp between any commands recorded on an encoder. //! (enabled with wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS) //! * `wgpu::RenderPass/ComputePass::write_timestamp` writes a timestamp within commands of a render pass. //! Note that some GPU architectures do not support this. //! (native only, enabled with wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES) //! //! Any timestamp is written to a `wgpu::QuerySet` which needs to be resolved to a buffer with `wgpu::BufferUsages::QUERY_RESOLVE`. //! Since this usage is incompatible with `wgpu::BufferUsages::MAP_READ` we need to copy the resolved timestamps to a separate buffer afterwards. //! //! The period, i.e. the unit of time, of the timestamps in wgpu is undetermined and needs to be queried with `wgpu::Queue::get_timestamp_period` //! in order to get comparable results. use wgpu::util::DeviceExt; struct Queries { set: wgpu::QuerySet, resolve_buffer: wgpu::Buffer, destination_buffer: wgpu::Buffer, num_queries: u64, next_unused_query: u32, } struct QueryResults { encoder_timestamps: [u64; 2], render_start_end_timestamps: [u64; 2], render_inside_timestamp: Option, compute_start_end_timestamps: [u64; 2], compute_inside_timestamp: Option, } impl QueryResults { // Queries: // * encoder timestamp start // * encoder timestamp end // * render start // * render in-between (optional) // * render end // * compute start // * compute in-between (optional) // * compute end const NUM_QUERIES: u64 = 8; #[expect( clippy::redundant_closure, reason = "false positive for `get_next_slot`, which needs to be used by reference" )] fn from_raw_results(timestamps: Vec, timestamps_inside_passes: bool) -> Self { assert_eq!(timestamps.len(), Self::NUM_QUERIES as usize); let mut next_slot = 0; let mut get_next_slot = || { let slot = timestamps[next_slot]; next_slot += 1; slot }; let mut encoder_timestamps = [0, 0]; encoder_timestamps[0] = get_next_slot(); let render_start_end_timestamps = [get_next_slot(), get_next_slot()]; let render_inside_timestamp = timestamps_inside_passes.then(|| get_next_slot()); let compute_start_end_timestamps = [get_next_slot(), get_next_slot()]; let compute_inside_timestamp = timestamps_inside_passes.then(|| get_next_slot()); encoder_timestamps[1] = get_next_slot(); QueryResults { encoder_timestamps, render_start_end_timestamps, render_inside_timestamp, compute_start_end_timestamps, compute_inside_timestamp, } } fn print(&self, queue: &wgpu::Queue) { let period = queue.get_timestamp_period(); let elapsed_us = |start, end: u64| end.wrapping_sub(start) as f64 * period as f64 / 1000.0; println!( "Elapsed time before render until after compute: {:.2} μs", elapsed_us(self.encoder_timestamps[0], self.encoder_timestamps[1]), ); println!( "Elapsed time render pass: {:.2} μs", elapsed_us( self.render_start_end_timestamps[0], self.render_start_end_timestamps[1] ) ); if let Some(timestamp) = self.render_inside_timestamp { println!( "Elapsed time first triangle: {:.2} μs", elapsed_us(self.render_start_end_timestamps[0], timestamp) ); } println!( "Elapsed time compute pass: {:.2} μs", elapsed_us( self.compute_start_end_timestamps[0], self.compute_start_end_timestamps[1] ) ); if let Some(timestamp) = self.compute_inside_timestamp { println!( "Elapsed time after first dispatch: {:.2} μs", elapsed_us(self.compute_start_end_timestamps[0], timestamp) ); } } } impl Queries { fn new(device: &wgpu::Device, num_queries: u64) -> Self { Queries { set: device.create_query_set(&wgpu::QuerySetDescriptor { label: Some("Timestamp query set"), count: num_queries as _, ty: wgpu::QueryType::Timestamp, }), resolve_buffer: device.create_buffer(&wgpu::BufferDescriptor { label: Some("query resolve buffer"), size: size_of::() as u64 * num_queries, usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::QUERY_RESOLVE, mapped_at_creation: false, }), destination_buffer: device.create_buffer(&wgpu::BufferDescriptor { label: Some("query dest buffer"), size: size_of::() as u64 * num_queries, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false, }), num_queries, next_unused_query: 0, } } fn resolve(&self, encoder: &mut wgpu::CommandEncoder) { encoder.resolve_query_set( &self.set, // TODO(https://github.com/gfx-rs/wgpu/issues/3993): Musn't be larger than the number valid queries in the set. 0..self.next_unused_query, &self.resolve_buffer, 0, ); encoder.copy_buffer_to_buffer( &self.resolve_buffer, 0, &self.destination_buffer, 0, self.resolve_buffer.size(), ); } fn wait_for_results(&self, device: &wgpu::Device, is_test_on_metal: bool) -> Vec { self.destination_buffer .slice(..) .map_async(wgpu::MapMode::Read, |_| ()); let poll_type = if is_test_on_metal { // Use a short timeout because the `timestamps_encoder` test (which // is also marked as flaky) has been observed to hang on Metal. // // Note that a timeout here is *not* considered an error. In this // particular case that is what we want, but in general, waits in // tests should probably treat a timeout as an error. wgpu::PollType::Wait { submission_index: None, timeout: Some(std::time::Duration::from_secs(5)), } } else { wgpu::PollType::wait_indefinitely() }; device.poll(poll_type).unwrap(); let timestamps = { let timestamp_view = self .destination_buffer .slice(..(size_of::() as wgpu::BufferAddress * self.num_queries)) .get_mapped_range(); bytemuck::cast_slice(×tamp_view).to_vec() }; self.destination_buffer.unmap(); timestamps } } async fn run() { // Instantiates instance of wgpu let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env()); // `request_adapter` instantiates the general connection to the GPU let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions::default()) .await .expect("Failed to request adapter."); // Check timestamp features. let features = adapter.features() & (wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES); if features.contains(wgpu::Features::TIMESTAMP_QUERY) { println!("Adapter supports timestamp queries."); } else { println!("Adapter does not support timestamp queries, aborting."); return; } let timestamps_inside_passes = features.contains(wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES); if timestamps_inside_passes { println!("Adapter supports timestamp queries within passes."); } else { println!("Adapter does not support timestamp queries within passes."); } // `request_device` instantiates the feature specific connection to the GPU, defining some parameters, // `features` being the available features. let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: None, required_features: features, required_limits: wgpu::Limits::downlevel_defaults(), experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) .await .unwrap(); let queries = submit_render_and_compute_pass_with_queries(&device, &queue); let raw_results = queries.wait_for_results(&device, false); println!("Raw timestamp buffer contents: {raw_results:?}"); QueryResults::from_raw_results(raw_results, timestamps_inside_passes).print(&queue); } fn submit_render_and_compute_pass_with_queries( device: &wgpu::Device, queue: &wgpu::Queue, ) -> Queries { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); let mut queries = Queries::new(device, QueryResults::NUM_QUERIES); let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); if device .features() .contains(wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS) { encoder.write_timestamp(&queries.set, queries.next_unused_query); queries.next_unused_query += 1; } // Render two triangles and profile it. render_pass( device, &shader, &mut encoder, &queries.set, &mut queries.next_unused_query, ); // Compute a hash function on a single thread a bunch of time and profile it. compute_pass( device, &shader, &mut encoder, &queries.set, &mut queries.next_unused_query, ); if device .features() .contains(wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS) { encoder.write_timestamp(&queries.set, queries.next_unused_query); queries.next_unused_query += 1; } queries.resolve(&mut encoder); queue.submit(Some(encoder.finish())); queries } fn compute_pass( device: &wgpu::Device, module: &wgpu::ShaderModule, encoder: &mut wgpu::CommandEncoder, query_set: &wgpu::QuerySet, next_unused_query: &mut u32, ) { let storage_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Storage Buffer"), contents: bytemuck::cast_slice(&[42]), usage: wgpu::BufferUsages::STORAGE, }); let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: None, module, entry_point: Some("main_cs"), compilation_options: Default::default(), cache: None, }); let bind_group_layout = compute_pipeline.get_bind_group_layout(0); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: storage_buffer.as_entire_binding(), }], }); let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: Some(wgpu::ComputePassTimestampWrites { query_set, beginning_of_pass_write_index: Some(*next_unused_query), end_of_pass_write_index: Some(*next_unused_query + 1), }), }); *next_unused_query += 2; cpass.set_pipeline(&compute_pipeline); cpass.set_bind_group(0, &bind_group, &[]); cpass.dispatch_workgroups(1, 1, 1); if device .features() .contains(wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES) { cpass.write_timestamp(query_set, *next_unused_query); *next_unused_query += 1; } cpass.dispatch_workgroups(1, 1, 1); } fn render_pass( device: &wgpu::Device, module: &wgpu::ShaderModule, encoder: &mut wgpu::CommandEncoder, query_set: &wgpu::QuerySet, next_unused_query: &mut u32, ) { let format = wgpu::TextureFormat::Rgba8Unorm; let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[], immediate_size: 0, }); let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(format.into())], }), primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let render_target = device.create_texture(&wgpu::TextureDescriptor { label: Some("rendertarget"), size: wgpu::Extent3d { width: 512, height: 512, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[format], }); let render_target_view = render_target.create_view(&wgpu::TextureViewDescriptor::default()); let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &render_target_view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: Some(wgpu::RenderPassTimestampWrites { query_set, beginning_of_pass_write_index: Some(*next_unused_query), end_of_pass_write_index: Some(*next_unused_query + 1), }), occlusion_query_set: None, multiview_mask: None, }); *next_unused_query += 2; rpass.set_pipeline(&render_pipeline); rpass.draw(0..3, 0..1); if device .features() .contains(wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES) { rpass.write_timestamp(query_set, *next_unused_query); *next_unused_query += 1; } rpass.draw(0..3, 0..1); } pub fn main() { #[cfg(not(target_arch = "wasm32"))] { env_logger::init(); pollster::block_on(run()); } #[cfg(target_arch = "wasm32")] { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); console_log::init().expect("could not initialize logger"); wasm_bindgen_futures::spawn_local(run()); } } #[cfg(test)] pub mod tests { use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration}; use super::{submit_render_and_compute_pass_with_queries, QueryResults}; #[gpu_test] pub static TIMESTAMPS_PASS_BOUNDARIES: GpuTestConfiguration = GpuTestConfiguration::new() .parameters( wgpu_test::TestParameters::default() .limits(wgpu::Limits::downlevel_defaults()) .features(wgpu::Features::TIMESTAMP_QUERY), ) .run_sync(|ctx| test_timestamps(ctx, false, false)); #[gpu_test] pub static TIMESTAMPS_ENCODER: GpuTestConfiguration = GpuTestConfiguration::new() .parameters( wgpu_test::TestParameters::default() .limits(wgpu::Limits::downlevel_defaults()) .features( wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS, ) // see https://github.com/gfx-rs/wgpu/issues/2521 // If marking this test non-flaky, also consider removing the silent // timeout in `wait_for_results`. .expect_fail(FailureCase::always().panic("unexpected timestamp").flaky()), ) .run_sync(|ctx| test_timestamps(ctx, true, false)); #[gpu_test] pub static TIMESTAMPS_PASSES: GpuTestConfiguration = GpuTestConfiguration::new() .parameters( wgpu_test::TestParameters::default() .limits(wgpu::Limits::downlevel_defaults()) .features( wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS | wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES, ) // see https://github.com/gfx-rs/wgpu/issues/2521 // If marking this test non-flaky, also consider removing the silent // timeout in `wait_for_results`. .expect_fail(FailureCase::always().panic("unexpected timestamp").flaky()), ) .run_sync(|ctx| test_timestamps(ctx, true, true)); fn test_timestamps( ctx: wgpu_test::TestingContext, timestamps_on_encoder: bool, timestamps_inside_passes: bool, ) { let is_metal = ctx.adapter.get_info().backend == wgpu::Backend::Metal; let queries = submit_render_and_compute_pass_with_queries(&ctx.device, &ctx.queue); let raw_results = queries.wait_for_results(&ctx.device, is_metal); let QueryResults { encoder_timestamps, render_start_end_timestamps, render_inside_timestamp, compute_start_end_timestamps, compute_inside_timestamp, } = QueryResults::from_raw_results(raw_results, timestamps_inside_passes); // Timestamps may wrap around, so can't really only reason about deltas! // Making things worse, deltas are allowed to be zero. let render_delta = render_start_end_timestamps[1].wrapping_sub(render_start_end_timestamps[0]); let compute_delta = compute_start_end_timestamps[1].wrapping_sub(compute_start_end_timestamps[0]); let encoder_delta = encoder_timestamps[1].wrapping_sub(encoder_timestamps[0]); if timestamps_on_encoder { assert!(encoder_delta > 0, "unexpected timestamp"); assert!( encoder_delta >= render_delta + compute_delta, "unexpected timestamp" ); } if let Some(render_inside_timestamp) = render_inside_timestamp { assert!( render_inside_timestamp >= render_start_end_timestamps[0], "unexpected timestamp" ); assert!( render_inside_timestamp <= render_start_end_timestamps[1], "unexpected timestamp" ); } if let Some(compute_inside_timestamp) = compute_inside_timestamp { assert!( compute_inside_timestamp >= compute_start_end_timestamps[0], "unexpected timestamp" ); assert!( compute_inside_timestamp <= compute_start_end_timestamps[1], "unexpected timestamp" ); } } } ================================================ FILE: examples/features/src/timestamp_queries/shader.wgsl ================================================ @vertex fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { let x = f32(i32(in_vertex_index) - 1); let y = f32(i32(in_vertex_index & 1u) * 2 - 1); return vec4(x, y, 0.0, 1.0); } @fragment fn fs_main() -> @location(0) vec4 { return vec4(1.0, 0.0, 0.0, 1.0); } @group(0) @binding(0) var buffer: array; // Used as both input and output for convenience. fn pcg_hash(input: u32) -> u32 { let state = input * 747796405u + 2891336453u; let word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; return (word >> 22u) ^ word; } @compute @workgroup_size(1) fn main_cs(@builtin(global_invocation_id) global_id: vec3) { var value = buffer[0]; for (var i = 0u; i < 128u; i += 1u) { value = pcg_hash(value); } buffer[0] = value; } ================================================ FILE: examples/features/src/uniform_values/README.md ================================================ # uniform_values Creates a window which displays a grayscale render of the [Mandelbrot set](https://en.wikipedia.org/wiki/Mandelbrot_set). Pressing the arrow keys will translate the set and scrolling the mouse wheel will zoom in and out. If the image appears too 'bright', it may be because you are using too few iterations or 'samples'. Use U and D to increase or decrease respectively the max number of iterations used. Make sure to play around with this too to get an optimally photogenic screen cap. The window can be resized and pressing ESC will close the window. Explore the Mandelbrot set using the power of uniform variables to transfer state from the main program to the shader! ## To Run ``` cargo run --bin wgpu-examples uniform_values ``` ## Usage of Uniform Buffers / Variables Since the codebase of this example is so large (because why not demonstrate with a sort-of game) and the points of interest in terms of the actual point of the example so small, there is a module doc comment at the top of main.rs that points out the important points of the usage of uniform values. ## Limitations At some point in exploring the fractal, you may discover there is actually a resolution; if you zoom to deep, things become weirdly pixilated. Unfortunately, the relatively basic shader is currently limited by the faults of 32-bit floating point precision. As much as I'd like to upgrade to 64-bit floats, the support in WGSL for f64's is limited and you can't even cast to one as of time of writing. Still pretty cool though. ## Screenshots ![On load](screenshot1.png) ![Zoomed in](screenshot2.png) ![A different part zoomed in](screenshot3.png) ================================================ FILE: examples/features/src/uniform_values/mod.rs ================================================ //! Points of interest for seeing uniforms in action: //! //! 1. the struct for the data stored in the uniform buffer is defined. //! 2. the uniform buffer itself is created. //! 3. the bind group that will bind the uniform buffer and it's layout are created. //! 4. the bind group layout is attached to the pipeline layout. //! 5. the uniform buffer and the bind group are stored alongside the pipeline. //! 6. an instance of `AppState` is created. This variable will be modified //! to change parameters in the shader and modified by app events to preform and save //! those changes. //! 7. (7a and 7b) the `state` variable created at (6) is modified by commands such //! as pressing the arrow keys or zooming in or out. //! 8. the contents of the `AppState` are loaded into the uniform buffer in preparation. //! 9. the bind group with the uniform buffer is attached to the render pass. //! //! The usage of the uniform buffer within the shader itself is pretty self-explanatory given //! some understanding of WGSL. use std::{future::Future, sync::Arc}; // We won't bring StorageBuffer into scope as that might be too easy to confuse // with actual GPU-allocated wgpu storage buffers. use encase::ShaderType; use wgpu::CurrentSurfaceTexture; use winit::{ application::ApplicationHandler, event::{KeyEvent, WindowEvent}, event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy}, keyboard::{Key, NamedKey}, window::Window, }; const ZOOM_INCREMENT_FACTOR: f32 = 1.1; const CAMERA_POS_INCREMENT_FACTOR: f32 = 0.1; #[cfg(not(target_arch = "wasm32"))] fn spawn(f: impl Future + 'static) { pollster::block_on(f); } #[cfg(target_arch = "wasm32")] fn spawn(f: impl Future + 'static) { wasm_bindgen_futures::spawn_local(f); } // (1) #[derive(Debug, ShaderType)] struct ShaderState { pub cursor_pos: glam::Vec2, pub zoom: f32, pub max_iterations: u32, } impl ShaderState { // Translating Rust structures to WGSL is always tricky and can prove // incredibly difficult to remember all the rules by which WGSL // lays out and formats structs in memory. It is also often extremely // frustrating to debug when things don't go right. // // You may sometimes see structs translated to bytes through // using `#[repr(C)]` on the struct so that the struct has a defined, // guaranteed internal layout and then implementing bytemuck's POD // trait so that one can preform a bitwise cast. There are issues with // this approach though as C's struct layouts aren't always compatible // with WGSL, such as when special WGSL types like vec's and mat's // get involved that have special alignment rules and especially // when the target buffer is going to be used in the uniform memory // space. // // Here though, we use the encase crate which makes translating potentially // complex Rust structs easy through combined use of the [`ShaderType`] trait // / derive macro and the buffer structs which hold data formatted for WGSL // in either the storage or uniform spaces. fn as_wgsl_bytes(&self) -> encase::internal::Result> { let mut buffer = encase::UniformBuffer::new(Vec::new()); buffer.write(self)?; Ok(buffer.into_inner()) } fn translate_view(&mut self, increments: i32, axis: usize) { self.cursor_pos[axis] += CAMERA_POS_INCREMENT_FACTOR * increments as f32 / self.zoom; } fn zoom(&mut self, amount: f32) { self.zoom += ZOOM_INCREMENT_FACTOR * amount * self.zoom.powf(1.02); self.zoom = self.zoom.max(1.1); } } impl Default for ShaderState { fn default() -> Self { ShaderState { cursor_pos: glam::Vec2::ZERO, zoom: 1.0, max_iterations: 50, } } } struct WgpuContext { pub instance: wgpu::Instance, pub window: Arc, pub surface: wgpu::Surface<'static>, pub surface_config: wgpu::SurfaceConfiguration, pub device: wgpu::Device, pub queue: wgpu::Queue, pub pipeline: wgpu::RenderPipeline, pub bind_group: wgpu::BindGroup, pub uniform_buffer: wgpu::Buffer, } impl WgpuContext { async fn new( window: Arc, display_handle: winit::event_loop::OwnedDisplayHandle, ) -> WgpuContext { let size = window.inner_size(); let instance = wgpu::Instance::new( wgpu::InstanceDescriptor::new_with_display_handle_from_env(Box::new(display_handle)), ); let surface = instance.create_surface(window.clone()).unwrap(); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface), force_fallback_adapter: false, }) .await .unwrap(); let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) .await .unwrap(); let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); // (2) let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: size_of::() as u64, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); // (3) let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, count: None, }], }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &uniform_buffer, offset: 0, size: None, }), }], }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, // (4) bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); let swapchain_capabilities = surface.get_capabilities(&adapter); let swapchain_format = swapchain_capabilities.formats[0]; let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(swapchain_format.into())], }), primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }); let surface_config = surface .get_default_config(&adapter, size.width, size.height) .unwrap(); surface.configure(&device, &surface_config); // (5) WgpuContext { instance, window, surface, surface_config, device, queue, pipeline, bind_group, uniform_buffer, } } fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { self.surface_config.width = new_size.width; self.surface_config.height = new_size.height; self.surface.configure(&self.device, &self.surface_config); } } enum UniformAction { Initialized(WgpuContext), } #[expect(clippy::large_enum_variant)] enum RunState { Uninitialized, Loading, Running { wgpu_ctx: WgpuContext, // (6) shader_state: ShaderState, }, } struct App { proxy: EventLoopProxy, window: Option>, state: RunState, } impl App { fn new(event_loop: &EventLoop) -> Self { Self { proxy: event_loop.create_proxy(), window: None, state: RunState::Uninitialized, } } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { if !matches!(self.state, RunState::Uninitialized) { return; } self.state = RunState::Loading; #[cfg_attr( not(target_arch = "wasm32"), expect(unused_mut, reason = "wasm32 re-assigns to specify canvas") )] let mut attributes = Window::default_attributes() .with_title("Remember: Use U/D to change sample count!") .with_inner_size(winit::dpi::LogicalSize::new(900, 900)); #[cfg(target_arch = "wasm32")] { use wasm_bindgen::JsCast; use winit::platform::web::WindowAttributesExtWebSys; let canvas = web_sys::window() .unwrap() .document() .unwrap() .get_element_by_id("canvas") .unwrap() .dyn_into::() .unwrap(); attributes = attributes.with_canvas(Some(canvas)); } let window = Arc::new( event_loop .create_window(attributes) .expect("Failed to create window"), ); self.window = Some(window.clone()); let display_handle = event_loop.owned_display_handle(); let proxy = self.proxy.clone(); spawn(async move { let wgpu_ctx = WgpuContext::new(window, display_handle).await; let _ = proxy.send_event(UniformAction::Initialized(wgpu_ctx)); }); } fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UniformAction) { match event { UniformAction::Initialized(wgpu_ctx) => { self.state = RunState::Running { wgpu_ctx, shader_state: ShaderState::default(), }; if let Some(window) = &self.window { window.request_redraw(); } } } } fn exiting(&mut self, _event_loop: &ActiveEventLoop) { self.state = RunState::Uninitialized; } fn window_event( &mut self, event_loop: &ActiveEventLoop, _window_id: winit::window::WindowId, event: WindowEvent, ) { let RunState::Running { wgpu_ctx, shader_state, } = &mut self.state else { return; }; match event { WindowEvent::CloseRequested => { event_loop.exit(); } WindowEvent::KeyboardInput { event: KeyEvent { logical_key, text, .. }, .. } => { if let Key::Named(key) = logical_key { match key { NamedKey::Escape => event_loop.exit(), NamedKey::ArrowUp => shader_state.translate_view(1, 1), NamedKey::ArrowDown => shader_state.translate_view(-1, 1), NamedKey::ArrowLeft => shader_state.translate_view(-1, 0), NamedKey::ArrowRight => shader_state.translate_view(1, 0), _ => {} } } if let Some(text) = text { if text == "u" { shader_state.max_iterations += 3; } else if text == "d" { shader_state.max_iterations = shader_state.max_iterations.saturating_sub(3); } }; if let Some(window) = &self.window { window.request_redraw(); } } WindowEvent::MouseWheel { delta, .. } => { let change = match delta { winit::event::MouseScrollDelta::LineDelta(_, vertical) => vertical, winit::event::MouseScrollDelta::PixelDelta(pos) => pos.y as f32 / 20.0, }; // (7b) shader_state.zoom(change); if let Some(window) = &self.window { window.request_redraw(); } } WindowEvent::Resized(new_size) => { wgpu_ctx.resize(new_size); if let Some(window) = &self.window { window.request_redraw(); } } WindowEvent::RedrawRequested => { let frame = match wgpu_ctx.surface.get_current_texture() { CurrentSurfaceTexture::Success(frame) => frame, CurrentSurfaceTexture::Timeout | CurrentSurfaceTexture::Occluded => { if let Some(window) = &self.window { window.request_redraw(); } return; } CurrentSurfaceTexture::Suboptimal(_) | CurrentSurfaceTexture::Outdated => { wgpu_ctx .surface .configure(&wgpu_ctx.device, &wgpu_ctx.surface_config); if let Some(window) = &self.window { window.request_redraw(); } return; } CurrentSurfaceTexture::Validation => { unreachable!("No error scope registered, so validation errors will panic") } CurrentSurfaceTexture::Lost => { wgpu_ctx.surface = wgpu_ctx .instance .create_surface(wgpu_ctx.window.clone()) .unwrap(); wgpu_ctx .surface .configure(&wgpu_ctx.device, &wgpu_ctx.surface_config); if let Some(window) = &self.window { window.request_redraw(); } return; } }; let view = frame .texture .create_view(&wgpu::TextureViewDescriptor::default()); // (8) wgpu_ctx.queue.write_buffer( &wgpu_ctx.uniform_buffer, 0, &shader_state.as_wgsl_bytes().expect( "Error in encase translating ShaderState \ struct to WGSL bytes.", ), ); let mut encoder = wgpu_ctx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, occlusion_query_set: None, timestamp_writes: None, multiview_mask: None, }); render_pass.set_pipeline(&wgpu_ctx.pipeline); // (9) render_pass.set_bind_group(0, Some(&wgpu_ctx.bind_group), &[]); render_pass.draw(0..3, 0..1); } wgpu_ctx.queue.submit(Some(encoder.finish())); if let Some(window) = &self.window { window.pre_present_notify(); } frame.present(); } WindowEvent::Occluded(is_occluded) => { if !is_occluded { if let Some(window) = &self.window { window.request_redraw(); } } } _ => {} } } } pub fn main() { cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); console_log::init().expect("could not initialize logger"); } else { env_logger::builder().format_timestamp_nanos().init(); } } let event_loop = EventLoop::with_user_event().build().unwrap(); #[cfg_attr(target_arch = "wasm32", expect(unused_mut))] let mut app = App::new(&event_loop); cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { use winit::platform::web::EventLoopExtWebSys; let document = web_sys::window() .and_then(|win| win.document()) .expect("Failed to get document."); let body = document.body().unwrap(); let controls_text = document .create_element("p") .expect("Failed to create controls text as element."); controls_text.set_inner_html( "Controls:
Up, Down, Left, Right: Move view,
Scroll: Zoom,
U, D: Increase / decrease sample count.", ); body.append_child(&controls_text) .expect("Failed to append controls text to body."); event_loop.spawn_app(app); } else { event_loop.run_app(&mut app).unwrap(); } } } ================================================ FILE: examples/features/src/uniform_values/shader.wgsl ================================================ // Some credit to https://github.com/paulgb/wgsl-playground/tree/main. // We use separate the x and y instead of using a vec2 to avoid wgsl padding. struct AppState { pos_x: f32, pos_y: f32, zoom: f32, max_iterations: u32, } struct VertexInput { @builtin(vertex_index) vertex_index: u32, }; struct VertexOutput { @builtin(position) position: vec4, @location(0) coord: vec2, }; @group(0) @binding(0) var app_state: AppState; @vertex fn vs_main(in: VertexInput) -> VertexOutput { var vertices = array, 3>( vec2(-1., 1.), vec2(3.0, 1.), vec2(-1., -3.0), ); var out: VertexOutput; out.coord = vertices[in.vertex_index]; out.position = vec4(out.coord, 0.0, 1.0); return out; } @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { let max_iterations = app_state.max_iterations; var final_iteration = max_iterations; let c = vec2( // Translated to put everything nicely in frame. (in.coord.x) * 3.0 / app_state.zoom + app_state.pos_x, (in.coord.y) * 3.0 / app_state.zoom + app_state.pos_y ); var current_z = c; var next_z: vec2; for (var i = 0u; i < max_iterations; i++) { next_z.x = (current_z.x * current_z.x - current_z.y * current_z.y) + c.x; next_z.y = (2.0 * current_z.x * current_z.y) + c.y; current_z = next_z; if length(current_z) > 4.0 { final_iteration = i; break; } } let value = f32(final_iteration) / f32(max_iterations); return vec4(value, value, value, 1.0); } ================================================ FILE: examples/features/src/utils.rs ================================================ #[cfg(not(target_arch = "wasm32"))] use std::io::Write; use std::time::Instant; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] fn get_content_div() -> web_sys::Element { web_sys::window() .and_then(|window| window.document()) .and_then(|document| document.get_element_by_id("content")) .expect("Could not get document / content.") } /// Replaces the site body with a message telling the user to open the console and use that. #[cfg(target_arch = "wasm32")] pub fn add_web_nothing_to_see_msg() { get_content_div().set_inner_html( "

This is a compute example, so there's nothing to see here. Open the console!

", ); } /// Outputs a vector of RGBA bytes as a png image with the given dimensions on the given path. #[cfg(not(target_arch = "wasm32"))] pub fn output_image_native(image_data: Vec, texture_dims: (usize, usize), path: String) { let mut png_data = Vec::::with_capacity(image_data.len()); let mut encoder = png::Encoder::new( std::io::Cursor::new(&mut png_data), texture_dims.0 as u32, texture_dims.1 as u32, ); encoder.set_color(png::ColorType::Rgba); let mut png_writer = encoder.write_header().unwrap(); png_writer.write_image_data(&image_data[..]).unwrap(); png_writer.finish().unwrap(); log::info!("PNG file encoded in memory."); let mut file = std::fs::File::create(&path).unwrap(); file.write_all(&png_data[..]).unwrap(); log::info!("PNG file written to disc as \"{path}\"."); } /// Effectively a version of `output_image_native` but meant for web browser contexts. /// /// This is achieved via in `img` element on the page. If the target image element does /// not exist, this function creates one. If it does, the image data is overridden. /// /// This function makes use of a hidden staging canvas which the data is copied to in /// order to create a data URL. #[cfg(target_arch = "wasm32")] pub fn output_image_wasm(image_data: Vec, texture_dims: (usize, usize)) { let document = web_sys::window().unwrap().document().unwrap(); let content_div = get_content_div(); let canvas = if let Some(found_canvas) = document.get_element_by_id("staging-canvas") { match found_canvas.dyn_into::() { Ok(canvas_as_canvas) => canvas_as_canvas, Err(e) => { log::error!( "In searching for a staging canvas for outputting an image \ (element with id \"staging-canvas\"), found non-canvas element: {e:?}. Replacing with standard staging canvas." ); e.remove(); create_staging_canvas(&document) } } } else { log::info!("Output image staging canvas element not found; creating."); create_staging_canvas(&document) }; // Having the size attributes the right size is so important, we should always do it // just to be safe. Also, what if we might want the image size to be able to change? let image_dimension_strings = (texture_dims.0.to_string(), texture_dims.1.to_string()); canvas .set_attribute("width", image_dimension_strings.0.as_str()) .unwrap(); canvas .set_attribute("height", image_dimension_strings.1.as_str()) .unwrap(); let context = canvas .get_context("2d") .unwrap() .unwrap() .dyn_into::() .unwrap(); let image_data = web_sys::ImageData::new_with_u8_clamped_array( wasm_bindgen::Clamped(&image_data), texture_dims.0 as u32, ) .unwrap(); context.put_image_data(&image_data, 0.0, 0.0).unwrap(); // Get the img element that will act as our target for rendering from the canvas. let image_element = if let Some(found_image_element) = document.get_element_by_id("output-image-target") { match found_image_element.dyn_into::() { Ok(e) => e, Err(e) => { log::error!( "Found an element with the id \"output-image-target\" but it was not an image: {e:?}. Replacing with default image output element.", ); e.remove(); create_output_image_element(&document) } } } else { log::info!("Output image element not found; creating."); create_output_image_element(&document) }; // The canvas is currently the image we ultimately want. We can create a data url from it now. let data_url = canvas.to_data_url().unwrap(); image_element.set_src(&data_url); log::info!("Copied image from staging canvas to image element."); if document.get_element_by_id("image-for-you-text").is_none() { log::info!("\"Image for you\" text not found; creating."); let p = document .create_element("p") .expect("Failed to create p element for \"image for you text\"."); p.set_text_content(Some( "The above image is for you! You can drag it to your desktop to download.", )); p.set_id("image-for-you-text"); content_div .append_child(&p) .expect("Failed to append \"image for you text\" to document."); } } #[cfg(target_arch = "wasm32")] fn create_staging_canvas(document: &web_sys::Document) -> web_sys::HtmlCanvasElement { let content_div = get_content_div(); let new_canvas = document .create_element("canvas") .expect("Failed to create staging canvas.") .dyn_into::() .unwrap(); // We don't want to show the canvas, we just want it to exist in the background. new_canvas.set_attribute("hidden", "true").unwrap(); new_canvas.set_attribute("background-color", "red").unwrap(); content_div.append_child(&new_canvas).unwrap(); log::info!("Created new staging canvas: {:?}", &new_canvas); new_canvas } #[cfg(target_arch = "wasm32")] fn create_output_image_element(document: &web_sys::Document) -> web_sys::HtmlImageElement { let content_div = get_content_div(); let new_image = document .create_element("img") .expect("Failed to create output image element.") .dyn_into::() .unwrap(); new_image.set_id("output-image-target"); content_div.replace_children_with_node_1(&new_image); log::info!("Created new output target image: {:?}", &new_image); new_image } #[cfg(not(target_arch = "wasm32"))] /// If the environment variable `WGPU_ADAPTER_NAME` is set, this function will attempt to /// initialize the adapter with that name. If it is not set, it will attempt to initialize /// the adapter which supports the required features. pub(crate) async fn get_adapter_with_capabilities_or_from_env( instance: &wgpu::Instance, required_features: &wgpu::Features, required_downlevel_capabilities: &wgpu::DownlevelCapabilities, surface: &Option<&wgpu::Surface<'_>>, ) -> wgpu::Adapter { use wgpu::Backends; if std::env::var("WGPU_ADAPTER_NAME").is_ok() { let adapter = wgpu::util::initialize_adapter_from_env_or_default(instance, *surface) .await .expect("No suitable GPU adapters found on the system!"); let adapter_info = adapter.get_info(); log::info!("Using {} ({:?})", adapter_info.name, adapter_info.backend); let adapter_features = adapter.features(); assert!( adapter_features.contains(*required_features), "Adapter does not support required features for this example: {:?}", *required_features - adapter_features ); let downlevel_capabilities = adapter.get_downlevel_capabilities(); assert!( downlevel_capabilities.shader_model >= required_downlevel_capabilities.shader_model, "Adapter does not support the minimum shader model required to run this example: {:?}", required_downlevel_capabilities.shader_model ); assert!( downlevel_capabilities .flags .contains(required_downlevel_capabilities.flags), "Adapter does not support the downlevel capabilities required to run this example: {:?}", required_downlevel_capabilities.flags - downlevel_capabilities.flags ); adapter } else { let adapters = instance.enumerate_adapters(Backends::all()).await; let mut chosen_adapter = None; for adapter in adapters { if let Some(surface) = surface { if !adapter.is_surface_supported(surface) { continue; } } let required_features = *required_features; let adapter_features = adapter.features(); if !adapter_features.contains(required_features) { continue; } else { chosen_adapter = Some(adapter); break; } } chosen_adapter.expect("No suitable GPU adapters found on the system!") } } #[cfg(target_arch = "wasm32")] pub(crate) async fn get_adapter_with_capabilities_or_from_env( instance: &wgpu::Instance, required_features: &wgpu::Features, required_downlevel_capabilities: &wgpu::DownlevelCapabilities, surface: &Option<&wgpu::Surface<'_>>, ) -> wgpu::Adapter { let adapter = wgpu::util::initialize_adapter_from_env_or_default(instance, *surface) .await .expect("No suitable GPU adapters found on the system!"); let adapter_info = adapter.get_info(); log::info!("Using {} ({:?})", adapter_info.name, adapter_info.backend); let adapter_features = adapter.features(); assert!( adapter_features.contains(*required_features), "Adapter does not support required features for this example: {:?}", *required_features - adapter_features ); let downlevel_capabilities = adapter.get_downlevel_capabilities(); assert!( downlevel_capabilities.shader_model >= required_downlevel_capabilities.shader_model, "Adapter does not support the minimum shader model required to run this example: {:?}", required_downlevel_capabilities.shader_model ); assert!( downlevel_capabilities .flags .contains(required_downlevel_capabilities.flags), "Adapter does not support the downlevel capabilities required to run this example: {:?}", required_downlevel_capabilities.flags - downlevel_capabilities.flags ); adapter } /// A custom timer that only starts counting after the first call to get its time value. /// Useful because some examples have animations that would otherwise get started at initialization /// leading to random CI fails. #[derive(Default)] pub struct AnimationTimer { start_time: Option, } impl AnimationTimer { pub fn time(&mut self) -> f32 { match self.start_time { None => { self.start_time = Some(Instant::now()); 0.0 } Some(ref instant) => instant.elapsed().as_secs_f32(), } } } ================================================ FILE: examples/features/src/water/README.md ================================================ # Water example This example renders animated water. It demonstrates Read only Depth/Stencil (abbreviated RODS), where a depth/stencil buffer is used as an attachment which is read-only. In this case it's used in the shaders to calculate reflections and depth. ## Files: ``` water ├── main.rs ------------------ Main program ├── point_gen.rs ------------- Hexagon point generation ├── README.md ---------------- This readme ├── screenshot.png ----------- Screenshot ├── terrain.wgsl ------------- WGSL Shader for terrain └── water.wgsl --------------- WGSL Shader for water ``` ## To run ``` cargo run --bin wgpu-examples water ``` ## Screenshot ![Water example](./screenshot.png) ================================================ FILE: examples/features/src/water/mod.rs ================================================ mod point_gen; use bytemuck::{Pod, Zeroable}; use glam::Vec3; use nanorand::{Rng, WyRand}; use std::{f32::consts, iter}; use wgpu::util::DeviceExt; /// /// Radius of the terrain. /// /// Changing this value will change the size of the /// water and terrain. Note however, that changes to /// this value will require modification of the time /// scale in the `render` method below. /// const SIZE: f32 = 29.0; /// /// Location of the camera. /// Location of light is in terrain/water shaders. /// const CAMERA: Vec3 = glam::Vec3::new(-200.0, 70.0, 200.0); struct Matrices { view: glam::Mat4, flipped_view: glam::Mat4, projection: glam::Mat4, } #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)] struct TerrainUniforms { view_projection: [f32; 16], clipping_plane: [f32; 4], } #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)] struct WaterUniforms { view: [f32; 16], projection: [f32; 16], time_size_width: [f32; 4], height: [f32; 4], } struct Uniforms { terrain_normal: TerrainUniforms, terrain_flipped: TerrainUniforms, water: WaterUniforms, } struct Example { water_vertex_buf: wgpu::Buffer, water_vertex_count: usize, water_bind_group_layout: wgpu::BindGroupLayout, water_bind_group: wgpu::BindGroup, water_uniform_buf: wgpu::Buffer, water_pipeline: wgpu::RenderPipeline, terrain_vertex_buf: wgpu::Buffer, terrain_vertex_count: usize, terrain_normal_bind_group: wgpu::BindGroup, terrain_normal_uniform_buf: wgpu::Buffer, /// /// Contains uniform variables where the camera /// has been placed underwater. /// terrain_flipped_uniform_buf: wgpu::Buffer, terrain_pipeline: wgpu::RenderPipeline, /// A render bundle for drawing the terrain. /// /// This isn't really necessary, but it does make sure we have at /// least one use of `RenderBundleEncoder::set_bind_group` among /// the examples. terrain_bundle: wgpu::RenderBundle, reflect_view: wgpu::TextureView, depth_buffer: wgpu::TextureView, current_frame: usize, /// /// Used to prevent issues when rendering after /// minimizing the window. /// active: Option, } impl Example { /// /// Creates the view matrices, and the corrected projection matrix. /// fn generate_matrices(aspect_ratio: f32) -> Matrices { let projection = glam::Mat4::perspective_rh(consts::FRAC_PI_4, aspect_ratio, 10.0, 400.0); let reg_view = glam::Mat4::look_at_rh( CAMERA, glam::Vec3::new(0f32, 0.0, 0.0), glam::Vec3::Y, //Note that y is up. Differs from other examples. ); let scale = glam::Mat4::from_scale(glam::Vec3::new(8.0, 1.5, 8.0)); let reg_view = reg_view * scale; let flipped_view = glam::Mat4::look_at_rh( glam::Vec3::new(CAMERA.x, -CAMERA.y, CAMERA.z), glam::Vec3::ZERO, glam::Vec3::Y, ); let flipped_view = flipped_view * scale; Matrices { view: reg_view, flipped_view, projection, } } fn generate_uniforms(width: u32, height: u32) -> Uniforms { let Matrices { view, flipped_view, projection, } = Self::generate_matrices(width as f32 / height as f32); Uniforms { terrain_normal: TerrainUniforms { view_projection: *(projection * view).as_ref(), clipping_plane: [0.0; 4], }, terrain_flipped: TerrainUniforms { view_projection: *(projection * flipped_view).as_ref(), clipping_plane: [0., 1., 0., 0.], }, water: WaterUniforms { view: *view.as_ref(), projection: *projection.as_ref(), time_size_width: [0.0, 1.0, SIZE * 2.0, width as f32], height: [height as f32, 0.0, 0.0, 0.0], }, } } /// /// Initializes Uniforms and textures. /// fn initialize_resources( config: &wgpu::SurfaceConfiguration, device: &wgpu::Device, queue: &wgpu::Queue, water_uniforms: &wgpu::Buffer, terrain_normal_uniforms: &wgpu::Buffer, terrain_flipped_uniforms: &wgpu::Buffer, water_bind_group_layout: &wgpu::BindGroupLayout, ) -> (wgpu::TextureView, wgpu::TextureView, wgpu::BindGroup) { // Matrices for our projection and view. // flipped_view is the view from under the water. let Uniforms { terrain_normal, terrain_flipped, water, } = Self::generate_uniforms(config.width, config.height); // Put the uniforms into buffers on the GPU queue.write_buffer( terrain_normal_uniforms, 0, bytemuck::cast_slice(&[terrain_normal]), ); queue.write_buffer( terrain_flipped_uniforms, 0, bytemuck::cast_slice(&[terrain_flipped]), ); queue.write_buffer(water_uniforms, 0, bytemuck::cast_slice(&[water])); let texture_extent = wgpu::Extent3d { width: config.width, height: config.height, depth_or_array_layers: 1, }; let reflection_texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("Reflection Render Texture"), size: texture_extent, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: config.view_formats[0], usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }); let draw_depth_buffer = device.create_texture(&wgpu::TextureDescriptor { label: Some("Depth Buffer"), size: texture_extent, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Depth32Float, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }); let color_sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("Color Sampler"), address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Linear, mipmap_filter: wgpu::MipmapFilterMode::Nearest, ..Default::default() }); let depth_sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("Depth Sampler"), ..Default::default() }); let depth_view = draw_depth_buffer.create_view(&wgpu::TextureViewDescriptor::default()); let water_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: water_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: water_uniforms.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView( &reflection_texture.create_view(&wgpu::TextureViewDescriptor::default()), ), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&depth_view), }, wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&color_sampler), }, wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::Sampler(&depth_sampler), }, ], label: Some("Water Bind Group"), }); ( reflection_texture.create_view(&wgpu::TextureViewDescriptor::default()), depth_view, water_bind_group, ) } } impl crate::framework::Example for Example { fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, device: &wgpu::Device, queue: &wgpu::Queue, ) -> Self { // Size of one water vertex let water_vertex_size = size_of::(); let water_vertices = point_gen::HexWaterMesh::generate(SIZE).generate_points(); // Size of one terrain vertex let terrain_vertex_size = size_of::(); // Noise generation let terrain_noise = noise::OpenSimplex::default(); // Random colouration let mut terrain_random = WyRand::new_seed(42); // Generate terrain. The closure determines what each hexagon will look like. let terrain = point_gen::HexTerrainMesh::generate(SIZE, |point| -> point_gen::TerrainVertex { use noise::NoiseFn; let noise = terrain_noise.get([point[0] as f64 / 5.0, point[1] as f64 / 5.0]) + 0.1; let y = noise as f32 * 22.0; // Multiplies a colour by some random amount. fn mul_arr(mut arr: [u8; 4], by: f32) -> [u8; 4] { arr[0] = (arr[0] as f32 * by).min(255.0) as u8; arr[1] = (arr[1] as f32 * by).min(255.0) as u8; arr[2] = (arr[2] as f32 * by).min(255.0) as u8; arr } // Under water const DARK_SAND: [u8; 4] = [235, 175, 71, 255]; // Coast const SAND: [u8; 4] = [217, 191, 76, 255]; // Normal const GRASS: [u8; 4] = [122, 170, 19, 255]; // Mountain const SNOW: [u8; 4] = [175, 224, 237, 255]; // Random colouration. let random = terrain_random.generate::() * 0.2 + 0.9; // Choose colour. let colour = if y <= 0.0 { DARK_SAND } else if y <= 0.8 { SAND } else if y <= 10.0 { GRASS } else { SNOW }; point_gen::TerrainVertex { position: Vec3::new(point[0], y, point[1]), colour: mul_arr(colour, random), } }); // Generate the buffer data. let terrain_vertices = terrain.make_buffer_data(); // Create the buffers on the GPU to hold the data. let water_vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Water vertices"), contents: bytemuck::cast_slice(&water_vertices), usage: wgpu::BufferUsages::VERTEX, }); let terrain_vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Terrain vertices"), contents: bytemuck::cast_slice(&terrain_vertices), usage: wgpu::BufferUsages::VERTEX, }); // Create the bind group layout. This is what our uniforms will look like. let water_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Water Bind Group Layout"), entries: &[ // Uniform variables such as projection/view. wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new( size_of::() as _, ), }, count: None, }, // Reflection texture. wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { multisampled: false, sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, }, count: None, }, // Depth texture for terrain. wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { multisampled: false, sample_type: wgpu::TextureSampleType::Float { filterable: false }, view_dimension: wgpu::TextureViewDimension::D2, }, count: None, }, // Sampler to be able to sample the textures. wgpu::BindGroupLayoutEntry { binding: 3, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, // Sampler to be able to sample the textures. wgpu::BindGroupLayoutEntry { binding: 4, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), count: None, }, ], }); let terrain_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Terrain Bind Group Layout"), entries: &[ // Regular uniform variables like view/projection. wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new( size_of::() as _ ), }, count: None, }, ], }); // Create our pipeline layouts. let water_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("water"), bind_group_layouts: &[Some(&water_bind_group_layout)], immediate_size: 0, }); let terrain_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("terrain"), bind_group_layouts: &[Some(&terrain_bind_group_layout)], immediate_size: 0, }); let water_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { label: Some("Water Uniforms"), size: size_of::() as _, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let terrain_normal_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { label: Some("Normal Terrain Uniforms"), size: size_of::() as _, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let terrain_flipped_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { label: Some("Flipped Terrain Uniforms"), size: size_of::() as _, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); // Create bind group. // This puts values behind what was laid out in the bind group layout. let (reflect_view, depth_buffer, water_bind_group) = Self::initialize_resources( config, device, queue, &water_uniform_buf, &terrain_normal_uniform_buf, &terrain_flipped_uniform_buf, &water_bind_group_layout, ); let terrain_normal_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &terrain_bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: terrain_normal_uniform_buf.as_entire_binding(), }], label: Some("Terrain Normal Bind Group"), }); // Binds to the uniform buffer where the // camera has been placed underwater. let terrain_flipped_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &terrain_bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: terrain_flipped_uniform_buf.as_entire_binding(), }], label: Some("Terrain Flipped Bind Group"), }); // Upload/compile them to GPU code. let terrain_module = device.create_shader_module(wgpu::include_wgsl!("terrain.wgsl")); let water_module = device.create_shader_module(wgpu::include_wgsl!("water.wgsl")); // Create the render pipelines. These describe how the data will flow through the GPU, and what // constraints and modifiers it will have. let water_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("water"), // The "layout" is what uniforms will be needed. layout: Some(&water_pipeline_layout), // Vertex shader and input buffers vertex: wgpu::VertexState { module: &water_module, entry_point: Some("vs_main"), compilation_options: Default::default(), // Layout of our vertices. This should match the structs // which are uploaded to the GPU. This should also be // ensured by tagging on either a `#[repr(C)]` onto a // struct, or a `#[repr(transparent)]` if it only contains // one item, which is itself `repr(C)`. buffers: &[wgpu::VertexBufferLayout { array_stride: water_vertex_size as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &wgpu::vertex_attr_array![0 => Sint16x2, 1 => Sint8x4], }], }, // Fragment shader and output targets fragment: Some(wgpu::FragmentState { module: &water_module, entry_point: Some("fs_main"), compilation_options: Default::default(), // Describes how the colour will be interpolated // and assigned to the output attachment. targets: &[Some(wgpu::ColorTargetState { format: config.view_formats[0], blend: Some(wgpu::BlendState { color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::SrcAlpha, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, operation: wgpu::BlendOperation::Add, }, alpha: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::One, operation: wgpu::BlendOperation::Max, }, }), write_mask: wgpu::ColorWrites::ALL, })], }), // How the triangles will be rasterized. This is more important // for the terrain because of the beneath-the water shot. // This is also dependent on how the triangles are being generated. primitive: wgpu::PrimitiveState { // What kind of data are we passing in? topology: wgpu::PrimitiveTopology::TriangleList, front_face: wgpu::FrontFace::Cw, ..Default::default() }, // Describes how us writing to the depth/stencil buffer // will work. Since this is water, we need to read from the // depth buffer both as a texture in the shader, and as an // input attachment to do depth-testing. We don't write, so // depth_write_enabled is set to false. This is called RODS, // or read-only depth stencil. Here, we don't use stencil. depth_stencil: Some(wgpu::DepthStencilState { format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: Some(false), depth_compare: Some(wgpu::CompareFunction::Less), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }), // No multisampling is used. multisample: wgpu::MultisampleState::default(), multiview_mask: None, // No pipeline caching is used cache: None, }); // Same idea as the water pipeline. let terrain_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("terrain"), layout: Some(&terrain_pipeline_layout), vertex: wgpu::VertexState { module: &terrain_module, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[wgpu::VertexBufferLayout { array_stride: terrain_vertex_size as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Unorm8x4], }], }, fragment: Some(wgpu::FragmentState { module: &terrain_module, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), primitive: wgpu::PrimitiveState { front_face: wgpu::FrontFace::Ccw, cull_mode: Some(wgpu::Face::Front), ..Default::default() }, depth_stencil: Some(wgpu::DepthStencilState { format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: Some(true), depth_compare: Some(wgpu::CompareFunction::Less), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }), multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None }); // A render bundle to draw the terrain. let terrain_bundle = { let mut encoder = device.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor { label: None, color_formats: &[Some(config.view_formats[0])], depth_stencil: Some(wgpu::RenderBundleDepthStencil { format: wgpu::TextureFormat::Depth32Float, depth_read_only: false, stencil_read_only: true, }), sample_count: 1, multiview: None, }); encoder.set_pipeline(&terrain_pipeline); encoder.set_bind_group(0, &terrain_flipped_bind_group, &[]); encoder.set_vertex_buffer(0, terrain_vertex_buf.slice(..)); encoder.draw(0..terrain_vertices.len() as u32, 0..1); encoder.finish(&wgpu::RenderBundleDescriptor::default()) }; // Done Example { water_vertex_buf, water_vertex_count: water_vertices.len(), water_bind_group_layout, water_bind_group, water_uniform_buf, water_pipeline, terrain_vertex_buf, terrain_vertex_count: terrain_vertices.len(), terrain_normal_bind_group, terrain_normal_uniform_buf, terrain_flipped_uniform_buf, terrain_pipeline, terrain_bundle, reflect_view, depth_buffer, current_frame: 0, active: Some(0), } } fn update(&mut self, _event: winit::event::WindowEvent) { //empty } fn resize( &mut self, config: &wgpu::SurfaceConfiguration, device: &wgpu::Device, queue: &wgpu::Queue, ) { if config.width == 0 && config.height == 0 { // Stop rendering altogether. self.active = None; return; } self.active = Some(self.current_frame); // Regenerate all of the buffers and textures. let (reflect_view, depth_buffer, water_bind_group) = Self::initialize_resources( config, device, queue, &self.water_uniform_buf, &self.terrain_normal_uniform_buf, &self.terrain_flipped_uniform_buf, &self.water_bind_group_layout, ); self.water_bind_group = water_bind_group; self.depth_buffer = depth_buffer; self.reflect_view = reflect_view; } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { // Increment frame count regardless of if we draw. self.current_frame += 1; #[expect(clippy::eq_op, reason = "keeping common divisor on all elements")] let back_color = wgpu::Color { r: 161.0 / 255.0, g: 246.0 / 255.0, b: 255.0 / 255.0, a: 1.0, }; // Write the sin/cos values to the uniform buffer for the water. let (water_sin, water_cos) = ((self.current_frame as f32) / 600.0).sin_cos(); queue.write_buffer( &self.water_uniform_buf, size_of::<[f32; 16]>() as wgpu::BufferAddress * 2, bytemuck::cast_slice(&[water_sin, water_cos]), ); // Only render valid frames. See resize method. if let Some(active) = self.active { if active >= self.current_frame { return; } } else { return; } // The encoder provides a way to turn our instructions here, into // a command buffer the GPU can understand. let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Main Command Encoder"), }); // First pass: render the reflection. { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &self.reflect_view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(back_color), store: wgpu::StoreOp::Store, }, })], // We still need to use the depth buffer here // since the pipeline requires it. depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &self.depth_buffer, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), store: wgpu::StoreOp::Store, }), stencil_ops: None, }), timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.execute_bundles([&self.terrain_bundle]); } // Terrain right side up. This time we need to use the // depth values, so we must use StoreOp::Store. { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(back_color), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &self.depth_buffer, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), store: wgpu::StoreOp::Store, }), stencil_ops: None, }), timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.terrain_pipeline); rpass.set_bind_group(0, &self.terrain_normal_bind_group, &[]); rpass.set_vertex_buffer(0, self.terrain_vertex_buf.slice(..)); rpass.draw(0..self.terrain_vertex_count as u32, 0..1); } // Render the water. This reads from the depth buffer, but does not write // to it, so it cannot be in the same render pass. { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &self.depth_buffer, depth_ops: None, stencil_ops: None, }), timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); rpass.set_pipeline(&self.water_pipeline); rpass.set_bind_group(0, &self.water_bind_group, &[]); rpass.set_vertex_buffer(0, self.water_vertex_buf.slice(..)); rpass.draw(0..self.water_vertex_count as u32, 0..1); } queue.submit(iter::once(encoder.finish())); } } pub fn main() { crate::framework::run::("water"); } #[cfg(test)] #[wgpu_test::gpu_test] pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { name: "water", image_path: "/examples/features/src/water/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::default(), base_test_parameters: wgpu_test::TestParameters::default() .downlevel_flags(wgpu::DownlevelFlags::READ_ONLY_DEPTH_STENCIL) // To be fixed in . .expect_fail(wgpu_test::FailureCase { backends: Some(wgpu::Backends::VULKAN), reasons: vec![wgpu_test::FailureReason::validation_error() .with_message("WRITE_AFTER_WRITE hazard detected.")], behavior: wgpu_test::FailureBehavior::AssertFailure, ..Default::default() }), comparisons: &[wgpu_test::ComparisonType::Mean(0.018)], // Bounded by Apple A9 _phantom: std::marker::PhantomData::, }; ================================================ FILE: examples/features/src/water/point_gen.rs ================================================ //! //! This module covers generating points in a hexagonal fashion. //! use bytemuck::{Pod, Zeroable}; use std::collections::HashMap; // The following constants are used in calculations. // A and B are multiplication factors for x and y. /// /// X multiplication factor. /// 1.0 / sqrt(2) /// const A: f32 = std::f32::consts::FRAC_1_SQRT_2; /// /// Y multiplication factor. /// sqrt(3) / sqrt(2) == sqrt(1.5) /// const B: f32 = SQRT_3 * A; /// /// `sin(45deg)` is used to rotate the points. /// const S45: f32 = std::f32::consts::FRAC_1_SQRT_2; /// /// `cos(45deg)` is used to rotate the points. /// const C45: f32 = S45; const SQRT_3: f32 = 1.7320508; #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)] pub struct TerrainVertexAttributes { position: [f32; 3], normal: [f32; 3], colour: [u8; 4], } #[repr(C)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)] pub struct WaterVertexAttributes { position: [i16; 2], offsets: [i8; 4], } /// /// Represents the center of a single hexagon. /// #[derive(Copy, Clone, Debug)] pub struct TerrainVertex { pub position: glam::Vec3, pub colour: [u8; 4], } /// /// Gets the surrounding hexagonal points from a point. /// /// +---0---1 /// | / | | /// 5---p---2 /// | | / | /// 4---3---+ /// fn surrounding_hexagonal_points(x: isize, y: isize) -> [(isize, isize); 6] { [ (x, y - 1), (x + 1, y - 1), (x + 1, y), (x, y + 1), (x - 1, y + 1), (x - 1, y), ] } fn surrounding_point_values_iter( hashmap: &HashMap<(isize, isize), T>, x: isize, y: isize, for_each: impl FnMut((&T, &T)), ) { let points = surrounding_hexagonal_points(x, y); let points = [ points[0], points[1], points[2], points[3], points[4], points[5], points[0], ]; points .windows(2) .map(|x| (hashmap.get(&x[0]), hashmap.get(&x[1]))) .flat_map(|(a, b)| a.and_then(|x| b.map(|y| (x, y)))) .for_each(for_each); } /// /// Used in calculating terrain normals. /// pub fn calculate_normal(a: glam::Vec3, b: glam::Vec3, c: glam::Vec3) -> glam::Vec3 { (b - a).normalize().cross((c - a).normalize()).normalize() } /// /// Given the radius, how large of a square do we need to make a unit hexagon grid? /// fn q_given_r(radius: f32) -> usize { ((((((4.0 * radius) / SQRT_3) + 1.0).floor() / 2.0).floor() * 2.0) + 1.0) as usize } /// /// Represents terrain, however it contains the vertices only once. /// #[derive(Clone)] pub struct HexTerrainMesh { pub vertices: HashMap<(isize, isize), TerrainVertex>, half_size: isize, } impl HexTerrainMesh { /// /// Generates the vertices (or the centers of the hexagons). The colour and height is determined by /// a function passed in by the user. /// pub fn generate(radius: f32, mut gen_vertex: impl FnMut([f32; 2]) -> TerrainVertex) -> Self { let width = q_given_r(radius); let half_width = (width / 2) as isize; let mut map = HashMap::new(); let mut max = f32::NEG_INFINITY; for i in -half_width..=half_width { let x_o = i as f32; for j in -half_width..=half_width { let y_o = j as f32; let x = A * (x_o * C45 - y_o * S45); let z = B * (x_o * S45 + y_o * C45); if x.hypot(z) < radius { let vertex = gen_vertex([x, z]); if vertex.position.y > max { max = vertex.position.y; } map.insert((i, j), vertex); } } } Self { vertices: map, half_size: width as isize / 2, } } /// /// Creates the points required to render the mesh. /// pub fn make_buffer_data(&self) -> Vec { let mut vertices = Vec::new(); fn middle(p1: &TerrainVertex, p2: &TerrainVertex, p: &TerrainVertex) -> glam::Vec3 { (p1.position + p2.position + p.position) / 3.0 } fn half(p1: &TerrainVertex, p2: &TerrainVertex) -> glam::Vec3 { (p1.position + p2.position) / 2.0 } let mut push_triangle = |p1: &TerrainVertex, p2: &TerrainVertex, p: &TerrainVertex, c: [u8; 4]| { let m = middle(p1, p2, p); let ap = half(p1, p); let bp = half(p2, p); let p = p.position; let n1 = calculate_normal(ap, m, p); let n2 = calculate_normal(m, bp, p); vertices.extend( [ap, m, p, m, bp, p] .iter() .zip( std::iter::repeat::<[f32; 3]>(n1.into()) .chain(std::iter::repeat::<[f32; 3]>(n2.into())), ) .zip(std::iter::repeat(c)) .map(|((pos, normal), colour)| TerrainVertexAttributes { position: *pos.as_ref(), normal, colour, }), ); }; for i in -self.half_size..=self.half_size { for j in -self.half_size..=self.half_size { if let Some(p) = self.vertices.get(&(i, j)) { surrounding_point_values_iter(&self.vertices, i, j, |(a, b)| { push_triangle(a, b, p, p.colour) }); } } } vertices } } /// /// Water mesh which contains vertex data for the water mesh. /// /// It stores the values multiplied and rounded to the /// nearest whole number to be more efficient with space when /// sending large meshes to the GPU. /// pub struct HexWaterMesh { pub vertices: HashMap<(isize, isize), [i16; 2]>, half_size: isize, } impl HexWaterMesh { pub fn generate(radius: f32) -> Self { let width = q_given_r(radius); let half_width = (width / 2) as isize; let mut map = HashMap::new(); for i in -half_width..=half_width { let x_o = i as f32; for j in -half_width..=half_width { let y_o = j as f32; let x = A * (x_o * C45 - y_o * S45); let z = B * (x_o * S45 + y_o * C45); if x.hypot(z) < radius { let x = (x * 2.0).round() as i16; let z = ((z / B) * std::f32::consts::SQRT_2).round() as i16; map.insert((i, j), [x, z]); } } } Self { vertices: map, half_size: half_width, } } /// /// Generates the points required to render the mesh. /// pub fn generate_points(&self) -> Vec { let mut vertices = Vec::new(); fn calculate_differences(a: [i16; 2], b: [i16; 2], c: [i16; 2]) -> [i8; 4] { [ (b[0] - a[0]) as i8, (b[1] - a[1]) as i8, (c[0] - a[0]) as i8, (c[1] - a[1]) as i8, ] } let mut push_triangle = |a: [i16; 2], b: [i16; 2], c: [i16; 2]| { let bc = calculate_differences(a, b, c); let ca = calculate_differences(b, c, a); let ab = calculate_differences(c, a, b); vertices.extend( [a, b, c] .iter() .zip([bc, ca, ab].iter()) .map(|(&position, &offsets)| WaterVertexAttributes { position, offsets }), ); }; for i in -self.half_size..=self.half_size { for j in -self.half_size..=self.half_size { if (i - j) % 3 == 0 { if let Some(&p) = self.vertices.get(&(i, j)) { surrounding_point_values_iter(&self.vertices, i, j, |(a, b)| { push_triangle(*a, *b, p) }); } } } } vertices } } ================================================ FILE: examples/features/src/water/terrain.wgsl ================================================ struct Uniforms { projection_view: mat4x4, clipping_plane: vec4, }; @group(0) @binding(0) var uniforms: Uniforms; const light = vec3(150.0, 70.0, 0.0); const light_colour = vec3(1.0, 0.98, 0.82); const ambient = 0.2; struct VertexOutput { @builtin(position) position: vec4, @location(0) colour: vec4, // Comment this out if using user-clipping planes: @location(1) clip_dist: f32, }; @vertex fn vs_main( @location(0) position: vec3, @location(1) normal: vec3, @location(2) colour: vec4, ) -> VertexOutput { var result: VertexOutput; result.position = uniforms.projection_view * vec4(position, 1.0); // https://www.desmos.com/calculator/nqgyaf8uvo let normalized_light_direction = normalize(position - light); let brightness_diffuse = clamp(dot(normalized_light_direction, normal), 0.2, 1.0); result.colour = vec4(max((brightness_diffuse + ambient) * light_colour * colour.rgb, vec3(0.0, 0.0, 0.0)), colour.a); result.clip_dist = dot(vec4(position, 1.0), uniforms.clipping_plane); return result; } @fragment fn fs_main( vertex: VertexOutput, ) -> @location(0) vec4 { // Comment this out if using user-clipping planes: if(vertex.clip_dist < 0.0) { discard; } return vec4(vertex.colour.xyz, 1.0); } ================================================ FILE: examples/features/src/water/water.wgsl ================================================ struct Uniforms { view: mat4x4, projection: mat4x4, time_size_width: vec4, viewport_height: f32, }; @group(0) @binding(0) var uniforms: Uniforms; const light_point = vec3(150.0, 70.0, 0.0); const light_colour = vec3(1.0, 0.98, 0.82); const one = vec4(1.0, 1.0, 1.0, 1.0); const Y_SCL: f32 = 0.86602540378443864676372317075294; const CURVE_BIAS: f32 = -0.1; const INV_1_CURVE_BIAS: f32 = 1.11111111111; //1.0 / (1.0 + CURVE_BIAS); // Polyfill for modf to deal with differences between chrome's WebGPU and // current naga. fn modf_polyfill_vec3(value: vec3, int_part: ptr>) -> vec3 { *int_part = trunc(value); return value - *int_part; } fn modf_polyfill_vec4(value: vec4, int_part: ptr>) -> vec4 { *int_part = trunc(value); return value - *int_part; } // // The following code to calculate simplex 3D // is from https://github.com/ashima/webgl-noise // // Simplex 3D Noise // by Ian McEwan, Ashima Arts. // fn permute(x: vec4) -> vec4 { var temp: vec4 = 289.0 * one; return modf_polyfill_vec4(((x*34.0) + one) * x, &temp); } fn taylorInvSqrt(r: vec4) -> vec4 { return 1.79284291400159 * one - 0.85373472095314 * r; } fn snoise(v: vec3) -> f32 { let C = vec2(1.0/6.0, 1.0/3.0); let D = vec4(0.0, 0.5, 1.0, 2.0); // First corner //TODO: use the splat operations when available let vCy = dot(v, C.yyy); var i: vec3 = floor(v + vec3(vCy, vCy, vCy)); let iCx = dot(i, C.xxx); let x0 = v - i + vec3(iCx, iCx, iCx); // Other corners let g = step(x0.yzx, x0.xyz); let l = (vec3(1.0, 1.0, 1.0) - g).zxy; let i1 = min(g, l); let i2 = max(g, l); // x0 = x0 - 0.0 + 0.0 * C.xxx; // x1 = x0 - i1 + 1.0 * C.xxx; // x2 = x0 - i2 + 2.0 * C.xxx; // x3 = x0 - 1.0 + 3.0 * C.xxx; let x1 = x0 - i1 + C.xxx; let x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y let x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y // Permutations var temp: vec3 = 289.0 * one.xyz; i = modf_polyfill_vec3(i, &temp); let p = permute( permute( permute(i.zzzz + vec4(0.0, i1.z, i2.z, 1.0)) + i.yyyy + vec4(0.0, i1.y, i2.y, 1.0)) + i.xxxx + vec4(0.0, i1.x, i2.x, 1.0)); // Gradients: 7x7 points over a square, mapped onto an octahedron. // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) let n_ = 0.142857142857;// 1.0/7.0 let ns = n_ * D.wyz - D.xzx; let j = p - 49.0 * floor(p * ns.z * ns.z);// mod(p,7*7) let x_ = floor(j * ns.z); let y_ = floor(j - 7.0 * x_);// mod(j,N) var x: vec4 = x_ *ns.x + ns.yyyy; var y: vec4 = y_ *ns.x + ns.yyyy; let h = one - abs(x) - abs(y); let b0 = vec4(x.xy, y.xy); let b1 = vec4(x.zw, y.zw); //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - one; //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - one; let s0 = floor(b0)*2.0 + one; let s1 = floor(b1)*2.0 + one; let sh = -step(h, 0.0 * one); let a0 = b0.xzyw + s0.xzyw*sh.xxyy; let a1 = b1.xzyw + s1.xzyw*sh.zzww; var p0 = vec3(a0.xy, h.x); var p1 = vec3(a0.zw, h.y); var p2 = vec3(a1.xy, h.z); var p3 = vec3(a1.zw, h.w); //Normalise gradients let norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w; // Mix final noise value var m: vec4 = max(0.6 * one - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0 * one); m *= m; return 9.0 * dot(m*m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3))); } // End of 3D simplex code. fn apply_distortion(pos: vec3) -> vec3 { var perlin_pos: vec3 = pos; //Do noise transformation to permit for smooth, //continuous movement. //TODO: we should be able to name them `sin` and `cos`. let sn = uniforms.time_size_width.x; let cs = uniforms.time_size_width.y; let size = uniforms.time_size_width.z; // Rotate 90 Z, Move Left Size / 2 perlin_pos = vec3(perlin_pos.y - perlin_pos.x - size, perlin_pos.x, perlin_pos.z); let xcos = perlin_pos.x * cs; let xsin = perlin_pos.x * sn; let ycos = perlin_pos.y * cs; let ysin = perlin_pos.y * sn; let zcos = perlin_pos.z * cs; let zsin = perlin_pos.z * sn; // Rotate Time Y let perlin_pos_y = vec3(xcos + zsin, perlin_pos.y, -xsin + xcos); // Rotate Time Z let perlin_pos_z = vec3(xcos - ysin, xsin + ycos, perlin_pos.x); // Rotate 90 Y perlin_pos = vec3(perlin_pos.z - perlin_pos.x, perlin_pos.y, perlin_pos.x); // Rotate Time X let perlin_pos_x = vec3(perlin_pos.x, ycos - zsin, ysin + zcos); // Sample at different places for x/y/z to get random-looking water. return vec3( //TODO: use splats pos.x + snoise(perlin_pos_x + 2.0*one.xxx) * 0.4, pos.y + snoise(perlin_pos_y - 2.0*one.xxx) * 1.8, pos.z + snoise(perlin_pos_z) * 0.4 ); } // Multiply the input by the scale values. fn make_position(original: vec2) -> vec4 { let interpreted = vec3(original.x * 0.5, 0.0, original.y * Y_SCL); return vec4(apply_distortion(interpreted), 1.0); } // Create the normal, and apply the curve. Change the Curve Bias above. fn make_normal(a: vec3, b: vec3, c: vec3) -> vec3 { let norm = normalize(cross(b - c, a - c)); let center = (a + b + c) * (1.0 / 3.0); //TODO: use splat return (normalize(a - center) * CURVE_BIAS + norm) * INV_1_CURVE_BIAS; } // Calculate the fresnel effect. fn calc_fresnel(view: vec3, normal: vec3) -> f32 { var refractive: f32 = abs(dot(view, normal)); refractive = pow(refractive, 1.33333333333); return refractive; } // Calculate the specular lighting. fn calc_specular(eye: vec3, normal: vec3, light: vec3) -> f32 { let light_reflected = reflect(light, normal); var specular: f32 = max(dot(eye, light_reflected), 0.0); specular = pow(specular, 10.0); return specular; } struct VertexOutput { @builtin(position) position: vec4, @location(0) f_WaterScreenPos: vec2, @location(1) f_Fresnel: f32, @location(2) f_Light: vec3, }; @vertex fn vs_main( @location(0) position: vec2, @location(1) offsets: vec4, ) -> VertexOutput { let p_pos = vec2(position); let b_pos = make_position(p_pos + vec2(offsets.xy)); let c_pos = make_position(p_pos + vec2(offsets.zw)); let a_pos = make_position(p_pos); let original_pos = vec4(p_pos.x * 0.5, 0.0, p_pos.y * Y_SCL, 1.0); let vm = uniforms.view; let transformed_pos = vm * a_pos; //TODO: use vector splats for division let water_pos = transformed_pos.xyz * (1.0 / transformed_pos.w); let normal = make_normal((vm * a_pos).xyz, (vm * b_pos).xyz, (vm * c_pos).xyz); let eye = normalize(-water_pos); let transformed_light = vm * vec4(light_point, 1.0); var result: VertexOutput; result.f_Light = light_colour * calc_specular(eye, normal, normalize(water_pos.xyz - (transformed_light.xyz * (1.0 / transformed_light.w)))); result.f_Fresnel = calc_fresnel(eye, normal); let gridpos = uniforms.projection * vm * original_pos; result.f_WaterScreenPos = (0.5 * gridpos.xy * (1.0 / gridpos.w)) + vec2(0.5, 0.5); result.position = uniforms.projection * transformed_pos; return result; } const water_colour = vec3(0.0, 0.46, 0.95); const zNear = 10.0; const zFar = 400.0; @group(0) @binding(1) var reflection: texture_2d; @group(0) @binding(2) var terrain_depth_tex: texture_2d; @group(0) @binding(3) var colour_sampler: sampler; @group(0) @binding(4) var depth_sampler: sampler; fn to_linear_depth(depth: f32) -> f32 { let z_n = 2.0 * depth - 1.0; let z_e = 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear)); return z_e; } @fragment fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { let reflection_colour = textureSample(reflection, colour_sampler, vertex.f_WaterScreenPos.xy).xyz; let pixel_depth = to_linear_depth(vertex.position.z); let normalized_coords = vertex.position.xy / vec2(uniforms.time_size_width.w, uniforms.viewport_height); let terrain_depth = to_linear_depth(textureSample(terrain_depth_tex, depth_sampler, normalized_coords).r); let dist = terrain_depth - pixel_depth; let clamped = pow(smoothstep(0.0, 1.5, dist), 4.8); let final_colour = vertex.f_Light + reflection_colour; let t = smoothstep(1.0, 5.0, dist) * 0.2; //TODO: splat for mix()? let depth_colour = mix(final_colour, water_colour, vec3(t, t, t)); return vec4(depth_colour, clamped * (1.0 - vertex.f_Fresnel)); } ================================================ FILE: examples/features/web-static/index.html ================================================
================================================ FILE: examples/standalone/01_hello_compute/Cargo.toml ================================================ [package] name = "wgpu-example-01-hello-compute" edition = "2021" rust-version = "1.87" publish = false [dependencies] bytemuck = "1.22.0" env_logger = "0.11" pollster = "0.4" wgpu = "29.0.0" ================================================ FILE: examples/standalone/01_hello_compute/cargo-generate.toml ================================================ ================================================ FILE: examples/standalone/01_hello_compute/src/main.rs ================================================ /// To serve as an introduction to the wgpu api, we will implement a simple /// compute shader which takes a list of numbers on the CPU and doubles them on the GPU. /// /// While this isn't a very practical example, you will see all the major components /// of using wgpu headlessly, including getting a device, running a shader, and transferring /// data between the CPU and GPU. /// /// If you time the recording and execution of this example you will certainly see that /// running on the gpu is slower than doing the same calculation on the cpu. This is because /// floating point multiplication is a very simple operation so the transfer/submission overhead /// is quite a lot higher than the actual computation. This is normal and shows that the GPU /// needs a lot higher work/transfer ratio to come out ahead. use std::{num::NonZeroU64, str::FromStr}; use wgpu::util::DeviceExt; fn main() { // Parse all arguments as floats. We need to skip argument 0, which is the name of the program. let arguments: Vec = std::env::args() .skip(1) .map(|s| { f32::from_str(&s).unwrap_or_else(|_| panic!("Cannot parse argument {s:?} as a float.")) }) .collect(); if arguments.is_empty() { println!("No arguments provided. Please provide a list of numbers to double."); return; } println!("Parsed {} arguments", arguments.len()); // wgpu uses `log` for all of our logging, so we initialize a logger with the `env_logger` crate. // // To change the log level, set the `RUST_LOG` environment variable. See the `env_logger` // documentation for more information. env_logger::init(); // We first initialize an wgpu `Instance`, which contains any "global" state wgpu needs. // // This is what loads the vulkan/dx12/metal/opengl libraries. let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle()); // We then create an `Adapter` which represents a physical gpu in the system. It allows // us to query information about it and create a `Device` from it. // // This function is asynchronous in WebGPU, so request_adapter returns a future. On native/webgl // the future resolves immediately, so we can block on it without harm. let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())) .expect("Failed to create adapter"); // Print out some basic information about the adapter. println!("Running on Adapter: {:#?}", adapter.get_info()); // Check to see if the adapter supports compute shaders. While WebGPU guarantees support for // compute shaders, wgpu supports a wider range of devices through the use of "downlevel" devices. let downlevel_capabilities = adapter.get_downlevel_capabilities(); if !downlevel_capabilities .flags .contains(wgpu::DownlevelFlags::COMPUTE_SHADERS) { panic!("Adapter does not support compute shaders"); } // We then create a `Device` and a `Queue` from the `Adapter`. // // The `Device` is used to create and manage GPU resources. // The `Queue` is a queue used to submit work for the GPU to process. let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, })) .expect("Failed to create device"); // Create a shader module from our shader code. This will parse and validate the shader. // // `include_wgsl` is a macro provided by wgpu like `include_str` which constructs a ShaderModuleDescriptor. // If you want to load shaders differently, you can construct the ShaderModuleDescriptor manually. let module = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); // Create a buffer with the data we want to process on the GPU. // // `create_buffer_init` is a utility provided by `wgpu::util::DeviceExt` which simplifies creating // a buffer with some initial data. // // We use the `bytemuck` crate to cast the slice of f32 to a &[u8] to be uploaded to the GPU. let input_data_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: None, contents: bytemuck::cast_slice(&arguments), usage: wgpu::BufferUsages::STORAGE, }); // Now we create a buffer to store the output data. let output_data_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: input_data_buffer.size(), usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: false, }); // Finally we create a buffer which can be read by the CPU. This buffer is how we will read // the data. We need to use a separate buffer because we need to have a usage of `MAP_READ`, // and that usage can only be used with `COPY_DST`. let download_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: input_data_buffer.size(), usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false, }); // A bind group layout describes the types of resources that a bind group can contain. Think // of this like a C-style header declaration, ensuring both the pipeline and bind group agree // on the types of resources. let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[ // Input buffer wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true }, // This is the size of a single element in the buffer. min_binding_size: Some(NonZeroU64::new(4).unwrap()), has_dynamic_offset: false, }, count: None, }, // Output buffer wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, // This is the size of a single element in the buffer. min_binding_size: Some(NonZeroU64::new(4).unwrap()), has_dynamic_offset: false, }, count: None, }, ], }); // The bind group contains the actual resources to bind to the pipeline. // // Even when the buffers are individually dropped, wgpu will keep the bind group and buffers // alive until the bind group itself is dropped. let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: input_data_buffer.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: output_data_buffer.as_entire_binding(), }, ], }); // The pipeline layout describes the bind groups that a pipeline expects let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); // The pipeline is the ready-to-go program state for the GPU. It contains the shader modules, // the interfaces (bind group layouts) and the shader entry point. let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: Some(&pipeline_layout), module: &module, entry_point: Some("doubleMe"), compilation_options: wgpu::PipelineCompilationOptions::default(), cache: None, }); // The command encoder allows us to record commands that we will later submit to the GPU. let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); // A compute pass is a single series of compute operations. While we are recording a compute // pass, we cannot record to the encoder. let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None, }); // Set the pipeline that we want to use compute_pass.set_pipeline(&pipeline); // Set the bind group that we want to use compute_pass.set_bind_group(0, &bind_group, &[]); // Now we dispatch a series of workgroups. Each workgroup is a 3D grid of individual programs. // // We defined the workgroup size in the shader as 64x1x1. So in order to process all of our // inputs, we ceiling divide the number of inputs by 64. If the user passes 32 inputs, we will // dispatch 1 workgroups. If the user passes 65 inputs, we will dispatch 2 workgroups, etc. let workgroup_count = arguments.len().div_ceil(64); compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); // Now we drop the compute pass, giving us access to the encoder again. drop(compute_pass); // We add a copy operation to the encoder. This will copy the data from the output buffer on the // GPU to the download buffer on the CPU. encoder.copy_buffer_to_buffer( &output_data_buffer, 0, &download_buffer, 0, output_data_buffer.size(), ); // We finish the encoder, giving us a fully recorded command buffer. let command_buffer = encoder.finish(); // At this point nothing has actually been executed on the gpu. We have recorded a series of // commands that we want to execute, but they haven't been sent to the gpu yet. // // Submitting to the queue sends the command buffer to the gpu. The gpu will then execute the // commands in the command buffer in order. queue.submit([command_buffer]); // We now map the download buffer so we can read it. Mapping tells wgpu that we want to read/write // to the buffer directly by the CPU and it should not permit any more GPU operations on the buffer. // // Mapping requires that the GPU be finished using the buffer before it resolves, so mapping has a callback // to tell you when the mapping is complete. let buffer_slice = download_buffer.slice(..); buffer_slice.map_async(wgpu::MapMode::Read, |_| { // In this case we know exactly when the mapping will be finished, // so we don't need to do anything in the callback. }); // Wait for the GPU to finish working on the submitted work. This doesn't work on WebGPU, so we would need // to rely on the callback to know when the buffer is mapped. device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); // We can now read the data from the buffer. let data = buffer_slice.get_mapped_range(); // Convert the data back to a slice of f32. let result: &[f32] = bytemuck::cast_slice(&data); // Print out the result. println!("Result: {result:?}"); } ================================================ FILE: examples/standalone/01_hello_compute/src/shader.wgsl ================================================ // Input to the shader. The length of the array is determined by what buffer is bound. // // Out of bounds accesses @group(0) @binding(0) var input: array; // Output of the shader. @group(0) @binding(1) var output: array; // Ideal workgroup size depends on the hardware, the workload, and other factors. However, it should // _generally_ be a multiple of 64. Common sizes are 64x1x1, 256x1x1; or 8x8x1, 16x16x1 for 2D workloads. @compute @workgroup_size(64) fn doubleMe(@builtin(global_invocation_id) global_id: vec3) { // While compute invocations are 3d, we're only using one dimension. let index = global_id.x; // Because we're using a workgroup size of 64, if the input size isn't a multiple of 64, // we will have some "extra" invocations. This is fine, but we should tell them to stop // to avoid out-of-bounds accesses. let array_length = arrayLength(&input); if (global_id.x >= array_length) { return; } // Do the multiply by two and write to the output. output[global_id.x] = input[global_id.x] * 2.0; } ================================================ FILE: examples/standalone/02_hello_window/Cargo.toml ================================================ [package] name = "wgpu-example-02-hello-window" edition = "2021" rust-version = "1.87" publish = false [dependencies] env_logger = "0.11" pollster = "0.4" wgpu = "29.0.0" winit = { version = "0.30.8", features = ["android-native-activity"] } ================================================ FILE: examples/standalone/02_hello_window/src/main.rs ================================================ use std::sync::Arc; use winit::{ application::ApplicationHandler, event::WindowEvent, event_loop::{ActiveEventLoop, ControlFlow, EventLoop, OwnedDisplayHandle}, window::{Window, WindowId}, }; struct State { instance: wgpu::Instance, window: Arc, device: wgpu::Device, queue: wgpu::Queue, size: winit::dpi::PhysicalSize, surface: wgpu::Surface<'static>, surface_format: wgpu::TextureFormat, } impl State { async fn new(display: OwnedDisplayHandle, window: Arc) -> State { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_with_display_handle( Box::new(display), )); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions::default()) .await .unwrap(); let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor::default()) .await .unwrap(); let size = window.inner_size(); let surface = instance.create_surface(window.clone()).unwrap(); let cap = surface.get_capabilities(&adapter); let surface_format = cap.formats[0]; let state = State { instance, window, device, queue, size, surface, surface_format, }; // Configure surface for the first time state.configure_surface(); state } fn get_window(&self) -> &Window { &self.window } fn configure_surface(&self) { let surface_config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: self.surface_format, // Request compatibility with the sRGB-format texture view we‘re going to create later. view_formats: vec![self.surface_format.add_srgb_suffix()], alpha_mode: wgpu::CompositeAlphaMode::Auto, width: self.size.width, height: self.size.height, desired_maximum_frame_latency: 2, present_mode: wgpu::PresentMode::AutoVsync, }; self.surface.configure(&self.device, &surface_config); } fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { self.size = new_size; // reconfigure the surface self.configure_surface(); } fn render(&mut self) { // Create texture view. // NOTE: We must handle Timeout because the surface may be unavailable // (e.g., when the window is occluded on macOS). let surface_texture = match self.surface.get_current_texture() { wgpu::CurrentSurfaceTexture::Success(texture) => texture, wgpu::CurrentSurfaceTexture::Occluded | wgpu::CurrentSurfaceTexture::Timeout => return, wgpu::CurrentSurfaceTexture::Suboptimal(_) | wgpu::CurrentSurfaceTexture::Outdated => { self.configure_surface(); return; } wgpu::CurrentSurfaceTexture::Validation => { unreachable!("No error scope registered, so validation errors will panic") } wgpu::CurrentSurfaceTexture::Lost => { self.surface = self.instance.create_surface(self.window.clone()).unwrap(); self.configure_surface(); return; } }; let texture_view = surface_texture .texture .create_view(&wgpu::TextureViewDescriptor { // Without add_srgb_suffix() the image we will be working with // might not be "gamma correct". format: Some(self.surface_format.add_srgb_suffix()), ..Default::default() }); // Renders a GREEN screen let mut encoder = self.device.create_command_encoder(&Default::default()); // Create the renderpass which will clear the screen. let renderpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &texture_view, depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); // If you wanted to call any drawing commands, they would go here. // End the renderpass. drop(renderpass); // Submit the command in the queue to execute self.queue.submit([encoder.finish()]); self.window.pre_present_notify(); surface_texture.present(); } } #[derive(Default)] struct App { state: Option, } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { // Create window object let window = Arc::new( event_loop .create_window(Window::default_attributes()) .unwrap(), ); let state = pollster::block_on(State::new( event_loop.owned_display_handle(), window.clone(), )); self.state = Some(state); window.request_redraw(); } fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { let state = self.state.as_mut().unwrap(); match event { WindowEvent::CloseRequested => { println!("The close button was pressed; stopping"); event_loop.exit(); } WindowEvent::RedrawRequested => { state.render(); // Emits a new redraw requested event. state.get_window().request_redraw(); } WindowEvent::Resized(size) => { // Reconfigures the size of the surface. We do not re-render // here as this event is always followed up by redraw request. state.resize(size); } _ => (), } } } fn main() { // wgpu uses `log` for all of our logging, so we initialize a logger with the `env_logger` crate. // // To change the log level, set the `RUST_LOG` environment variable. See the `env_logger` // documentation for more information. env_logger::init(); let event_loop = EventLoop::new().unwrap(); // When the current loop iteration finishes, immediately begin a new // iteration regardless of whether or not new events are available to // process. Preferred for applications that want to render as fast as // possible, like games. event_loop.set_control_flow(ControlFlow::Poll); // When the current loop iteration finishes, suspend the thread until // another event arrives. Helps keeping CPU utilization low if nothing // is happening, which is preferred if the application might be idling in // the background. // event_loop.set_control_flow(ControlFlow::Wait); let mut app = App::default(); event_loop.run_app(&mut app).unwrap(); } ================================================ FILE: examples/standalone/custom_backend/Cargo.toml ================================================ [package] name = "wgpu-example-custom-backend" edition = "2021" rust-version = "1.87" publish = false [features] default = ["web"] web = ["wgpu/web"] [dependencies] wgpu = { version = "29.0.0", features = [ "custom", "wgsl", ], default-features = false } pollster = { version = "0.4", features = ["macro"] } ================================================ FILE: examples/standalone/custom_backend/src/custom.rs ================================================ #![allow(dead_code)] use std::pin::Pin; use std::sync::Arc; use wgpu::custom::{ AdapterInterface, ComputePipelineInterface, DeviceInterface, DispatchAdapter, DispatchBlas, DispatchDevice, DispatchQueue, DispatchShaderModule, DispatchSurface, InstanceInterface, QueueInterface, RequestAdapterFuture, ShaderModuleInterface, }; #[derive(Debug, Clone)] pub struct Counter(Arc<()>); impl Counter { pub fn new() -> Self { Self(Arc::new(())) } pub fn count(&self) -> usize { Arc::strong_count(&self.0) } } #[derive(Debug)] pub struct CustomInstance(pub Counter); impl InstanceInterface for CustomInstance { fn new(__desc: wgpu::InstanceDescriptor) -> Self where Self: Sized, { Self(Counter::new()) } unsafe fn create_surface( &self, _target: wgpu::SurfaceTargetUnsafe, ) -> Result { unimplemented!() } fn request_adapter( &self, _options: &wgpu::RequestAdapterOptions<'_, '_>, ) -> std::pin::Pin> { Box::pin(std::future::ready(Ok(DispatchAdapter::custom( CustomAdapter(self.0.clone()), )))) } fn poll_all_devices(&self, _force_wait: bool) -> bool { unimplemented!() } fn wgsl_language_features(&self) -> wgpu::WgslLanguageFeatures { unimplemented!() } fn enumerate_adapters( &self, _backends: wgpu::Backends, ) -> Pin> { unimplemented!() } } #[derive(Debug)] struct CustomAdapter(Counter); impl AdapterInterface for CustomAdapter { fn request_device( &self, desc: &wgpu::DeviceDescriptor<'_>, ) -> Pin> { assert_eq!(desc.label, Some("device")); let res: Result<_, wgpu::RequestDeviceError> = Ok(( DispatchDevice::custom(CustomDevice(self.0.clone())), DispatchQueue::custom(CustomQueue(self.0.clone())), )); Box::pin(std::future::ready(res)) } fn cooperative_matrix_properties(&self) -> Vec { Vec::new() } fn is_surface_supported(&self, _surface: &DispatchSurface) -> bool { unimplemented!() } fn features(&self) -> wgpu::Features { unimplemented!() } fn limits(&self) -> wgpu::Limits { unimplemented!() } fn downlevel_capabilities(&self) -> wgpu::DownlevelCapabilities { unimplemented!() } fn get_info(&self) -> wgpu::AdapterInfo { unimplemented!() } fn get_texture_format_features( &self, _format: wgpu::TextureFormat, ) -> wgpu::TextureFormatFeatures { unimplemented!() } fn get_presentation_timestamp(&self) -> wgpu::PresentationTimestamp { unimplemented!() } } #[derive(Debug)] struct CustomDevice(Counter); impl DeviceInterface for CustomDevice { fn features(&self) -> wgpu::Features { unimplemented!() } fn limits(&self) -> wgpu::Limits { unimplemented!() } fn adapter_info(&self) -> wgpu::AdapterInfo { unimplemented!() } fn create_shader_module( &self, desc: wgpu::ShaderModuleDescriptor<'_>, _shader_bound_checks: wgpu::ShaderRuntimeChecks, ) -> DispatchShaderModule { assert_eq!(desc.label, Some("shader")); DispatchShaderModule::custom(CustomShaderModule(self.0.clone())) } unsafe fn create_shader_module_passthrough( &self, _desc: &wgpu::ShaderModuleDescriptorPassthrough<'_>, ) -> DispatchShaderModule { unimplemented!() } fn create_bind_group_layout( &self, _desc: &wgpu::BindGroupLayoutDescriptor<'_>, ) -> wgpu::custom::DispatchBindGroupLayout { unimplemented!() } fn create_bind_group( &self, _desc: &wgpu::BindGroupDescriptor<'_>, ) -> wgpu::custom::DispatchBindGroup { unimplemented!() } fn create_pipeline_layout( &self, _desc: &wgpu::PipelineLayoutDescriptor<'_>, ) -> wgpu::custom::DispatchPipelineLayout { unimplemented!() } fn create_render_pipeline( &self, _desc: &wgpu::RenderPipelineDescriptor<'_>, ) -> wgpu::custom::DispatchRenderPipeline { unimplemented!() } fn create_mesh_pipeline( &self, _desc: &wgpu::MeshPipelineDescriptor<'_>, ) -> wgpu::custom::DispatchRenderPipeline { unimplemented!() } fn create_compute_pipeline( &self, desc: &wgpu::ComputePipelineDescriptor<'_>, ) -> wgpu::custom::DispatchComputePipeline { let module = desc.module.as_custom::().unwrap(); wgpu::custom::DispatchComputePipeline::custom(CustomComputePipeline(module.0.clone())) } unsafe fn create_pipeline_cache( &self, _desc: &wgpu::PipelineCacheDescriptor<'_>, ) -> wgpu::custom::DispatchPipelineCache { unimplemented!() } fn create_buffer(&self, _desc: &wgpu::BufferDescriptor<'_>) -> wgpu::custom::DispatchBuffer { unimplemented!() } fn create_texture(&self, _desc: &wgpu::TextureDescriptor<'_>) -> wgpu::custom::DispatchTexture { unimplemented!() } fn create_external_texture( &self, _desc: &wgpu::ExternalTextureDescriptor<'_>, _planes: &[&wgpu::TextureView], ) -> wgpu::custom::DispatchExternalTexture { unimplemented!() } fn create_blas( &self, _desc: &wgpu::CreateBlasDescriptor<'_>, _sizes: wgpu::BlasGeometrySizeDescriptors, ) -> (Option, wgpu::custom::DispatchBlas) { unimplemented!() } fn create_tlas(&self, _desc: &wgpu::CreateTlasDescriptor<'_>) -> wgpu::custom::DispatchTlas { unimplemented!() } fn create_sampler(&self, _desc: &wgpu::SamplerDescriptor<'_>) -> wgpu::custom::DispatchSampler { unimplemented!() } fn create_query_set( &self, _desc: &wgpu::QuerySetDescriptor<'_>, ) -> wgpu::custom::DispatchQuerySet { unimplemented!() } fn create_command_encoder( &self, _desc: &wgpu::CommandEncoderDescriptor<'_>, ) -> wgpu::custom::DispatchCommandEncoder { unimplemented!() } fn create_render_bundle_encoder( &self, _desc: &wgpu::RenderBundleEncoderDescriptor<'_>, ) -> wgpu::custom::DispatchRenderBundleEncoder { unimplemented!() } fn set_device_lost_callback(&self, _device_lost_callback: wgpu::custom::BoxDeviceLostCallback) { unimplemented!() } fn on_uncaptured_error(&self, _handler: Arc) { unimplemented!() } fn push_error_scope(&self, _filter: wgpu::ErrorFilter) -> u32 { unimplemented!() } fn pop_error_scope(&self, _index: u32) -> Pin> { unimplemented!() } unsafe fn start_graphics_debugger_capture(&self) { unimplemented!() } unsafe fn stop_graphics_debugger_capture(&self) { unimplemented!() } fn poll( &self, _maintain: wgpu::wgt::PollType, ) -> Result { unimplemented!() } fn get_internal_counters(&self) -> wgpu::InternalCounters { unimplemented!() } fn generate_allocator_report(&self) -> Option { unimplemented!() } fn destroy(&self) { unimplemented!() } } #[derive(Debug)] pub struct CustomShaderModule(pub Counter); impl ShaderModuleInterface for CustomShaderModule { fn get_compilation_info(&self) -> Pin> { unimplemented!() } } #[derive(Debug)] struct CustomQueue(Counter); impl QueueInterface for CustomQueue { fn write_buffer( &self, _buffer: &wgpu::custom::DispatchBuffer, _offset: wgpu::BufferAddress, _data: &[u8], ) { unimplemented!() } fn create_staging_buffer( &self, _size: wgpu::BufferSize, ) -> Option { unimplemented!() } fn validate_write_buffer( &self, _buffer: &wgpu::custom::DispatchBuffer, _offset: wgpu::BufferAddress, _size: wgpu::BufferSize, ) -> Option<()> { unimplemented!() } fn write_staging_buffer( &self, _buffer: &wgpu::custom::DispatchBuffer, _offset: wgpu::BufferAddress, _staging_buffer: &wgpu::custom::DispatchQueueWriteBuffer, ) { unimplemented!() } fn write_texture( &self, _texture: wgpu::TexelCopyTextureInfo<'_>, _data: &[u8], _data_layout: wgpu::TexelCopyBufferLayout, _size: wgpu::Extent3d, ) { unimplemented!() } fn submit( &self, _command_buffers: &mut dyn Iterator, ) -> u64 { unimplemented!() } fn get_timestamp_period(&self) -> f32 { unimplemented!() } fn on_submitted_work_done(&self, _callback: wgpu::custom::BoxSubmittedWorkDoneCallback) { unimplemented!() } #[cfg(all(target_arch = "wasm32", feature = "web"))] fn copy_external_image_to_texture( &self, _source: &wgpu::CopyExternalImageSourceInfo, _dest: wgpu::CopyExternalImageDestInfo<&wgpu::Texture>, _size: wgpu::Extent3d, ) { unimplemented!() } fn compact_blas(&self, _blas: &DispatchBlas) -> (Option, DispatchBlas) { unimplemented!() } } #[derive(Debug)] pub struct CustomComputePipeline(pub Counter); impl ComputePipelineInterface for CustomComputePipeline { fn get_bind_group_layout(&self, _index: u32) -> wgpu::custom::DispatchBindGroupLayout { unimplemented!() } } ================================================ FILE: examples/standalone/custom_backend/src/main.rs ================================================ use std::marker::PhantomData; use custom::{Counter, CustomShaderModule}; use wgpu::{DeviceDescriptor, RequestAdapterOptions}; mod custom; #[pollster::main] async fn main() { let counter = Counter::new(); { let custom_instance = custom::CustomInstance(counter.clone()); // wrap custom instance into wgpu abstraction let instance = wgpu::Instance::from_custom(custom_instance); assert_eq!(counter.count(), 2); // do work on instance (usually by passing it to other libs) // here we will simulate a library and ensure that counter is incremented let adapter = instance .request_adapter(&RequestAdapterOptions::default()) .await .unwrap(); assert_eq!(counter.count(), 3); let (device, _queue) = adapter .request_device(&DeviceDescriptor { label: Some("device"), ..Default::default() }) .await .unwrap(); assert_eq!(counter.count(), 5); let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("shader"), source: wgpu::ShaderSource::Dummy(PhantomData), }); let custom_module = module.as_custom::().unwrap(); assert_eq!(custom_module.0.count(), 6); let _module_clone = module.clone(); assert_eq!(counter.count(), 6); let _pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: None, module: &module, entry_point: None, compilation_options: Default::default(), cache: None, }); assert_eq!(counter.count(), 7); } assert_eq!(counter.count(), 1); } ================================================ FILE: lock-analyzer/Cargo.toml ================================================ [package] name = "lock-analyzer" edition.workspace = true rust-version.workspace = true keywords.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true version.workspace = true authors.workspace = true publish = false [dependencies] ron.workspace = true anyhow.workspace = true [dependencies.serde] workspace = true features = ["default", "serde_derive"] [lints.clippy] disallowed_types = "allow" ================================================ FILE: lock-analyzer/src/main.rs ================================================ //! Analyzer for data produced by `wgpu-core`'s `observe_locks` feature. //! //! When `wgpu-core`'s `observe_locks` feature is enabled, if the //! `WGPU_CORE_LOCK_OBSERVE_DIR` environment variable is set to the //! path of an existing directory, then every thread that acquires a //! lock in `wgpu-core` will write its own log file to that directory. //! You can then run this program to read those files and summarize //! the results. //! //! This program also consults the `WGPU_CORE_LOCK_OBSERVE_DIR` //! environment variable to find the log files written by `wgpu-core`. //! //! See `wgpu_core/src/lock/observing.rs` for a general explanation of //! this analysis. use std::sync::Arc; use std::{ collections::{btree_map::Entry, BTreeMap, BTreeSet, HashMap}, fmt, path::PathBuf, }; use anyhow::{Context, Result}; fn main() -> Result<()> { let mut ranks: BTreeMap = BTreeMap::default(); let Ok(dir) = std::env::var("WGPU_CORE_LOCK_OBSERVE_DIR") else { eprintln!(concat!( "Please set the `WGPU_CORE_LOCK_OBSERVE_DIR` environment variable\n", "to the path of the directory containing the files written by\n", "`wgpu-core`'s `observe_locks` feature." )); anyhow::bail!("`WGPU_CORE_LOCK_OBSERVE_DIR` environment variable is not set"); }; let entries = std::fs::read_dir(&dir).with_context(|| format!("failed to read directory {dir}"))?; for entry in entries { let entry = entry.with_context(|| format!("failed to read directory entry from {dir}"))?; let name = PathBuf::from(&entry.file_name()); let Some(extension) = name.extension() else { eprintln!("Ignoring {}", name.display()); continue; }; if extension != "ron" { eprintln!("Ignoring {}", name.display()); continue; } let contents = std::fs::read(entry.path()) .with_context(|| format!("failed to read lock observations from {}", name.display()))?; // The addresses of `&'static Location<'static>` values could // vary from run to run. let mut locations: HashMap> = HashMap::default(); for line in contents.split(|&b| b == b'\n') { if line.is_empty() { continue; } let action = ron::de::from_bytes::(line) .with_context(|| format!("Error parsing action from {}", name.display()))?; match action { Action::Location { address, file, line, column, } => { let file = match file.split_once("src/") { Some((_, after)) => after.to_string(), None => file, }; assert!(locations .insert(address, Arc::new(Location { file, line, column })) .is_none()); } Action::Rank { bit, member_name, const_name, } => match ranks.entry(bit) { Entry::Occupied(occupied) => { let rank = occupied.get(); assert_eq!(rank.member_name, member_name); assert_eq!(rank.const_name, const_name); } Entry::Vacant(vacant) => { vacant.insert(Rank { member_name, const_name, acquisitions: BTreeMap::default(), }); } }, Action::Acquisition { older_rank, older_location, newer_rank, newer_location, } => { let older_location = locations[&older_location].clone(); let newer_location = locations[&newer_location].clone(); ranks .get_mut(&older_rank) .unwrap() .acquisitions .entry(newer_rank) .or_default() .entry(older_location) .or_default() .insert(newer_location); } } } } for older_rank in ranks.values() { if older_rank.is_leaf() { // We'll print leaf locks separately, below. continue; } println!( " rank {} {:?} followed by {{", older_rank.const_name, older_rank.member_name ); let mut acquired_any_leaf_locks = false; let mut first_newer = true; for (newer_rank, locations) in &older_rank.acquisitions { // List acquisitions of leaf locks at the end. if ranks[newer_rank].is_leaf() { acquired_any_leaf_locks = true; continue; } if !first_newer { println!(); } for (older_location, newer_locations) in locations { if newer_locations.len() == 1 { for newer_loc in newer_locations { println!(" // holding {older_location} while locking {newer_loc}"); } } else { println!(" // holding {older_location} while locking:"); for newer_loc in newer_locations { println!(" // {newer_loc}"); } } } println!(" {},", ranks[newer_rank].const_name); first_newer = false; } if acquired_any_leaf_locks { // We checked that older_rank isn't a leaf lock, so we // must have printed something above. if !first_newer { println!(); } println!(" // leaf lock acquisitions:"); for newer_rank in older_rank.acquisitions.keys() { if !ranks[newer_rank].is_leaf() { continue; } println!(" {},", ranks[newer_rank].const_name); } } println!(" }};"); println!(); } for older_rank in ranks.values() { if !older_rank.is_leaf() { continue; } println!( " rank {} {:?} followed by {{ }};", older_rank.const_name, older_rank.member_name ); } Ok(()) } #[derive(Debug, serde::Deserialize)] #[serde(deny_unknown_fields)] enum Action { /// A location that we will refer to in later actions. Location { address: LocationAddress, file: String, line: u32, column: u32, }, /// A lock rank that we will refer to in later actions. Rank { bit: u32, member_name: String, const_name: String, }, /// An attempt to acquire a lock while holding another lock. Acquisition { /// The number of the already acquired lock's rank. older_rank: u32, /// The source position at which we acquired it. Specifically, /// its `Location`'s address, as an integer. older_location: LocationAddress, /// The number of the rank of the lock we are acquiring. newer_rank: u32, /// The source position at which we are acquiring it. /// Specifically, its `Location`'s address, as an integer. newer_location: LocationAddress, }, } /// The memory address at which the `Location` was stored in the /// observed process. /// /// This is not `usize` because it does not represent an address in /// this `lock-analyzer` process. We might generate logs on a 64-bit /// machine and analyze them on a 32-bit machine. The `u64` type is a /// reasonable universal type for addresses on any machine. type LocationAddress = u64; struct Rank { member_name: String, const_name: String, acquisitions: BTreeMap, } impl Rank { fn is_leaf(&self) -> bool { self.acquisitions.is_empty() } } type LocationSet = BTreeMap, BTreeSet>>; #[derive(Eq, Ord, PartialEq, PartialOrd)] struct Location { file: String, line: u32, column: u32, } impl fmt::Display for Location { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}", self.file, self.line) } } ================================================ FILE: naga/.cargo/config.toml ================================================ [alias] xtask = "run -p naga-xtask --" ================================================ FILE: naga/.gitattributes ================================================ tests/naga/out/**/* text eol=lf ================================================ FILE: naga/.gitignore ================================================ /target **/*.rs.bk Cargo.lock .DS_Store .fuse_hidden* .idea .vscode *.swp /*.dot /*.metal /*.metallib /*.ron /*.spv /*.vert /*.frag /*.comp /*.wgsl /*.hlsl /*.txt ================================================ FILE: naga/CHANGELOG.md ================================================ # Change Log For changelogs after v0.14, see [the wgpu changelog](../CHANGELOG.md). ## v0.14 (2023-10-25) #### GENERAL - Add support for const-expressions. ([#2309](https://github.com/gfx-rs/naga/pull/2309)) **@teoxoy**, **@jimblandy** - Add support for the `rgb10a2uint` storage format. ([#2525](https://github.com/gfx-rs/naga/pull/2525)) **@teoxoy** - Implement module compaction for snapshot testing and the CLI. ([#2472](https://github.com/gfx-rs/naga/pull/2472)) **@jimblandy** - Fix validation and GLSL parsing of `ldexp`. ([#2449](https://github.com/gfx-rs/naga/pull/2449)) **@fornwall** - Add support for dual source blending. ([#2427](https://github.com/gfx-rs/naga/pull/2427)) **@freqmod** - Bump `indexmap` to v2. ([#2426](https://github.com/gfx-rs/naga/pull/2426)) **@daxpedda** - Bump MSRV to 1.65. ([#2420](https://github.com/gfx-rs/naga/pull/2420)) **@jimblandy** #### API - Split `UnaryOperator::Not` into `UnaryOperator::LogicalNot` & `UnaryOperator::BitwiseNot`. ([#2554](https://github.com/gfx-rs/naga/pull/2554)) **@teoxoy** - Remove `IsFinite` & `IsNormal` relational functions. ([#2532](https://github.com/gfx-rs/naga/pull/2532)) **@teoxoy** - Derive `PartialEq` on `Expression`. ([#2417](https://github.com/gfx-rs/naga/pull/2417)) **@robtfm** - Use `FastIndexMap` for `SpecialTypes::predeclared_types`. ([#2495](https://github.com/gfx-rs/naga/pull/2495)) **@jimblandy** #### CLI - Change `--generate-debug-symbols` from an `option` to a `switch`. ([#2472](https://github.com/gfx-rs/naga/pull/2472)) **@jimblandy** - Add support for `.{vert,frag,comp}.glsl` files. ([#2462](https://github.com/gfx-rs/naga/pull/2462)) **@eliemichel** #### VALIDATOR - Require `Capabilities::FLOAT64` for 64-bit floating-point literals. ([#2567](https://github.com/gfx-rs/naga/pull/2567)) **@jimblandy** - Add `Capabilities::CUBE_ARRAY_TEXTURES`. ([#2530](https://github.com/gfx-rs/naga/pull/2530)) **@teoxoy** - Disallow passing pointers to variables in the workgroup address space to functions. ([#2507](https://github.com/gfx-rs/naga/pull/2507)) **@teoxoy** - Avoid OOM with large sparse resource bindings. ([#2561](https://github.com/gfx-rs/naga/pull/2561)) **@teoxoy** - Require that `Function` and `Private` variables be `CONSTRUCTIBLE`. ([#2545](https://github.com/gfx-rs/naga/pull/2545)) **@jimblandy** - Disallow floating-point NaNs and infinities. ([#2508](https://github.com/gfx-rs/naga/pull/2508)) **@teoxoy** - Temporarily disable uniformity analysis for the fragment stage. ([#2515](https://github.com/gfx-rs/naga/pull/2515)) **@teoxoy** - Validate that `textureSampleBias` is only used in the fragment stage. ([#2515](https://github.com/gfx-rs/naga/pull/2515)) **@teoxoy** - Validate variable initializer for address spaces. ([#2513](https://github.com/gfx-rs/naga/pull/2513)) **@teoxoy** - Prevent using multiple push constant variables in one entry point. ([#2484](https://github.com/gfx-rs/naga/pull/2484)) **@andriyDev** - Validate `binding_array` variable address space. ([#2422](https://github.com/gfx-rs/naga/pull/2422)) **@teoxoy** - Validate storage buffer access. ([#2415](https://github.com/gfx-rs/naga/pull/2415)) **@teoxoy** #### WGSL-IN - Fix expected min arg count of `textureLoad`. ([#2584](https://github.com/gfx-rs/naga/pull/2584)) **@teoxoy** - Turn `Error::Other` into `Error::Internal`, to help devs. ([#2574](https://github.com/gfx-rs/naga/pull/2574)) **@jimblandy** - Fix OOB typifier indexing. ([#2570](https://github.com/gfx-rs/naga/pull/2570)) **@teoxoy** - Add support for the `bgra8unorm` storage format. ([#2542](https://github.com/gfx-rs/naga/pull/2542) & [#2550](https://github.com/gfx-rs/naga/pull/2550)) **@nical** - Remove the `outerProduct` built-in function. ([#2535](https://github.com/gfx-rs/naga/pull/2535)) **@teoxoy** - Add support for `i32` overload of the `sign` built-in function. ([#2463](https://github.com/gfx-rs/naga/pull/2463)) **@fornwall** - Properly implement `modf` and `frexp`. ([#2454](https://github.com/gfx-rs/naga/pull/2454)) **@fornwall** - Add support for scalar overloads of `all` & `any` built-in functions. ([#2445](https://github.com/gfx-rs/naga/pull/2445)) **@fornwall** - Don't splat the left hand operand of a binary operation if it's not a scalar. ([#2444](https://github.com/gfx-rs/naga/pull/2444)) **@fornwall** - Avoid splatting all binary operator expressions. ([#2440](https://github.com/gfx-rs/naga/pull/2440)) **@fornwall** - Error on repeated or missing `@workgroup_size()`. ([#2435](https://github.com/gfx-rs/naga/pull/2435)) **@fornwall** - Error on repeated attributes. ([#2428](https://github.com/gfx-rs/naga/pull/2428)) **@fornwall** - Fix error message for invalid `texture{Load,Store}()` on arrayed textures. ([#2432](https://github.com/gfx-rs/naga/pull/2432)) **@fornwall** #### SPV-IN - Disable `Modf` & `Frexp` and translate `ModfStruct` & `FrexpStruct` to their IR equivalents. ([#2527](https://github.com/gfx-rs/naga/pull/2527)) **@teoxoy** - Don't advertise support for `Capability::ImageMSArray` & `Capability::InterpolationFunction`. ([#2529](https://github.com/gfx-rs/naga/pull/2529)) **@teoxoy** - Fix `OpImageQueries` to allow Uints. ([#2404](https://github.com/gfx-rs/naga/pull/2404)) **@evahop** #### GLSL-IN - Disable `modf` & `frexp`. ([#2527](https://github.com/gfx-rs/naga/pull/2527)) **@teoxoy** #### SPV-OUT - Require `ClipDistance` & `CullDistance` capabilities if necessary. ([#2528](https://github.com/gfx-rs/naga/pull/2528)) **@teoxoy** - Change `naga::back::spv::DebugInfo::file_name` to a `&Path`. ([#2501](https://github.com/gfx-rs/naga/pull/2501)) **@jimblandy** - Always give structs with runtime arrays a `Block` decoration. ([#2455](https://github.com/gfx-rs/naga/pull/2455)) **@TheoDulka** - Decorate the result of the `OpLoad` with `NonUniform` (not the access chain) when loading images/samplers (resources in the Handle address space). ([#2422](https://github.com/gfx-rs/naga/pull/2422)) **@teoxoy** - Cache `OpConstantNull`. ([#2414](https://github.com/gfx-rs/naga/pull/2414)) **@evahop** #### MSL-OUT - Add and fix minimum Metal version checks for optional functionality. ([#2486](https://github.com/gfx-rs/naga/pull/2486)) **@teoxoy** - Make varyings' struct members unique. ([#2521](https://github.com/gfx-rs/naga/pull/2521)) **@evahop** - Add experimental vertex pulling transform flag. ([#5254](https://github.com/gfx-rs/wgpu/pull/5254)) **@bradwerth** - Fixup some generated MSL for vertex buffer unpack functions. ([#5829](https://github.com/gfx-rs/wgpu/pull/5829)) **@bradwerth** - Make vertex pulling transform on by default. ([#5773](https://github.com/gfx-rs/wgpu/pull/5773)) **@bradwerth** #### GLSL-OUT - Cull functions that should not be available for a given stage. ([#2531](https://github.com/gfx-rs/naga/pull/2531)) **@teoxoy** - Rename identifiers containing double underscores. ([#2510](https://github.com/gfx-rs/naga/pull/2510)) **@evahop** - Polyfill `frexp`. ([#2504](https://github.com/gfx-rs/naga/pull/2504)) **@evahop** - Add built-in functions to keywords. ([#2410](https://github.com/gfx-rs/naga/pull/2410)) **@fornwall** #### WGSL-OUT - Generate correct code for bit complement on integers. ([#2548](https://github.com/gfx-rs/naga/pull/2548)) **@jimblandy** - Don't include type parameter in splat expressions. ([#2469](https://github.com/gfx-rs/naga/pull/2469)) **@jimblandy** ## v0.13 (2023-07-21) #### GENERAL - Move from `make` to `cargo xtask` workflows. ([#2297](https://github.com/gfx-rs/naga/pull/2297)) **@ErichDonGubler** - Omit non referenced expressions from output. ([#2378](https://github.com/gfx-rs/naga/pull/2378)) **@teoxoy** - Bump `bitflags` to v2. ([#2358](https://github.com/gfx-rs/naga/pull/2358)) **@daxpedda** - Implement `workgroupUniformLoad`. ([#2201](https://github.com/gfx-rs/naga/pull/2201)) **@DJMcNab** #### API - Expose early depth test field. ([#2393](https://github.com/gfx-rs/naga/pull/2393)) **@Joeoc2001** - Split image bounds check policy. ([#2265](https://github.com/gfx-rs/naga/pull/2265)) **@teoxoy** - Change type of constant sized arrays to `NonZeroU32`. ([#2337](https://github.com/gfx-rs/naga/pull/2337)) **@teoxoy** - Introduce `GlobalCtx`. ([#2335](https://github.com/gfx-rs/naga/pull/2335)) **@teoxoy** - Introduce `Expression::Literal`. ([#2333](https://github.com/gfx-rs/naga/pull/2333)) **@teoxoy** - Introduce `Expression::ZeroValue`. ([#2332](https://github.com/gfx-rs/naga/pull/2332)) **@teoxoy** - Add support for const-expressions (only at the API level, functionality is still WIP). ([#2266](https://github.com/gfx-rs/naga/pull/2266)) **@teoxoy**, **@jimblandy** #### DOCS - Document which expressions are in scope for a `break_if` expression. ([#2326](https://github.com/gfx-rs/naga/pull/2326)) **@jimblandy** #### VALIDATOR - Don't `use std::opsIndex`, used only when `"validate"` is on. ([#2383](https://github.com/gfx-rs/naga/pull/2383)) **@jimblandy** - Remove unneeded `ConstantError::Unresolved{Component,Size}`. ([#2330](https://github.com/gfx-rs/naga/pull/2330)) **@ErichDonGubler** - Remove `TypeError::UnresolvedBase`. ([#2308](https://github.com/gfx-rs/naga/pull/2308)) **@ErichDonGubler** #### WGSL-IN - Error on param redefinition. ([#2342](https://github.com/gfx-rs/naga/pull/2342)) **@SparkyPotato** #### SPV-IN - Improve documentation for SPIR-V control flow parsing. ([#2324](https://github.com/gfx-rs/naga/pull/2324)) **@jimblandy** - Obey the `is_depth` field of `OpTypeImage`. ([#2341](https://github.com/gfx-rs/naga/pull/2341)) **@expenses** - Convert conditional backedges to `break if`. ([#2290](https://github.com/gfx-rs/naga/pull/2290)) **@eddyb** #### GLSL-IN - Support commas in structure definitions. ([#2400](https://github.com/gfx-rs/naga/pull/2400)) **@fornwall** #### SPV-OUT - Add debug info. ([#2379](https://github.com/gfx-rs/naga/pull/2379)) **@wicast** - Use `IndexSet` instead of `HashSet` for iterated sets (capabilities/extensions). ([#2389](https://github.com/gfx-rs/naga/pull/2389)) **@eddyb** - Support array bindings of buffers. ([#2282](https://github.com/gfx-rs/naga/pull/2282)) **@kvark** #### MSL-OUT - Rename `allow_point_size` to `allow_and_force_point_size`. ([#2280](https://github.com/gfx-rs/naga/pull/2280)) **@teoxoy** - Initialize arrays inline. ([#2331](https://github.com/gfx-rs/naga/pull/2331)) **@teoxoy** #### HLSL-OUT - Implement Pack/Unpack for HLSL. ([#2353](https://github.com/gfx-rs/naga/pull/2353)) **@Elabajaba** - Complete HLSL reserved symbols. ([#2367](https://github.com/gfx-rs/naga/pull/2367)) **@teoxoy** - Handle case insensitive FXC keywords. ([#2347](https://github.com/gfx-rs/naga/pull/2347)) **@PJB3005** - Fix return type for firstbitlow/high. ([#2315](https://github.com/gfx-rs/naga/pull/2315)) **@evahop** #### GLSL-OUT - `textureSize` level must be a signed integer. ([#2397](https://github.com/gfx-rs/naga/pull/2397)) **@nical** - Fix functions with array return type. ([#2382](https://github.com/gfx-rs/naga/pull/2382)) **@Gordon-F** #### WGSL-OUT - Output `@interpolate(flat)` attribute for integer locations. ([#2318](https://github.com/gfx-rs/naga/pull/2318)) **@expenses** ## v0.12.3 (2023-07-09) #### WGSL-OUT - (Backport) Output `@interpolate(flat)` attribute for integer locations. ([#2318](https://github.com/gfx-rs/naga/pull/2318)) **@expenses** ## v0.12.2 (2023-05-30) #### SPV-OUT - (Backport) Support array bindings of buffers. ([#2282](https://github.com/gfx-rs/naga/pull/2282)) **@kvark** ## v0.12.1 (2023-05-18) #### SPV-IN - (Backport) Convert conditional backedges to `break if`. ([#2290](https://github.com/gfx-rs/naga/pull/2290)) **@eddyb** ## v0.12 (2023-04-19) #### GENERAL - Allow `array_index` to be unsigned. ([#2298](https://github.com/gfx-rs/naga/pull/2298)) **@daxpedda** - Add ray query support. ([#2256](https://github.com/gfx-rs/naga/pull/2256)) **@kvark** - Add partial derivative builtins. ([#2277](https://github.com/gfx-rs/naga/pull/2277)) **@evahop** - Skip `gl_PerVertex` unused builtins in the SPIR-V frontend. ([#2272](https://github.com/gfx-rs/naga/pull/2272)) **@teoxoy** - Differentiate between `i32` and `u32` in switch statement cases. ([#2269](https://github.com/gfx-rs/naga/pull/2269)) **@evahop** - Fix zero initialization of workgroup memory. ([#2259](https://github.com/gfx-rs/naga/pull/2259)) **@teoxoy** - Add `countTrailingZeros`. ([#2243](https://github.com/gfx-rs/naga/pull/2243)) **@gents83** - Fix texture built-ins where u32 was expected. ([#2245](https://github.com/gfx-rs/naga/pull/2245)) **@evahop** - Add `countLeadingZeros`. ([#2226](https://github.com/gfx-rs/naga/pull/2226)) **@evahop** - [glsl/hlsl-out] Write sizes of arrays behind pointers in function arguments. ([#2250](https://github.com/gfx-rs/naga/pull/2250)) **@pluiedev** #### VALIDATOR - Validate vertex stage returns the position built-in. ([#2264](https://github.com/gfx-rs/naga/pull/2264)) **@teoxoy** - Enforce discard is only used in the fragment stage. ([#2262](https://github.com/gfx-rs/naga/pull/2262)) **@Uriopass** - Add `Capabilities::MULTISAMPLED_SHADING`. ([#2255](https://github.com/gfx-rs/naga/pull/2255)) **@teoxoy** - Add `Capabilities::EARLY_DEPTH_TEST`. ([#2255](https://github.com/gfx-rs/naga/pull/2255)) **@teoxoy** - Add `Capabilities::MULTIVIEW`. ([#2255](https://github.com/gfx-rs/naga/pull/2255)) **@teoxoy** - Improve forward declaration validation. ([#2232](https://github.com/gfx-rs/naga/pull/2232)) **@JCapucho** #### WGSL-IN - Use `alias` instead of `type` for type aliases. ([#2299](https://github.com/gfx-rs/naga/pull/2299)) **@FL33TW00D** - Add predeclared vector and matrix type aliases. ([#2251](https://github.com/gfx-rs/naga/pull/2251)) **@evahop** - Improve invalid assignment diagnostic. ([#2233](https://github.com/gfx-rs/naga/pull/2233)) **@SparkyPotato** - Expect semicolons wherever required. ([#2233](https://github.com/gfx-rs/naga/pull/2233)) **@SparkyPotato** - Fix panic on invalid zero array size. ([#2233](https://github.com/gfx-rs/naga/pull/2233)) **@SparkyPotato** - Check for leading `{` while parsing a block. ([#2233](https://github.com/gfx-rs/naga/pull/2233)) **@SparkyPotato** #### SPV-IN - Don't apply interpolation to fragment shaders outputs. ([#2239](https://github.com/gfx-rs/naga/pull/2239)) **@JCapucho** #### GLSL-IN - Add switch implicit type conversion. ([#2273](https://github.com/gfx-rs/naga/pull/2273)) **@evahop** - Document some fields of `naga::front::glsl::context::Context`. ([#2244](https://github.com/gfx-rs/naga/pull/2244)) **@jimblandy** - Perform output parameters implicit casts. ([#2063](https://github.com/gfx-rs/naga/pull/2063)) **@JCapucho** - Add `not` vector relational builtin. ([#2227](https://github.com/gfx-rs/naga/pull/2227)) **@JCapucho** - Add double overloads for relational vector builtins. ([#2227](https://github.com/gfx-rs/naga/pull/2227)) **@JCapucho** - Add bool overloads for relational vector builtins. ([#2227](https://github.com/gfx-rs/naga/pull/2227)) **@JCapucho** #### SPV-OUT - Fix invalid spirv being generated from integer dot products. ([#2291](https://github.com/gfx-rs/naga/pull/2291)) **@PyryM** - Fix adding illegal decorators on fragment outputs. ([#2286](https://github.com/gfx-rs/naga/pull/2286)) **@Wumpf** - Fix `countLeadingZeros` impl. ([#2258](https://github.com/gfx-rs/naga/pull/2258)) **@teoxoy** - Cache constant composites. ([#2257](https://github.com/gfx-rs/naga/pull/2257)) **@evahop** - Support SPIR-V version 1.4. ([#2230](https://github.com/gfx-rs/naga/pull/2230)) **@kvark** #### MSL-OUT - Replace `per_stage_map` with `per_entry_point_map` ([#2237](https://github.com/gfx-rs/naga/pull/2237)) **@armansito** - Update `firstLeadingBit` for signed integers ([#2235](https://github.com/gfx-rs/naga/pull/2235)) **@evahop** #### HLSL-OUT - Use `Interlocked` intrinsic for atomic integers (#2294) ([#2294](https://github.com/gfx-rs/naga/pull/2294)) **@ErichDonGubler** - Document storage access generation. ([#2295](https://github.com/gfx-rs/naga/pull/2295)) **@jimblandy** - Emit constructor functions for arrays. ([#2281](https://github.com/gfx-rs/naga/pull/2281)) **@ErichDonGubler** - Clear `named_expressions` inserted by duplicated blocks. ([#2116](https://github.com/gfx-rs/naga/pull/2116)) **@teoxoy** #### GLSL-OUT - Skip `invariant` for `gl_FragCoord` on WebGL2. ([#2254](https://github.com/gfx-rs/naga/pull/2254)) **@grovesNL** - Inject default `gl_PointSize = 1.0` in vertex shaders if `FORCE_POINT_SIZE` option was set. ([#2223](https://github.com/gfx-rs/naga/pull/2223)) **@REASY** ## v0.11.1 (2023-05-18) #### SPV-IN - (Backport) Convert conditional backedges to `break if`. ([#2290](https://github.com/gfx-rs/naga/pull/2290)) **@eddyb** ## v0.11 (2023-01-25) - Move to the Rust 2021 edition ([#2085](https://github.com/gfx-rs/naga/pull/2085)) **@ErichDonGubler** - Bump MSRV to 1.63 ([#2129](https://github.com/gfx-rs/naga/pull/2129)) **@teoxoy** #### API - Add handle validation pass to `Validator` ([#2090](https://github.com/gfx-rs/naga/pull/2090)) **@ErichDonGubler** - Add `Range::new_from_bounds` ([#2148](https://github.com/gfx-rs/naga/pull/2148)) **@robtfm** #### DOCS - Fix docs for `Emit` statements ([#2208](https://github.com/gfx-rs/naga/pull/2208)) **@jimblandy** - Fix invalid `<...>` URLs with code spans ([#2176](https://github.com/gfx-rs/naga/pull/2176)) **@ErichDonGubler** - Explain how case clauses with multiple selectors are supported ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** - Document `EarlyDepthTest` and `ConservativeDepth` syntax ([#2132](https://github.com/gfx-rs/naga/pull/2132)) **@coreh** #### VALIDATOR - Allow `u32` coordinates for `textureStore`/`textureLoad` ([#2172](https://github.com/gfx-rs/naga/pull/2172)) **@PENGUINLIONG** - Fix array being flagged as constructible when its base isn't ([#2111](https://github.com/gfx-rs/naga/pull/2111)) **@teoxoy** - Add `type_flags` to `ModuleInfo` ([#2111](https://github.com/gfx-rs/naga/pull/2111)) **@teoxoy** - Remove overly restrictive array stride check ([#2215](https://github.com/gfx-rs/naga/pull/2215)) **@fintelia** - Let the uniformity analysis trust the handle validation pass ([#2200](https://github.com/gfx-rs/naga/pull/2200)) **@jimblandy** - Fix warnings when building tests without validation ([#2177](https://github.com/gfx-rs/naga/pull/2177)) **@jimblandy** - Add `ValidationFlags::BINDINGS` ([#2156](https://github.com/gfx-rs/naga/pull/2156)) **@kvark** - Fix `textureGather` on `texture_2d` ([#2138](https://github.com/gfx-rs/naga/pull/2138)) **@JMS55** #### ALL (FRONTENDS/BACKENDS) - Support 16-bit unorm/snorm formats ([#2210](https://github.com/gfx-rs/naga/pull/2210)) **@fintelia** - Support `gl_PointCoord` ([#2180](https://github.com/gfx-rs/naga/pull/2180)) **@Neo-Zhixing** #### ALL BACKENDS - Add support for zero-initializing workgroup memory ([#2111](https://github.com/gfx-rs/naga/pull/2111)) **@teoxoy** #### WGSL-IN - Implement module-level scoping ([#2075](https://github.com/gfx-rs/naga/pull/2075)) **@SparkyPotato** - Remove `isFinite` and `isNormal` ([#2218](https://github.com/gfx-rs/naga/pull/2218)) **@evahop** - Update inverse hyperbolic built-ins ([#2218](https://github.com/gfx-rs/naga/pull/2218)) **@evahop** - Add `refract` built-in ([#2218](https://github.com/gfx-rs/naga/pull/2218)) **@evahop** - Update reserved keywords ([#2130](https://github.com/gfx-rs/naga/pull/2130)) **@teoxoy** - Remove non-32bit integers ([#2146](https://github.com/gfx-rs/naga/pull/2146)) **@teoxoy** - Remove `workgroup_size` builtin ([#2147](https://github.com/gfx-rs/naga/pull/2147)) **@teoxoy** - Remove fallthrough statement ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** #### SPV-IN - Support binding arrays ([#2199](https://github.com/gfx-rs/naga/pull/2199)) **@Patryk27** #### GLSL-IN - Fix position propagation in lowering ([#2079](https://github.com/gfx-rs/naga/pull/2079)) **@JCapucho** - Update initializer list type when parsing ([#2066](https://github.com/gfx-rs/naga/pull/2066)) **@JCapucho** - Parenthesize unary negations to avoid `--` ([#2087](https://github.com/gfx-rs/naga/pull/2087)) **@ErichDonGubler** #### SPV-OUT - Add support for `atomicCompareExchangeWeak` ([#2165](https://github.com/gfx-rs/naga/pull/2165)) **@aweinstock314** - Omit extra switch case blocks where possible ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** - Fix switch cases after default not being output ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** #### MSL-OUT - Don't panic on missing bindings ([#2175](https://github.com/gfx-rs/naga/pull/2175)) **@kvark** - Omit extra switch case blocks where possible ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** - Fix `textureGather` compatibility on macOS 10.13 ([#2104](https://github.com/gfx-rs/naga/pull/2104)) **@xiaopengli89** - Fix incorrect atomic bounds check on metal back-end ([#2099](https://github.com/gfx-rs/naga/pull/2099)) **@raphlinus** - Parenthesize unary negations to avoid `--` ([#2087](https://github.com/gfx-rs/naga/pull/2087)) **@ErichDonGubler** #### HLSL-OUT - Simplify `write_default_init` ([#2111](https://github.com/gfx-rs/naga/pull/2111)) **@teoxoy** - Omit extra switch case blocks where possible ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** - Properly implement bitcast ([#2097](https://github.com/gfx-rs/naga/pull/2097)) **@cwfitzgerald** - Fix storage access chain through a matrix ([#2097](https://github.com/gfx-rs/naga/pull/2097)) **@cwfitzgerald** - Workaround FXC Bug in Matrix Indexing ([#2096](https://github.com/gfx-rs/naga/pull/2096)) **@cwfitzgerald** - Parenthesize unary negations to avoid `--` ([#2087](https://github.com/gfx-rs/naga/pull/2087)) **@ErichDonGubler** #### GLSL-OUT - Introduce a flag to include unused items ([#2205](https://github.com/gfx-rs/naga/pull/2205)) **@robtfm** - Use `fma` polyfill for versions below gles 320 ([#2197](https://github.com/gfx-rs/naga/pull/2197)) **@teoxoy** - Emit reflection info for non-struct uniforms ([#2189](https://github.com/gfx-rs/naga/pull/2189)) **@Rainb0wCodes** - Introduce a new block for switch cases ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** #### WGSL-OUT - Write correct scalar kind when `width != 4` ([#1514](https://github.com/gfx-rs/naga/pull/1514)) **@fintelia** ## v0.10.1 (2023-06-21) SPV-OUT - Backport #2389 (Use `IndexSet` instead of `HashSet` for iterated sets (capabilities/extensions)) by @eddyb, @jimblandy in https://github.com/gfx-rs/naga/pull/2391 SPV-IN - Backport #2290 (Convert conditional backedges to `break if`) by @eddyb in https://github.com/gfx-rs/naga/pull/2387 ## v0.10 (2022-10-05) - Make termcolor dependency optional by @AldaronLau in https://github.com/gfx-rs/naga/pull/2014 - Fix clippy lints for 1.63 by @JCapucho in https://github.com/gfx-rs/naga/pull/2026 - Saturate by @evahop in https://github.com/gfx-rs/naga/pull/2025 - Use `Option::as_deref` as appropriate. by @jimblandy in https://github.com/gfx-rs/naga/pull/2040 - Explicitly enable std for indexmap by @maxammann in https://github.com/gfx-rs/naga/pull/2062 - Fix compiler warning by @Gordon-F in https://github.com/gfx-rs/naga/pull/2074 API - Implement `Clone` for `Module` by @daxpedda in https://github.com/gfx-rs/naga/pull/2013 - Remove the glsl-validate feature by @JCapucho in https://github.com/gfx-rs/naga/pull/2045 DOCS - Document arithmetic binary operation type rules. by @jimblandy in https://github.com/gfx-rs/naga/pull/2051 VALIDATOR - Add `emit_to_{stderr,string}` helpers to validation error by @nolanderc in https://github.com/gfx-rs/naga/pull/2012 - Check regular functions don't have bindings by @JCapucho in https://github.com/gfx-rs/naga/pull/2050 WGSL-IN - Update reserved WGSL keywords by @norepimorphism in https://github.com/gfx-rs/naga/pull/2009 - Implement lexical scopes by @JCapucho in https://github.com/gfx-rs/naga/pull/2024 - Rename `Scope` to `Rule`, since we now have lexical scope. by @jimblandy in https://github.com/gfx-rs/naga/pull/2042 - Splat on compound assignments by @JCapucho in https://github.com/gfx-rs/naga/pull/2049 - Fix bad span in assignment lhs error by @JCapucho in https://github.com/gfx-rs/naga/pull/2054 - Fix inclusion of trivia in spans by @SparkyPotato in https://github.com/gfx-rs/naga/pull/2055 - Improve assignment diagnostics by @SparkyPotato in https://github.com/gfx-rs/naga/pull/2056 - Break up long string, reformat rest of file. by @jimblandy in https://github.com/gfx-rs/naga/pull/2057 - Fix line endings on wgsl reserved words list. by @jimblandy in https://github.com/gfx-rs/naga/pull/2059 GLSL-IN - Add support for .length() by @SpaceCat-Chan in https://github.com/gfx-rs/naga/pull/2017 - Fix missing stores for local declarations by @adeline-sparks in https://github.com/gfx-rs/naga/pull/2029 - Migrate to `SymbolTable` by @JCapucho in https://github.com/gfx-rs/naga/pull/2044 - Update initializer list type when parsing by @JCapucho in https://github.com/gfx-rs/naga/pull/2066 SPV-OUT - Don't decorate varyings with interpolation modes at pipeline start/end by @nical in https://github.com/gfx-rs/naga/pull/2038 - Decorate integer builtins as Flat in the spirv writer by @nical in https://github.com/gfx-rs/naga/pull/2035 - Properly combine the fixes for #2035 and #2038. by @jimblandy in https://github.com/gfx-rs/naga/pull/2041 - Don't emit no-op `OpBitCast` instructions. by @jimblandy in https://github.com/gfx-rs/naga/pull/2043 HLSL-OUT - Use the namer to sanitise entrypoint input/output struct names by @expenses in https://github.com/gfx-rs/naga/pull/2001 - Handle Unpack2x16float in hlsl by @expenses in https://github.com/gfx-rs/naga/pull/2002 - Add support for push constants by @JCapucho in https://github.com/gfx-rs/naga/pull/2005 DOT-OUT - Improvements by @JCapucho in https://github.com/gfx-rs/naga/pull/1987 ## v0.9 (2022-06-30) - Fix minimal-versions of dependencies ([#1840](https://github.com/gfx-rs/naga/pull/1840)) **@teoxoy** - Update MSRV to 1.56 ([#1838](https://github.com/gfx-rs/naga/pull/1838)) **@teoxoy** API - Rename `TypeFlags` `INTERFACE`/`HOST_SHARED` to `IO_SHARED`/`HOST_SHAREABLE` ([#1872](https://github.com/gfx-rs/naga/pull/1872)) **@jimblandy** - Expose more error information ([#1827](https://github.com/gfx-rs/naga/pull/1827), [#1937](https://github.com/gfx-rs/naga/pull/1937)) **@jakobhellermann** **@nical** **@jimblandy** - Do not unconditionally make error output colorful ([#1707](https://github.com/gfx-rs/naga/pull/1707)) **@rhysd** - Rename `StorageClass` to `AddressSpace` ([#1699](https://github.com/gfx-rs/naga/pull/1699)) **@kvark** - Add a way to emit errors to a path ([#1640](https://github.com/gfx-rs/naga/pull/1640)) **@laptou** CLI - Add `bincode` representation ([#1729](https://github.com/gfx-rs/naga/pull/1729)) **@kvark** - Include file path in WGSL parse error ([#1708](https://github.com/gfx-rs/naga/pull/1708)) **@rhysd** - Add `--version` flag ([#1706](https://github.com/gfx-rs/naga/pull/1706)) **@rhysd** - Support reading input from stdin via `--stdin-file-path` ([#1701](https://github.com/gfx-rs/naga/pull/1701)) **@rhysd** - Use `panic = "abort"` ([#1597](https://github.com/gfx-rs/naga/pull/1597)) **@jrmuizel** DOCS - Standardize some docs ([#1660](https://github.com/gfx-rs/naga/pull/1660)) **@NoelTautges** - Document `TypeInner::BindingArray` ([#1859](https://github.com/gfx-rs/naga/pull/1859)) **@jimblandy** - Clarify accepted types for `Expression::AccessIndex` ([#1862](https://github.com/gfx-rs/naga/pull/1862)) **@NoelTautges** - Document `proc::layouter` ([#1693](https://github.com/gfx-rs/naga/pull/1693)) **@jimblandy** - Document Naga's promises around validation and panics ([#1828](https://github.com/gfx-rs/naga/pull/1828)) **@jimblandy** - `FunctionInfo` doc fixes ([#1726](https://github.com/gfx-rs/naga/pull/1726)) **@jimblandy** VALIDATOR - Forbid returning pointers and atomics from functions ([#911](https://github.com/gfx-rs/naga/pull/911)) **@jimblandy** - Let validation check for more unsupported builtins ([#1962](https://github.com/gfx-rs/naga/pull/1962)) **@jimblandy** - Fix `Capabilities::SAMPLER_NON_UNIFORM_INDEXING` bitflag ([#1915](https://github.com/gfx-rs/naga/pull/1915)) **@cwfitzgerald** - Properly check that user-defined IO uses IO-shareable types ([#912](https://github.com/gfx-rs/naga/pull/912)) **@jimblandy** - Validate `ValuePointer` exactly like a `Pointer` to a `Scalar` ([#1875](https://github.com/gfx-rs/naga/pull/1875)) **@jimblandy** - Reject empty structs ([#1826](https://github.com/gfx-rs/naga/pull/1826)) **@jimblandy** - Validate uniform address space layout constraints ([#1812](https://github.com/gfx-rs/naga/pull/1812)) **@teoxoy** - Improve `AddressSpace` related error messages ([#1710](https://github.com/gfx-rs/naga/pull/1710)) **@kvark** WGSL-IN Main breaking changes - Commas to separate struct members (comma after last member is optional) - `struct S { a: f32; b: i32; }` -> `struct S { a: f32, b: i32 }` - Attribute syntax - `[[binding(0), group(0)]]` -> `@binding(0) @group(0)` - Entry point stage attributes - `@stage(vertex)` -> `@vertex` - `@stage(fragment)` -> `@fragment` - `@stage(compute)` -> `@compute` - Function renames - `smoothStep` -> `smoothstep` - `findLsb` -> `firstTrailingBit` - `findMsb` -> `firstLeadingBit` Specification Changes (relevant changes have also been applied to the WGSL backend) - Add support for `break if` ([#1993](https://github.com/gfx-rs/naga/pull/1993)) **@JCapucho** - Update number literal format ([#1863](https://github.com/gfx-rs/naga/pull/1863)) **@teoxoy** - Allow non-ascii characters in identifiers ([#1849](https://github.com/gfx-rs/naga/pull/1849)) **@teoxoy** - Update reserved keywords ([#1847](https://github.com/gfx-rs/naga/pull/1847), [#1870](https://github.com/gfx-rs/naga/pull/1870), [#1905](https://github.com/gfx-rs/naga/pull/1905)) **@teoxoy** **@Gordon-F** - Update entry point stage attributes ([#1833](https://github.com/gfx-rs/naga/pull/1833)) **@Gordon-F** - Make colon in case optional ([#1801](https://github.com/gfx-rs/naga/pull/1801)) **@Gordon-F** - Rename `smoothStep` to `smoothstep` ([#1800](https://github.com/gfx-rs/naga/pull/1800)) **@Gordon-F** - Make semicolon after struct declaration optional ([#1791](https://github.com/gfx-rs/naga/pull/1791)) **@stshine** - Use commas to separate struct members instead of semicolons ([#1773](https://github.com/gfx-rs/naga/pull/1773)) **@Gordon-F** - Rename `findLsb`/`findMsb` to `firstTrailingBit`/`firstLeadingBit` ([#1735](https://github.com/gfx-rs/naga/pull/1735)) **@kvark** - Make parenthesis optional for `if` and `switch` statements ([#1725](https://github.com/gfx-rs/naga/pull/1725)) **@Gordon-F** - Declare attributes with `@attrib` instead of `[[attrib]]` ([#1676](https://github.com/gfx-rs/naga/pull/1676)) **@kvark** - Allow non-structure buffer types ([#1682](https://github.com/gfx-rs/naga/pull/1682)) **@kvark** - Remove `stride` attribute ([#1681](https://github.com/gfx-rs/naga/pull/1681)) **@kvark** Improvements - Implement complete validation for size and align attributes ([#1979](https://github.com/gfx-rs/naga/pull/1979)) **@teoxoy** - Implement `firstTrailingBit`/`firstLeadingBit` u32 overloads ([#1865](https://github.com/gfx-rs/naga/pull/1865)) **@teoxoy** - Add error for non-floating-point matrix ([#1917](https://github.com/gfx-rs/naga/pull/1917)) **@grovesNL** - Implement partial vector & matrix identity constructors ([#1916](https://github.com/gfx-rs/naga/pull/1916)) **@teoxoy** - Implement phony assignment ([#1866](https://github.com/gfx-rs/naga/pull/1866), [#1869](https://github.com/gfx-rs/naga/pull/1869)) **@teoxoy** - Fix being able to match `~=` as LogicalOperation ([#1849](https://github.com/gfx-rs/naga/pull/1849)) **@teoxoy** - Implement Binding Arrays ([#1845](https://github.com/gfx-rs/naga/pull/1845)) **@cwfitzgerald** - Implement unary vector operators ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** - Implement zero value constructors and constructors that infer their type from their parameters ([#1790](https://github.com/gfx-rs/naga/pull/1790)) **@teoxoy** - Implement invariant attribute ([#1789](https://github.com/gfx-rs/naga/pull/1789), [#1822](https://github.com/gfx-rs/naga/pull/1822)) **@teoxoy** **@jimblandy** - Implement increment and decrement statements ([#1788](https://github.com/gfx-rs/naga/pull/1788), [#1912](https://github.com/gfx-rs/naga/pull/1912)) **@teoxoy** - Implement `while` loop ([#1787](https://github.com/gfx-rs/naga/pull/1787)) **@teoxoy** - Fix array size on globals ([#1717](https://github.com/gfx-rs/naga/pull/1717)) **@jimblandy** - Implement integer vector overloads for `dot` function ([#1689](https://github.com/gfx-rs/naga/pull/1689)) **@francesco-cattoglio** - Implement block comments ([#1675](https://github.com/gfx-rs/naga/pull/1675)) **@kocsis1david** - Implement assignment binary operators ([#1662](https://github.com/gfx-rs/naga/pull/1662)) **@kvark** - Implement `radians`/`degrees` builtin functions ([#1627](https://github.com/gfx-rs/naga/pull/1627)) **@encounter** - Implement `findLsb`/`findMsb` builtin functions ([#1473](https://github.com/gfx-rs/naga/pull/1473)) **@fintelia** - Implement `textureGather`/`textureGatherCompare` builtin functions ([#1596](https://github.com/gfx-rs/naga/pull/1596)) **@kvark** SPV-IN - Implement `OpBitReverse` and `OpBitCount` ([#1954](https://github.com/gfx-rs/naga/pull/1954)) **@JCapucho** - Add `MultiView` to `SUPPORTED_CAPABILITIES` ([#1934](https://github.com/gfx-rs/naga/pull/1934)) **@expenses** - Translate `OpSMod` and `OpFMod` correctly ([#1867](https://github.com/gfx-rs/naga/pull/1867), [#1995](https://github.com/gfx-rs/naga/pull/1995)) **@teoxoy** **@JCapucho** - Error on unsupported `MatrixStride` ([#1805](https://github.com/gfx-rs/naga/pull/1805)) **@teoxoy** - Align array stride for undecorated arrays ([#1724](https://github.com/gfx-rs/naga/pull/1724)) **@JCapucho** GLSL-IN - Don't allow empty last case in switch ([#1981](https://github.com/gfx-rs/naga/pull/1981)) **@JCapucho** - Fix last case fallthrough and empty switch ([#1981](https://github.com/gfx-rs/naga/pull/1981)) **@JCapucho** - Splat inputs for smoothstep if needed ([#1976](https://github.com/gfx-rs/naga/pull/1976)) **@JCapucho** - Fix parameter not changing to depth ([#1967](https://github.com/gfx-rs/naga/pull/1967)) **@JCapucho** - Fix matrix multiplication check ([#1953](https://github.com/gfx-rs/naga/pull/1953)) **@JCapucho** - Fix panic (stop emitter in conditional) ([#1952](https://github.com/gfx-rs/naga/pull/1952)) **@JCapucho** - Translate `mod` fn correctly ([#1867](https://github.com/gfx-rs/naga/pull/1867)) **@teoxoy** - Make the ternary operator behave as an if ([#1877](https://github.com/gfx-rs/naga/pull/1877)) **@JCapucho** - Add support for `clamp` function ([#1502](https://github.com/gfx-rs/naga/pull/1502)) **@sjinno** - Better errors for bad constant expression ([#1501](https://github.com/gfx-rs/naga/pull/1501)) **@sjinno** - Error on a `matCx2` used with the `std140` layout ([#1806](https://github.com/gfx-rs/naga/pull/1806)) **@teoxoy** - Allow nested accesses in lhs positions ([#1794](https://github.com/gfx-rs/naga/pull/1794)) **@JCapucho** - Use forced conversions for vector/matrix constructors ([#1796](https://github.com/gfx-rs/naga/pull/1796)) **@JCapucho** - Add support for `barrier` function ([#1793](https://github.com/gfx-rs/naga/pull/1793)) **@fintelia** - Fix panic (resume expression emit after `imageStore`) ([#1795](https://github.com/gfx-rs/naga/pull/1795)) **@JCapucho** - Allow multiple array specifiers ([#1780](https://github.com/gfx-rs/naga/pull/1780)) **@JCapucho** - Fix memory qualifiers being inverted ([#1779](https://github.com/gfx-rs/naga/pull/1779)) **@JCapucho** - Support arrays as input/output types ([#1759](https://github.com/gfx-rs/naga/pull/1759)) **@JCapucho** - Fix freestanding constructor parsing ([#1758](https://github.com/gfx-rs/naga/pull/1758)) **@JCapucho** - Fix matrix - scalar operations ([#1757](https://github.com/gfx-rs/naga/pull/1757)) **@JCapucho** - Fix matrix - matrix division ([#1757](https://github.com/gfx-rs/naga/pull/1757)) **@JCapucho** - Fix matrix comparisons ([#1757](https://github.com/gfx-rs/naga/pull/1757)) **@JCapucho** - Add support for `texelFetchOffset` ([#1746](https://github.com/gfx-rs/naga/pull/1746)) **@JCapucho** - Inject `sampler2DMSArray` builtins on use ([#1737](https://github.com/gfx-rs/naga/pull/1737)) **@JCapucho** - Inject `samplerCubeArray` builtins on use ([#1736](https://github.com/gfx-rs/naga/pull/1736)) **@JCapucho** - Add support for image builtin functions ([#1723](https://github.com/gfx-rs/naga/pull/1723)) **@JCapucho** - Add support for image declarations ([#1723](https://github.com/gfx-rs/naga/pull/1723)) **@JCapucho** - Texture builtins fixes ([#1719](https://github.com/gfx-rs/naga/pull/1719)) **@JCapucho** - Type qualifiers rework ([#1713](https://github.com/gfx-rs/naga/pull/1713)) **@JCapucho** - `texelFetch` accept multisampled textures ([#1715](https://github.com/gfx-rs/naga/pull/1715)) **@JCapucho** - Fix panic when culling nested block ([#1714](https://github.com/gfx-rs/naga/pull/1714)) **@JCapucho** - Fix composite constructors ([#1631](https://github.com/gfx-rs/naga/pull/1631)) **@JCapucho** - Fix using swizzle as out arguments ([#1632](https://github.com/gfx-rs/naga/pull/1632)) **@JCapucho** SPV-OUT - Implement `reverseBits` and `countOneBits` ([#1897](https://github.com/gfx-rs/naga/pull/1897)) **@hasali19** - Use `OpCopyObject` for matrix identity casts ([#1916](https://github.com/gfx-rs/naga/pull/1916)) **@teoxoy** - Use `OpCopyObject` for bool - bool conversion due to `OpBitcast` not being feasible for booleans ([#1916](https://github.com/gfx-rs/naga/pull/1916)) **@teoxoy** - Zero init variables in function and private address spaces ([#1871](https://github.com/gfx-rs/naga/pull/1871)) **@teoxoy** - Use `SRem` instead of `SMod` ([#1867](https://github.com/gfx-rs/naga/pull/1867)) **@teoxoy** - Add support for integer vector - scalar multiplication ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** - Add support for matrix addition and subtraction ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** - Emit required decorations on wrapper struct types ([#1815](https://github.com/gfx-rs/naga/pull/1815)) **@jimblandy** - Decorate array and struct type layouts unconditionally ([#1815](https://github.com/gfx-rs/naga/pull/1815)) **@jimblandy** - Fix wrong `MatrixStride` for `matCx2` and `mat2xR` ([#1781](https://github.com/gfx-rs/naga/pull/1781)) **@teoxoy** - Use `OpImageQuerySize` for MS images ([#1742](https://github.com/gfx-rs/naga/pull/1742)) **@JCapucho** MSL-OUT - Insert padding initialization for global constants ([#1988](https://github.com/gfx-rs/naga/pull/1988)) **@teoxoy** - Don't rely on cached expressions ([#1975](https://github.com/gfx-rs/naga/pull/1975)) **@JCapucho** - Fix pointers to private or workgroup address spaces possibly being read only ([#1901](https://github.com/gfx-rs/naga/pull/1901)) **@teoxoy** - Zero init variables in function address space ([#1871](https://github.com/gfx-rs/naga/pull/1871)) **@teoxoy** - Make binding arrays play nice with bounds checks ([#1855](https://github.com/gfx-rs/naga/pull/1855)) **@cwfitzgerald** - Permit `invariant` qualifier on vertex shader outputs ([#1821](https://github.com/gfx-rs/naga/pull/1821)) **@jimblandy** - Fix packed `vec3` stores ([#1816](https://github.com/gfx-rs/naga/pull/1816)) **@teoxoy** - Actually test push constants to be used ([#1767](https://github.com/gfx-rs/naga/pull/1767)) **@kvark** - Properly rename entry point arguments for struct members ([#1766](https://github.com/gfx-rs/naga/pull/1766)) **@jimblandy** - Qualify read-only storage with const ([#1763](https://github.com/gfx-rs/naga/pull/1763)) **@kvark** - Fix not unary operator for integer scalars ([#1760](https://github.com/gfx-rs/naga/pull/1760)) **@vincentisambart** - Add bounds checks for `ImageLoad` and `ImageStore` ([#1730](https://github.com/gfx-rs/naga/pull/1730)) **@jimblandy** - Fix resource bindings for non-structures ([#1718](https://github.com/gfx-rs/naga/pull/1718)) **@kvark** - Always check whether _buffer_sizes arg is needed ([#1717](https://github.com/gfx-rs/naga/pull/1717)) **@jimblandy** - WGSL storage address space should always correspond to MSL device address space ([#1711](https://github.com/gfx-rs/naga/pull/1711)) **@wtholliday** - Mitigation for MSL atomic bounds check ([#1703](https://github.com/gfx-rs/naga/pull/1703)) **@glalonde** HLSL-OUT - More `matCx2` fixes (#1989) ([#1989](https://github.com/gfx-rs/naga/pull/1989)) **@teoxoy** - Fix fallthrough in switch statements ([#1920](https://github.com/gfx-rs/naga/pull/1920)) **@teoxoy** - Fix missing break statements ([#1919](https://github.com/gfx-rs/naga/pull/1919)) **@teoxoy** - Fix `countOneBits` and `reverseBits` for signed integers ([#1928](https://github.com/gfx-rs/naga/pull/1928)) **@hasali19** - Fix array constructor return type ([#1914](https://github.com/gfx-rs/naga/pull/1914)) **@teoxoy** - Fix hlsl output for writes to scalar/vector storage buffer ([#1903](https://github.com/gfx-rs/naga/pull/1903)) **@hasali19** - Use `fmod` instead of `%` ([#1867](https://github.com/gfx-rs/naga/pull/1867)) **@teoxoy** - Use wrapped constructors when loading from storage address space ([#1893](https://github.com/gfx-rs/naga/pull/1893)) **@teoxoy** - Zero init struct constructor ([#1890](https://github.com/gfx-rs/naga/pull/1890)) **@teoxoy** - Flesh out matrix handling documentation ([#1850](https://github.com/gfx-rs/naga/pull/1850)) **@jimblandy** - Emit `row_major` qualifier on matrix uniform globals ([#1846](https://github.com/gfx-rs/naga/pull/1846)) **@jimblandy** - Fix bool splat ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** - Add more padding when necessary ([#1814](https://github.com/gfx-rs/naga/pull/1814)) **@teoxoy** - Support multidimensional arrays ([#1814](https://github.com/gfx-rs/naga/pull/1814)) **@teoxoy** - Don't output interpolation modifier if it's the default ([#1809](https://github.com/gfx-rs/naga/pull/1809)) **@NoelTautges** - Fix `matCx2` translation for uniform buffers ([#1802](https://github.com/gfx-rs/naga/pull/1802)) **@teoxoy** - Fix modifiers not being written in the vertex output and fragment input structs ([#1789](https://github.com/gfx-rs/naga/pull/1789)) **@teoxoy** - Fix matrix not being declared as transposed ([#1784](https://github.com/gfx-rs/naga/pull/1784)) **@teoxoy** - Insert padding between struct members ([#1786](https://github.com/gfx-rs/naga/pull/1786)) **@teoxoy** - Fix not unary operator for integer scalars ([#1760](https://github.com/gfx-rs/naga/pull/1760)) **@vincentisambart** GLSL-OUT - Fix vector bitcasts (#1966) ([#1966](https://github.com/gfx-rs/naga/pull/1966)) **@expenses** - Perform casts in int only math functions ([#1978](https://github.com/gfx-rs/naga/pull/1978)) **@JCapucho** - Don't rely on cached expressions ([#1975](https://github.com/gfx-rs/naga/pull/1975)) **@JCapucho** - Fix type error for `countOneBits` implementation ([#1897](https://github.com/gfx-rs/naga/pull/1897)) **@hasali19** - Fix storage format for `Rgba8Unorm` ([#1955](https://github.com/gfx-rs/naga/pull/1955)) **@JCapucho** - Implement bounds checks for `ImageLoad` ([#1889](https://github.com/gfx-rs/naga/pull/1889)) **@JCapucho** - Fix feature search in expressions ([#1887](https://github.com/gfx-rs/naga/pull/1887)) **@JCapucho** - Emit globals of any type ([#1823](https://github.com/gfx-rs/naga/pull/1823)) **@jimblandy** - Add support for boolean vector `~`, `|` and `&` ops ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** - Fix array function arguments ([#1814](https://github.com/gfx-rs/naga/pull/1814)) **@teoxoy** - Write constant sized array type for uniform ([#1768](https://github.com/gfx-rs/naga/pull/1768)) **@hatoo** - Texture function fixes ([#1742](https://github.com/gfx-rs/naga/pull/1742)) **@JCapucho** - Push constants use anonymous uniforms ([#1683](https://github.com/gfx-rs/naga/pull/1683)) **@JCapucho** - Add support for push constant emulation ([#1672](https://github.com/gfx-rs/naga/pull/1672)) **@JCapucho** - Skip unsized types if unused ([#1649](https://github.com/gfx-rs/naga/pull/1649)) **@kvark** - Write struct and array initializers ([#1644](https://github.com/gfx-rs/naga/pull/1644)) **@JCapucho** ## v0.8.5 (2022-01-25) MSL-OUT - Make VS-output positions invariant on even more systems ([#1697](https://github.com/gfx-rs/naga/pull/1697)) **@cwfitzgerald** - Improve support for point primitives ([#1696](https://github.com/gfx-rs/naga/pull/1696)) **@kvark** ## v0.8.4 (2022-01-24) MSL-OUT - Make VS-output positions invariant if possible ([#1687](https://github.com/gfx-rs/naga/pull/1687)) **@kvark** GLSL-OUT - Fix `floatBitsToUint` spelling ([#1688](https://github.com/gfx-rs/naga/pull/1688)) **@cwfitzgerald** - Call proper memory barrier functions ([#1680](https://github.com/gfx-rs/naga/pull/1680)) **@francesco-cattoglio** ## v0.8.3 (2022-01-20) - Don't pin `indexmap` version ([#1666](https://github.com/gfx-rs/naga/pull/1666)) **@a1phyr** MSL-OUT - Fix support for point primitives ([#1674](https://github.com/gfx-rs/naga/pull/1674)) **@kvark** GLSL-OUT - Fix sampler association ([#1671](https://github.com/gfx-rs/naga/pull/1671)) **@JCapucho** ## v0.8.2 (2022-01-11) VALIDATOR - Check structure resource types ([#1639](https://github.com/gfx-rs/naga/pull/1639)) **@kvark** WGSL-IN - Improve type mismatch errors ([#1658](https://github.com/gfx-rs/naga/pull/1658)) **@Gordon-F** SPV-IN - Implement more sign agnostic operations ([#1651](https://github.com/gfx-rs/naga/pull/1651), [#1650](https://github.com/gfx-rs/naga/pull/1650)) **@JCapucho** SPV-OUT - Fix modulo operator (use `OpFRem` instead of `OpFMod`) ([#1653](https://github.com/gfx-rs/naga/pull/1653)) **@JCapucho** MSL-OUT - Fix `texture1d` accesses ([#1647](https://github.com/gfx-rs/naga/pull/1647)) **@jimblandy** - Fix data packing functions ([#1637](https://github.com/gfx-rs/naga/pull/1637)) **@phoekz** ## v0.8.1 (2021-12-29) API - Make `WithSpan` cloneable ([#1620](https://github.com/gfx-rs/naga/pull/1620)) **@jakobhellermann** MSL-OUT - Fix packed vec access ([#1634](https://github.com/gfx-rs/naga/pull/1634)) **@kvark** - Fix packed float support ([#1630](https://github.com/gfx-rs/naga/pull/1630)) **@kvark** HLSL-OUT - Support arrays of matrices ([#1629](https://github.com/gfx-rs/naga/pull/1629)) **@kvark** - Use `mad` instead of `fma` function ([#1580](https://github.com/gfx-rs/naga/pull/1580)) **@parasyte** GLSL-OUT - Fix conflicting names for globals ([#1616](https://github.com/gfx-rs/naga/pull/1616)) **@Gordon-F** - Fix `fma` function ([#1580](https://github.com/gfx-rs/naga/pull/1580)) **@parasyte** ## v0.8 (2021-12-18) - development release for wgpu-0.12 - lots of fixes in all parts - validator: - now gated by `validate` feature - nicely detailed error messages with spans - API: - image gather operations - WGSL-in: - remove `[[block]]` attribute - `elseif` is removed in favor of `else if` - MSL-out: - full out-of-bounds checking ## v0.7.3 (2021-12-14) - API: - `view_index` builtin - GLSL-out: - reflect textures without samplers - SPV-out: - fix incorrect pack/unpack ## v0.7.2 (2021-12-01) - validator: - check stores for proper pointer class - HLSL-out: - fix stores into `mat3` - respect array strides - SPV-out: - fix multi-word constants - WGSL-in: - permit names starting with underscores - SPV-in: - cull unused builtins - support empty debug labels - GLSL-in: - don't panic on invalid integer operations ## v0.7.1 (2021-10-12) - implement casts from and to booleans in the backends ## v0.7 (2021-10-07) - development release for wgpu-0.11 - API: - bit extraction and packing functions - hyperbolic trigonometry functions - validation is gated by a cargo feature - `view_index` builtin - separate bounds checking policies for locals/buffers/textures - IR: - types and constants are guaranteed to be unique - WGSL-in: - new hex literal parser - updated list of reserved words - rewritten logic for resolving references and pointers - `switch` can use unsigned selectors - GLSL-in: - better support for texture sampling - better logic for auto-splatting scalars - GLSL-out: - fixed storage buffer layout - fix module operator - HLSL-out: - fixed texture queries - SPV-in: - control flow handling is rewritten from scratch - SPV-out: - fully covered out-of-bounds checking - option to emit point size - option to clamp output depth ## v0.6.3 (2021-09-08) - Reduced heap allocations when generating WGSL, HLSL, and GLSL - WGSL-in: - support module-scope `let` type inference - SPV-in: - fix depth sampling with projection - HLSL-out: - fix local struct construction - GLSL-out: - fix `select()` order - SPV-out: - allow working around Adreno issue with `OpName` ## v0.6.2 (2021-09-01) - SPV-out fixes: - requested capabilities for 1D and cube images, storage formats - handling `break` and `continue` in a `switch` statement - avoid generating duplicate `OpTypeImage` types - HLSL-out fixes: - fix output struct member names - MSL-out fixes: - fix packing of fields in interface structs - GLSL-out fixes: - fix non-fallthrough `switch` cases - GLSL-in fixes: - avoid infinite loop on invalid statements ## v0.6.1 (2021-08-24) - HLSL-out fixes: - array arguments - pointers to array arguments - switch statement - rewritten interface matching - SPV-in fixes: - array storage texture stores - tracking sampling across function parameters - updated petgraph dependencies - MSL-out: - gradient sampling - GLSL-out: - modulo operator on floats ## v0.6 (2021-08-18) - development release for wgpu-0.10 - API: - atomic types and functions - storage access is moved from global variables to the storage class and storage texture type - new built-ins: `primitive_index` and `num_workgroups` - support for multi-sampled depth images - WGSL: - `select()` order of true/false is swapped - HLSL backend is vastly improved and now usable - GLSL frontend is heavily reworked ## v0.5 (2021-06-18) - development release for wgpu-0.9 - API: - barriers - dynamic indexing of matrices and arrays is only allowed on variables - validator now accepts a list of IR capabilities to allow - improved documentation - Infrastructure: - much richer test suite, focused around consuming or emitting WGSL - lazy testing on large shader corpuses - the binary is moved to a sub-crate "naga-cli" - Frontends: - GLSL frontend: - rewritten from scratch and effectively revived, no longer depends on `pomelo` - only supports 440/450/460 versions for now - has optional support for codespan messages - SPIRV frontend has improved CFG resolution (still with issues unresolved) - WGSL got better error messages, workgroup memory support - Backends: - general: better expression naming and emitting - new HLSL backend (in progress) - MSL: - support `ArraySize` expression - better texture sampling instructions - GLSL: - multisampling on GLES - WGSL is vastly improved and now usable ## v0.4.2 (2021-05-28) - SPIR-V frontend: - fix image stores - fix matrix stride check - SPIR-V backend: - fix auto-deriving the capabilities - GLSL backend: - support sample interpolation - write out swizzled vector accesses ## v0.4.1 (2021-05-14) - numerous additions and improvements to SPIR-V frontend: - int8, in16, int64 - null constant initializers for structs and matrices - `OpArrayLength`, `OpCopyMemory`, `OpInBoundsAccessChain`, `OpLogicalXxxEqual` - outer product - fix struct size alignment - initialize built-ins with default values - fix read-only decorations on struct members - fix struct size alignment in WGSL - fix `fwidth` in WGSL - fix scalars arrays in GLSL backend ## v0.4 (2021-04-29) - development release for wgpu-0.8 - API: - expressions are explicitly emitted with `Statement::Emit` - entry points have inputs in arguments and outputs in the result type - `input`/`output` storage classes are gone, but `push_constant` is added - `Interpolation` is moved into `Binding::Location` variant - real pointer semantics with required `Expression::Load` - `TypeInner::ValuePointer` is added - image query expressions are added - new `Statement::ImageStore` - all function calls are `Statement::Call` - `GlobalUse` is moved out into processing - `Header` is removed - entry points are an array instead of a map - new `Swizzle` and `Splat` expressions - interpolation qualifiers are extended and required - struct member layout is based on the byte offsets - Infrastructure: - control flow uniformity analysis - texture-sampler combination gathering - `CallGraph` processor is moved out into `glsl` backend - `Interface` is removed, instead the analysis produces `ModuleInfo` with all the derived info - validation of statement tree, expressions, and constants - code linting is more strict for matches - new GraphViz `dot` backend for pretty visualization of the IR - Metal support for inlined samplers - `convert` example is transformed into the default binary target named `naga` - lots of frontend and backend fixes ## v0.3.2 (2021-02-15) - fix logical expression types - fix _FragDepth_ semantics - spv-in: - derive block status of structures - spv-out: - add lots of missing math functions - implement discard ## v0.3.1 (2021-01-31) - wgsl: - support constant array sizes - spv-out: - fix block decorations on nested structures - fix fixed-size arrays - fix matrix decorations inside structures - implement read-only decorations ## v0.3 (2021-01-30) - development release for wgpu-0.7 - API: - math functions - type casts - updated storage classes - updated image sub-types - image sampling/loading options - storage images - interpolation qualifiers - early and conservative depth - Processors: - name manager - automatic layout - termination analysis - validation of types, constants, variables, and entry points ## v0.2 (2020-08-17) - development release for wgpu-0.6 ## v0.1 (2020-02-26) - initial release ================================================ FILE: naga/Cargo.toml ================================================ [package] name = "naga" version.workspace = true authors.workspace = true edition.workspace = true description = "Shader translator and validator. Part of the wgpu project" repository.workspace = true keywords = ["shader", "SPIR-V", "GLSL", "MSL"] license.workspace = true exclude = ["bin/**/*", "tests/**/*", "Cargo.lock", "target/**/*"] # Override the workspace's `rust-version` key. Firefox uses `cargo vendor` to # copy the crates it actually uses out of the workspace, so it's meaningful for # them to have less restrictive MSRVs individually than the workspace as a # whole, if their code permits. See `../README.md` for details. rust-version = "1.87" [package.metadata.docs.rs] all-features = true [features] default = [] dot-out = [] glsl-in = ["dep:pp-rs"] glsl-out = [] ## Enables outputting to the Metal Shading Language (MSL). ## ## This enables MSL output regardless of the target platform. ## If you want to enable it only when targeting iOS/tvOS/watchOS/macOS, use `naga/msl-out-if-target-apple`. msl-out = [] ## Enables outputting to the Metal Shading Language (MSL) only if the target platform is iOS/tvOS/watchOS/macOS. ## ## If you want to enable MSL output it regardless of the target platform, use `naga/msl-out`. msl-out-if-target-apple = [] serialize = [ "dep:serde", "bitflags/serde", "half/serde", "hashbrown/serde", "indexmap/serde", ] deserialize = [ "dep:serde", "bitflags/serde", "half/serde", "hashbrown/serde", "indexmap/serde", ] arbitrary = [ "dep:arbitrary", "bitflags/arbitrary", "indexmap/arbitrary", "half/arbitrary", "half/std", ] spv-in = ["dep:petgraph", "petgraph/graphmap", "dep:spirv"] spv-out = ["dep:spirv"] wgsl-in = ["dep:hexf-parse", "dep:unicode-ident"] wgsl-out = [] ## Enables outputting to HLSL (Microsoft's High-Level Shader Language). ## ## This enables HLSL output regardless of the target platform. ## If you want to enable it only when targeting Windows, use `hlsl-out-if-target-windows`. hlsl-out = [] ## Enables outputting to HLSL (Microsoft's High-Level Shader Language) only if the target platform is Windows. ## ## If you want to enable HLSL output it regardless of the target platform, use `naga/hlsl-out`. hlsl-out-if-target-windows = [] ## Enables colored output through codespan-reporting and termcolor. termcolor = ["codespan-reporting/termcolor"] ## Enables writing output to stderr. stderr = ["codespan-reporting/std"] ## Enables integration with the underlying filesystem. fs = [] [dependencies] arbitrary = { workspace = true, features = ["derive"], optional = true } arrayvec.workspace = true bitflags.workspace = true bit-set.workspace = true cfg-if.workspace = true codespan-reporting = { workspace = true } hashbrown.workspace = true half = { workspace = true, features = ["num-traits"] } rustc-hash.workspace = true indexmap.workspace = true libm = { workspace = true, default-features = false } log.workspace = true num-traits.workspace = true once_cell = { workspace = true, features = ["alloc", "race"] } spirv = { workspace = true, optional = true } thiserror.workspace = true serde = { workspace = true, features = ["alloc", "derive"], optional = true } petgraph = { workspace = true, optional = true } pp-rs = { workspace = true, optional = true } hexf-parse = { workspace = true, optional = true } unicode-ident = { workspace = true, optional = true } [build-dependencies] cfg_aliases.workspace = true [dev-dependencies] diff.workspace = true env_logger.workspace = true hashbrown = { workspace = true, features = ["serde"] } hlsl-snapshots.workspace = true itertools.workspace = true naga-test.workspace = true ron.workspace = true rspirv.workspace = true # So we don't actually need this, however if we remove this, it # brakes calling `--features spirv` at the workspace level. I think # this is because there is a `dep:spirv` in the regular feature set, # so cargo tries to match the feature against that, fails as it's a optional dep, # and then refuses to build instead of ignoring it. spirv.workspace = true serde = { workspace = true, features = ["default", "derive"] } strum = { workspace = true } walkdir.workspace = true [lints.clippy] std_instead_of_alloc = "warn" std_instead_of_core = "warn" alloc_instead_of_core = "warn" ================================================ FILE: naga/LICENSE.APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: naga/LICENSE.MIT ================================================ MIT License Copyright (c) 2025 The gfx-rs developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: naga/README.md ================================================ # Naga [![Matrix](https://img.shields.io/badge/Matrix-%23naga%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#naga:matrix.org) [![Crates.io](https://img.shields.io/crates/v/naga.svg?label=naga)](https://crates.io/crates/naga) [![Docs.rs](https://docs.rs/naga/badge.svg)](https://docs.rs/naga) [![Build Status](https://github.com/gfx-rs/naga/workflows/pipeline/badge.svg)](https://github.com/gfx-rs/naga/actions) ![MSRV](https://img.shields.io/badge/rustc-1.90-blue.svg) [![codecov.io](https://codecov.io/gh/gfx-rs/naga/branch/master/graph/badge.svg?token=9VOKYO8BM2)](https://codecov.io/gh/gfx-rs/naga) The shader translation library for the needs of [wgpu](https://github.com/gfx-rs/wgpu). ## Supported end-points Front-end | Status | Feature | Notes | --------------- | ------------------ | ------- | ----- | SPIR-V (binary) | :white_check_mark: | spv-in | | WGSL | :white_check_mark: | wgsl-in | Fully validated | GLSL | :ok: | glsl-in | GLSL 440+ and Vulkan semantics only | Back-end | Status | Feature | Notes | --------------- | ------------------ | -------- | ----- | SPIR-V | :white_check_mark: | spv-out | | WGSL | :ok: | wgsl-out | | Metal | :white_check_mark: | msl-out | | HLSL | :white_check_mark: | hlsl-out | Shader Model 5.0+ (DirectX 11+) | GLSL | :ok: | glsl-out | GLSL 330+ and GLSL ES 300+ | AIR | | | | DXIL/DXIR | | | | DXBC | | | | DOT (GraphViz) | :ok: | dot-out | Not a shading language | :white_check_mark: = Primary support — :ok: = Secondary support — :construction: = Unsupported, but support in progress ## Conversion tool Naga can be used as a CLI, which allows testing the conversion of different code paths. First, install `naga-cli` from crates.io or directly from GitHub. ```bash # release version cargo install naga-cli # development version cargo install naga-cli --git https://github.com/gfx-rs/wgpu.git ``` Then, you can run `naga` command. ```bash naga my_shader.wgsl # validate only naga my_shader.spv my_shader.txt # dump the IR module into a file naga my_shader.spv my_shader.metal --flow-dir flow-dir # convert the SPV to Metal, also dump the SPIR-V flow graph to `flow-dir` naga my_shader.wgsl my_shader.vert --profile es310 # convert the WGSL to GLSL vertex stage under ES 3.20 profile ``` As naga includes a default binary target, you can also use `cargo run` without installation. This is useful when you develop naga itself or investigate the behavior of naga at a specific commit (e.g. [wgpu](https://github.com/gfx-rs/wgpu) might pin a different version of naga than the `HEAD` of this repository). ```bash cargo run my_shader.wgsl ``` ## Development workflow The main instrument aiding the development is the good old `cargo test --all-features --workspace`, which will run the unit tests and also update all the snapshots. You'll see these changes in git before committing the code. If working on a particular front-end or back-end, it may be convenient to enable the relevant features in `Cargo.toml`, e.g. ```toml default = ["spv-out"] #TEMP! ``` This allows IDE basic checks to report errors there unless your IDE is sufficiently configurable already. Finally, when changes to the snapshots are made, we should verify that the produced shaders are indeed valid for the target platforms they are compiled for: ```bash cargo xtask validate spv # for Vulkan shaders, requires SPIRV-Tools installed cargo xtask validate msl # for Metal shaders, requires XCode command-line tools installed cargo xtask validate glsl # for OpenGL shaders, requires GLSLang installed cargo xtask validate dot # for dot files, requires GraphViz installed cargo xtask validate wgsl # for WGSL shaders cargo xtask validate hlsl dxc # for HLSL shaders via DXC cargo xtask validate hlsl fxc # for HLSL shaders via FXC ``` ================================================ FILE: naga/build.rs ================================================ fn main() { cfg_aliases::cfg_aliases! { dot_out: { feature = "dot-out" }, glsl_out: { feature = "glsl-out" }, hlsl_out: { any(feature = "hlsl-out", all(target_os = "windows", feature = "hlsl-out-if-target-windows")) }, msl_out: { any(feature = "msl-out", all(target_vendor = "apple", feature = "msl-out-if-target-apple")) }, spv_out: { feature = "spv-out" }, wgsl_out: { feature = "wgsl-out" }, std: { any(test, feature = "wgsl-in", feature = "stderr", feature = "fs") }, no_std: { not(std) }, } } ================================================ FILE: naga/fuzz/.gitignore ================================================ target corpus artifacts ================================================ FILE: naga/fuzz/Cargo.toml ================================================ [package] name = "naga-fuzz" version.workspace = true authors.workspace = true publish = false edition.workspace = true license.workspace = true build = "build.rs" [package.metadata] cargo-fuzz = true [target.'cfg(not(any(target_arch = "wasm32", target_os = "ios")))'.dependencies] arbitrary = { workspace = true, features = ["derive"] } libfuzzer-sys.workspace = true [target.'cfg(not(any(target_arch = "wasm32", target_os = "ios", target_os = "visionos")))'.dependencies.naga] workspace = true features = ["arbitrary", "spv-in", "wgsl-in", "glsl-in"] [build-dependencies] cfg_aliases.workspace = true [[bin]] name = "spv_parser" path = "fuzz_targets/spv_parser.rs" bench = false test = false doc = false [[bin]] name = "wgsl_parser" path = "fuzz_targets/wgsl_parser.rs" bench = false test = false doc = false [[bin]] name = "glsl_parser" path = "fuzz_targets/glsl_parser.rs" bench = false test = false doc = false [[bin]] name = "ir" path = "fuzz_targets/ir.rs" bench = false test = false doc = false [lints.clippy] disallowed_types = "allow" ================================================ FILE: naga/fuzz/build.rs ================================================ fn main() { cfg_aliases::cfg_aliases! { fuzzable_platform: { not(any(target_arch = "wasm32", target_os = "ios", all(windows, target_arch = "aarch64"))) }, } // This cfg provided by cargo-fuzz println!("cargo::rustc-check-cfg=cfg(fuzzing)"); } ================================================ FILE: naga/fuzz/fuzz_targets/glsl_parser.rs ================================================ #![cfg_attr(all(fuzzable_platform, fuzzing), no_main)] #[cfg(all(fuzzable_platform, fuzzing))] mod fuzz { use std::iter::FromIterator; use arbitrary::Arbitrary; use libfuzzer_sys::fuzz_target; use naga::{ front::glsl::{Frontend, Options}, FastHashMap, ShaderStage, }; #[derive(Debug, Arbitrary)] struct OptionsProxy { pub stage: ShaderStage, pub defines: std::collections::HashMap, } impl From for Options { fn from(proxy: OptionsProxy) -> Self { Options { stage: proxy.stage, // NOTE: This is a workaround needed due to lack of rust-fuzz/arbitrary support for hashbrown. defines: FastHashMap::from_iter( proxy .defines .keys() .map(|k| (k.clone(), proxy.defines.get(&k.clone()).unwrap().clone())), ), } } } fuzz_target!(|data: (OptionsProxy, String)| { let (options, source) = data; // Ensure the parser can handle potentially malformed strings without crashing. let mut parser = Frontend::default(); let _result = parser.parse(&options.into(), &source); }); } #[cfg(not(all(fuzzable_platform, fuzzing)))] fn main() {} ================================================ FILE: naga/fuzz/fuzz_targets/ir.rs ================================================ #![cfg_attr(all(fuzzable_platform, fuzzing), no_main)] #[cfg(all(fuzzable_platform, fuzzing))] mod fuzz { use libfuzzer_sys::fuzz_target; fuzz_target!(|module: naga::Module| { use naga::valid as v; // Check if the module validates without errors. //TODO: may also fuzz the flags and capabilities let mut validator = v::Validator::new(v::ValidationFlags::all(), v::Capabilities::default()); let _result = validator.validate(&module); }); } #[cfg(not(all(fuzzable_platform, fuzzing)))] fn main() {} ================================================ FILE: naga/fuzz/fuzz_targets/spv_parser.rs ================================================ #![cfg_attr(all(fuzzable_platform, fuzzing), no_main)] #[cfg(all(fuzzable_platform, fuzzing))] mod fuzz { use libfuzzer_sys::fuzz_target; use naga::front::spv::{Frontend, Options}; fuzz_target!(|data: Vec| { // Ensure the parser can handle potentially malformed data without crashing. let options = Options::default(); let _result = Frontend::new(data.into_iter(), &options).parse(); }); } #[cfg(not(all(fuzzable_platform, fuzzing)))] fn main() {} ================================================ FILE: naga/fuzz/fuzz_targets/wgsl_parser.rs ================================================ #![cfg_attr(all(fuzzable_platform, fuzzing), no_main)] #[cfg(all(fuzzable_platform, fuzzing))] mod fuzz { use libfuzzer_sys::fuzz_target; use naga::front::wgsl::Frontend; fuzz_target!(|data: String| { // Ensure the parser can handle potentially malformed strings without crashing. let _result = Frontend::new().parse(&data); }); } #[cfg(not(all(fuzzable_platform, fuzzing)))] fn main() {} ================================================ FILE: naga/hlsl-snapshots/Cargo.toml ================================================ [package] name = "hlsl-snapshots" version.workspace = true edition.workspace = true publish = false license.workspace = true [lib] name = "hlsl_snapshots" path = "src/lib.rs" test = false [dependencies] anyhow.workspace = true nanoserde.workspace = true ================================================ FILE: naga/hlsl-snapshots/src/lib.rs ================================================ use std::{error::Error, fmt::Display, fs, io, path::Path}; use anyhow::{anyhow, ensure}; use nanoserde::{self, DeRon, DeRonErr, SerRon}; #[derive(Debug)] struct BadRonParse(BadRonParseKind); impl Display for BadRonParse { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "failed to read RON configuration of HLSL snapshot test") } } impl Error for BadRonParse { fn source(&self) -> Option<&(dyn Error + 'static)> { Some(&self.0) } } #[derive(Debug)] enum BadRonParseKind { Read { source: io::Error }, Parse { source: DeRonErr }, Empty, } impl Display for BadRonParseKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { BadRonParseKind::Read { source } => Display::fmt(source, f), BadRonParseKind::Parse { source } => Display::fmt(source, f), BadRonParseKind::Empty => write!(f, "no configuration was specified"), } } } impl Error for BadRonParseKind { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { BadRonParseKind::Read { source } => source.source(), BadRonParseKind::Parse { source } => source.source(), BadRonParseKind::Empty => None, } } } #[derive(Debug, DeRon, SerRon)] pub struct Config { pub vertex: Vec, pub fragment: Vec, pub compute: Vec, } impl Config { #[must_use] pub fn empty() -> Self { Self { vertex: Default::default(), fragment: Default::default(), compute: Default::default(), } } pub fn from_path(path: impl AsRef) -> anyhow::Result { let path = path.as_ref(); let raw_config = fs::read_to_string(path) .map_err(|source| BadRonParse(BadRonParseKind::Read { source }))?; let config = Config::deserialize_ron(&raw_config) .map_err(|source| BadRonParse(BadRonParseKind::Parse { source }))?; ensure!(!config.is_empty(), BadRonParse(BadRonParseKind::Empty)); Ok(config) } pub fn to_file(&self, path: impl AsRef) -> anyhow::Result<()> { let path = path.as_ref(); let mut s = self.serialize_ron(); s.push('\n'); fs::write(path, &s).map_err(|e| anyhow!("failed to write to {}: {e}", path.display())) } #[must_use] pub fn is_empty(&self) -> bool { let Self { vertex, fragment, compute, } = self; vertex.is_empty() && fragment.is_empty() && compute.is_empty() } } #[derive(Debug, DeRon, SerRon)] pub struct ConfigItem { pub entry_point: String, /// See also /// . pub target_profile: String, } ================================================ FILE: naga/src/arena/handle.rs ================================================ //! Well-typed indices into [`Arena`]s and [`UniqueArena`]s. //! //! This module defines [`Handle`] and related types. //! //! [`Arena`]: super::Arena //! [`UniqueArena`]: super::UniqueArena use core::{cmp::Ordering, fmt, hash, marker::PhantomData}; /// An unique index in the arena array that a handle points to. /// The "non-max" part ensures that an `Option>` has /// the same size and representation as `Handle`. pub type Index = crate::non_max_u32::NonMaxU32; #[derive(Clone, Copy, Debug, thiserror::Error, PartialEq)] #[error("Handle {index} of {kind} is either not present, or inaccessible yet")] pub struct BadHandle { pub kind: &'static str, pub index: usize, } impl BadHandle { pub fn new(handle: Handle) -> Self { Self { kind: core::any::type_name::(), index: handle.index(), } } } /// A strongly typed reference to an arena item. /// /// A `Handle` value can be used as an index into an [`Arena`] or [`UniqueArena`]. /// /// [`Arena`]: super::Arena /// [`UniqueArena`]: super::UniqueArena #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[cfg_attr( any(feature = "serialize", feature = "deserialize"), serde(transparent) )] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Handle { index: Index, #[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(skip))] marker: PhantomData, } impl Clone for Handle { fn clone(&self) -> Self { *self } } impl Copy for Handle {} impl PartialEq for Handle { fn eq(&self, other: &Self) -> bool { self.index == other.index } } impl Eq for Handle {} impl PartialOrd for Handle { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Handle { fn cmp(&self, other: &Self) -> Ordering { self.index.cmp(&other.index) } } impl fmt::Debug for Handle { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "[{}]", self.index) } } impl hash::Hash for Handle { fn hash(&self, hasher: &mut H) { self.index.hash(hasher) } } impl Handle { pub(crate) const fn new(index: Index) -> Self { Handle { index, marker: PhantomData, } } /// Returns the index of this handle. pub const fn index(self) -> usize { self.index.get() as usize } /// Convert a `usize` index into a `Handle`. pub(super) fn from_usize(index: usize) -> Self { let handle_index = u32::try_from(index) .ok() .and_then(Index::new) .expect("Failed to insert into arena. Handle overflows"); Handle::new(handle_index) } /// Write this handle's index to `formatter`, preceded by `prefix`. pub fn write_prefixed( &self, formatter: &mut fmt::Formatter, prefix: &'static str, ) -> fmt::Result { formatter.write_str(prefix)?; ::fmt(&self.index(), formatter) } } ================================================ FILE: naga/src/arena/handle_set.rs ================================================ //! The [`HandleSet`] type and associated definitions. use crate::arena::{Arena, Handle, UniqueArena}; /// A set of `Handle` values. #[derive(Debug)] pub struct HandleSet { /// Bound on indexes of handles stored in this set. len: usize, /// `members[i]` is true if the handle with index `i` is a member. members: bit_set::BitSet, /// This type is indexed by values of type `T`. as_keys: core::marker::PhantomData, } impl HandleSet { /// Return a new, empty `HandleSet`. pub fn new() -> Self { Self { len: 0, members: bit_set::BitSet::new(), as_keys: core::marker::PhantomData, } } pub fn is_empty(&self) -> bool { self.members.is_empty() } /// Return a new, empty `HandleSet`, sized to hold handles from `arena`. pub fn for_arena(arena: &impl ArenaType) -> Self { let len = arena.len(); Self { len, members: bit_set::BitSet::with_capacity(len), as_keys: core::marker::PhantomData, } } /// Remove all members from `self`. pub fn clear(&mut self) { self.members.make_empty(); } /// Remove all members from `self`, and reserve space to hold handles from `arena`. pub fn clear_for_arena(&mut self, arena: &impl ArenaType) { self.members.make_empty(); self.members.reserve_len(arena.len()); } /// Return an iterator over all handles that could be made members /// of this set. pub fn all_possible(&self) -> impl Iterator> { super::Range::full_range_from_size(self.len) } /// Add `handle` to the set. /// /// Return `true` if `handle` was not already present in the set. pub fn insert(&mut self, handle: Handle) -> bool { self.members.insert(handle.index()) } /// Remove `handle` from the set. /// /// Returns `true` if `handle` was present in the set. pub fn remove(&mut self, handle: Handle) -> bool { self.members.remove(handle.index()) } /// Add handles from `iter` to the set. pub fn insert_iter(&mut self, iter: impl IntoIterator>) { for handle in iter { self.insert(handle); } } /// Add all of the handles that can be included in this set. pub fn add_all(&mut self) { self.members.get_mut().fill(true); } pub fn contains(&self, handle: Handle) -> bool { self.members.contains(handle.index()) } /// Return an iterator over all handles in `self`. pub fn iter(&self) -> impl '_ + Iterator> { self.members.iter().map(Handle::from_usize) } /// Removes and returns the numerically largest handle in the set, or `None` /// if the set is empty. pub fn pop(&mut self) -> Option> { let members = core::mem::take(&mut self.members); let mut vec = members.into_bit_vec(); let result = vec.iter_mut().enumerate().rev().find_map(|(i, mut bit)| { if *bit { *bit = false; Some(i) } else { None } }); self.members = bit_set::BitSet::from_bit_vec(vec); result.map(Handle::from_usize) } } impl Default for HandleSet { fn default() -> Self { Self::new() } } pub trait ArenaType { fn len(&self) -> usize; } impl ArenaType for Arena { fn len(&self) -> usize { self.len() } } impl ArenaType for UniqueArena { fn len(&self) -> usize { self.len() } } ================================================ FILE: naga/src/arena/handlevec.rs ================================================ //! The [`HandleVec`] type and associated definitions. use super::handle::Handle; use alloc::{vec, vec::Vec}; use core::marker::PhantomData; use core::ops; /// A [`Vec`] indexed by [`Handle`]s. /// /// A `HandleVec` is a [`Vec`] indexed by values of type `Handle`, /// rather than `usize`. /// /// Rather than a `push` method, `HandleVec` has an [`insert`] method, analogous /// to [`HashMap::insert`], that requires you to provide the handle at which the /// new value should appear. However, since `HandleVec` only supports insertion /// at the end, the given handle's index must be equal to the `HandleVec`'s /// current length; otherwise, the insertion will panic. /// /// [`insert`]: HandleVec::insert /// [`HashMap::insert`]: hashbrown::HashMap::insert #[derive(Debug)] pub(crate) struct HandleVec { inner: Vec, as_keys: PhantomData, } impl Default for HandleVec { fn default() -> Self { Self { inner: vec![], as_keys: PhantomData, } } } #[allow(dead_code)] impl HandleVec { pub(crate) const fn new() -> Self { Self { inner: vec![], as_keys: PhantomData, } } pub(crate) fn with_capacity(capacity: usize) -> Self { Self { inner: Vec::with_capacity(capacity), as_keys: PhantomData, } } pub(crate) const fn len(&self) -> usize { self.inner.len() } /// Insert a mapping from `handle` to `value`. /// /// Unlike a [`HashMap`], a `HandleVec` can only have new entries inserted at /// the end, like [`Vec::push`]. So the index of `handle` must equal /// [`self.len()`]. /// /// [`HashMap`]: hashbrown::HashMap /// [`self.len()`]: HandleVec::len pub(crate) fn insert(&mut self, handle: Handle, value: U) { assert_eq!(handle.index(), self.inner.len()); self.inner.push(value); } pub(crate) fn get(&self, handle: Handle) -> Option<&U> { self.inner.get(handle.index()) } pub(crate) fn clear(&mut self) { self.inner.clear() } pub(crate) fn resize(&mut self, len: usize, fill: U) where U: Clone, { self.inner.resize(len, fill); } pub(crate) fn iter(&self) -> impl Iterator { self.inner.iter() } pub(crate) fn iter_mut(&mut self) -> impl Iterator { self.inner.iter_mut() } } impl ops::Index> for HandleVec { type Output = U; fn index(&self, handle: Handle) -> &Self::Output { &self.inner[handle.index()] } } impl ops::IndexMut> for HandleVec { fn index_mut(&mut self, handle: Handle) -> &mut Self::Output { &mut self.inner[handle.index()] } } ================================================ FILE: naga/src/arena/mod.rs ================================================ /*! The [`Arena`], [`UniqueArena`], and [`Handle`] types. To improve translator performance and reduce memory usage, most structures are stored in an [`Arena`]. An `Arena` stores a series of `T` values, indexed by [`Handle`] values, which are just wrappers around integer indexes. For example, a `Function`'s expressions are stored in an `Arena`, and compound expressions refer to their sub-expressions via `Handle` values. A [`UniqueArena`] is just like an `Arena`, except that it stores only a single instance of each value. The value type must implement `Eq` and `Hash`. Like an `Arena`, inserting a value into a `UniqueArena` returns a `Handle` which can be used to efficiently access the value, without a hash lookup. Inserting a value multiple times returns the same `Handle`. If the `span` feature is enabled, both `Arena` and `UniqueArena` can associate a source code span with each element. [`Handle`]: Handle */ mod handle; mod handle_set; mod handlevec; mod range; mod unique_arena; pub use handle::{BadHandle, Handle}; pub(crate) use handle_set::HandleSet; pub(crate) use handlevec::HandleVec; pub use range::{BadRangeError, Range}; pub use unique_arena::UniqueArena; use alloc::vec::Vec; use core::{fmt, ops}; use crate::Span; use handle::Index; /// An arena holding some kind of component (e.g., type, constant, /// instruction, etc.) that can be referenced. /// /// Adding new items to the arena produces a strongly-typed [`Handle`]. /// The arena can be indexed using the given handle to obtain /// a reference to the stored item. #[derive(Clone)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "serialize", serde(transparent))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(test, derive(PartialEq))] pub struct Arena { /// Values of this arena. data: Vec, #[cfg_attr(feature = "serialize", serde(skip))] span_info: Vec, } impl Default for Arena { fn default() -> Self { Self::new() } } impl fmt::Debug for Arena { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_map().entries(self.iter()).finish() } } impl Arena { /// Create a new arena with no initial capacity allocated. pub const fn new() -> Self { Arena { data: Vec::new(), span_info: Vec::new(), } } /// Extracts the inner vector. pub fn into_inner(self) -> Vec { self.data } /// Returns the current number of items stored in this arena. pub const fn len(&self) -> usize { self.data.len() } /// Returns `true` if the arena contains no elements. pub const fn is_empty(&self) -> bool { self.data.is_empty() } /// Returns an iterator over the items stored in this arena, returning both /// the item's handle and a reference to it. pub fn iter(&self) -> impl DoubleEndedIterator, &T)> + ExactSizeIterator { self.data .iter() .enumerate() .map(|(i, v)| (Handle::from_usize(i), v)) } /// Returns an iterator over the items stored in this arena, returning both /// the item's handle and a reference to it. pub fn iter_mut_span( &mut self, ) -> impl DoubleEndedIterator, &mut T, &Span)> + ExactSizeIterator { self.data .iter_mut() .zip(self.span_info.iter()) .enumerate() .map(|(i, (v, span))| (Handle::from_usize(i), v, span)) } /// Drains the arena, returning an iterator over the items stored. pub fn drain(&mut self) -> impl DoubleEndedIterator, T, Span)> { let arena = core::mem::take(self); arena .data .into_iter() .zip(arena.span_info) .enumerate() .map(|(i, (v, span))| (Handle::from_usize(i), v, span)) } /// Returns a iterator over the items stored in this arena, /// returning both the item's handle and a mutable reference to it. pub fn iter_mut(&mut self) -> impl DoubleEndedIterator, &mut T)> { self.data .iter_mut() .enumerate() .map(|(i, v)| (Handle::from_usize(i), v)) } /// Adds a new value to the arena, returning a typed handle. pub fn append(&mut self, value: T, span: Span) -> Handle { let index = self.data.len(); self.data.push(value); self.span_info.push(span); Handle::from_usize(index) } /// Fetch a handle to an existing type. pub fn fetch_if bool>(&self, fun: F) -> Option> { self.data .iter() .position(fun) .map(|index| Handle::from_usize(index)) } /// Adds a value with a custom check for uniqueness: /// returns a handle pointing to /// an existing element if the check succeeds, or adds a new /// element otherwise. pub fn fetch_if_or_append bool>( &mut self, value: T, span: Span, fun: F, ) -> Handle { if let Some(index) = self.data.iter().position(|d| fun(d, &value)) { Handle::from_usize(index) } else { self.append(value, span) } } /// Adds a value with a check for uniqueness, where the check is plain comparison. pub fn fetch_or_append(&mut self, value: T, span: Span) -> Handle where T: PartialEq, { self.fetch_if_or_append(value, span, T::eq) } pub fn try_get(&self, handle: Handle) -> Result<&T, BadHandle> { self.data .get(handle.index()) .ok_or_else(|| BadHandle::new(handle)) } /// Get a mutable reference to an element in the arena. pub fn get_mut(&mut self, handle: Handle) -> &mut T { self.data.get_mut(handle.index()).unwrap() } /// Get the range of handles from a particular number of elements to the end. pub fn range_from(&self, old_length: usize) -> Range { let range = old_length as u32..self.data.len() as u32; Range::from_index_range(range, self) } /// Clears the arena keeping all allocations pub fn clear(&mut self) { self.data.clear() } pub fn get_span(&self, handle: Handle) -> Span { *self .span_info .get(handle.index()) .unwrap_or(&Span::default()) } /// Assert that `handle` is valid for this arena. pub fn check_contains_handle(&self, handle: Handle) -> Result<(), BadHandle> { if handle.index() < self.data.len() { Ok(()) } else { Err(BadHandle::new(handle)) } } /// Assert that `range` is valid for this arena. pub fn check_contains_range(&self, range: &Range) -> Result<(), BadRangeError> { // Since `range.inner` is a `Range`, we only need to check that the // start precedes the end, and that the end is in range. if range.inner.start > range.inner.end { return Err(BadRangeError::new(range.clone())); } // Empty ranges are tolerated: they can be produced by compaction. if range.inner.start == range.inner.end { return Ok(()); } let last_handle = Handle::new(Index::new(range.inner.end - 1).unwrap()); if self.check_contains_handle(last_handle).is_err() { return Err(BadRangeError::new(range.clone())); } Ok(()) } pub(crate) fn retain_mut

(&mut self, mut predicate: P) where P: FnMut(Handle, &mut T) -> bool, { let mut index = 0; let mut retained = 0; self.data.retain_mut(|elt| { let handle = Handle::from_usize(index); let keep = predicate(handle, elt); // Since `predicate` needs mutable access to each element, // we can't feasibly call it twice, so we have to compact // spans by hand in parallel as part of this iteration. if keep { self.span_info[retained] = self.span_info[index]; retained += 1; } index += 1; keep }); self.span_info.truncate(retained); } } #[cfg(feature = "deserialize")] impl<'de, T> serde::Deserialize<'de> for Arena where T: serde::Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let data = Vec::deserialize(deserializer)?; let span_info = core::iter::repeat_n(Span::default(), data.len()).collect(); Ok(Self { data, span_info }) } } impl ops::Index> for Arena { type Output = T; fn index(&self, handle: Handle) -> &T { &self.data[handle.index()] } } impl ops::IndexMut> for Arena { fn index_mut(&mut self, handle: Handle) -> &mut T { &mut self.data[handle.index()] } } impl ops::Index> for Arena { type Output = [T]; fn index(&self, range: Range) -> &[T] { &self.data[range.inner.start as usize..range.inner.end as usize] } } #[cfg(test)] mod tests { use super::*; #[test] fn append_non_unique() { let mut arena: Arena = Arena::new(); let t1 = arena.append(0, Default::default()); let t2 = arena.append(0, Default::default()); assert!(t1 != t2); assert!(arena[t1] == arena[t2]); } #[test] fn append_unique() { let mut arena: Arena = Arena::new(); let t1 = arena.append(0, Default::default()); let t2 = arena.append(1, Default::default()); assert!(t1 != t2); assert!(arena[t1] != arena[t2]); } #[test] fn fetch_or_append_non_unique() { let mut arena: Arena = Arena::new(); let t1 = arena.fetch_or_append(0, Default::default()); let t2 = arena.fetch_or_append(0, Default::default()); assert!(t1 == t2); assert!(arena[t1] == arena[t2]) } #[test] fn fetch_or_append_unique() { let mut arena: Arena = Arena::new(); let t1 = arena.fetch_or_append(0, Default::default()); let t2 = arena.fetch_or_append(1, Default::default()); assert!(t1 != t2); assert!(arena[t1] != arena[t2]); } } ================================================ FILE: naga/src/arena/range.rs ================================================ //! Well-typed ranges of [`Arena`]s. //! //! This module defines the [`Range`] type, representing a contiguous range of //! entries in an [`Arena`]. //! //! [`Arena`]: super::Arena use core::{fmt, marker::PhantomData, ops}; use super::{ handle::{Handle, Index}, Arena, }; /// A strongly typed range of handles. #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[cfg_attr( any(feature = "serialize", feature = "deserialize"), serde(transparent) )] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(test, derive(PartialEq))] pub struct Range { pub(super) inner: ops::Range, #[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(skip))] marker: PhantomData, } impl Range { pub(crate) const fn erase_type(self) -> Range<()> { let Self { inner, marker: _ } = self; Range { inner, marker: PhantomData, } } } // NOTE: Keep this diagnostic in sync with that of [`BadHandle`]. #[derive(Clone, Debug, thiserror::Error)] #[cfg_attr(test, derive(PartialEq))] #[error("Handle range {range:?} of {kind} is either not present, or inaccessible yet")] pub struct BadRangeError { // This error is used for many `Handle` types, but there's no point in making this generic, so // we just flatten them all to `Handle<()>` here. kind: &'static str, range: Range<()>, } impl BadRangeError { pub fn new(range: Range) -> Self { Self { kind: core::any::type_name::(), range: range.erase_type(), } } } impl Clone for Range { fn clone(&self) -> Self { Range { inner: self.inner.clone(), marker: self.marker, } } } impl fmt::Debug for Range { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "[{}..{}]", self.inner.start, self.inner.end) } } impl Iterator for Range { type Item = Handle; fn next(&mut self) -> Option { if self.inner.start < self.inner.end { let next = self.inner.start; self.inner.start += 1; Some(Handle::new(Index::new(next).unwrap())) } else { None } } } impl Range { /// Return a range enclosing handles `first` through `last`, inclusive. pub fn new_from_bounds(first: Handle, last: Handle) -> Self { Self { inner: (first.index() as u32)..(last.index() as u32 + 1), marker: Default::default(), } } /// Return a range covering all handles with indices from `0` to `size`. pub(super) fn full_range_from_size(size: usize) -> Self { Self { inner: 0..size as u32, marker: Default::default(), } } /// return the first and last handles included in `self`. /// /// If `self` is an empty range, there are no handles included, so /// return `None`. pub const fn first_and_last(&self) -> Option<(Handle, Handle)> { if self.inner.start < self.inner.end { Some(( // `Range::new_from_bounds` expects a start- and end-inclusive // range, but `self.inner` is an end-exclusive range. Handle::new(Index::new(self.inner.start).unwrap()), Handle::new(Index::new(self.inner.end - 1).unwrap()), )) } else { None } } /// Return the index range covered by `self`. pub fn index_range(&self) -> ops::Range { self.inner.clone() } /// Construct a `Range` that covers the indices in `inner`. pub fn from_index_range(inner: ops::Range, arena: &Arena) -> Self { // Since `inner` is a `Range`, we only need to check that // the start and end are well-ordered, and that the end fits // within `arena`. assert!(inner.start <= inner.end); assert!(inner.end as usize <= arena.len()); Self { inner, marker: Default::default(), } } } ================================================ FILE: naga/src/arena/unique_arena.rs ================================================ //! The [`UniqueArena`] type and supporting definitions. use alloc::vec::Vec; use core::{fmt, hash, ops}; use super::handle::{BadHandle, Handle, Index}; use crate::{FastIndexSet, Span}; /// An arena whose elements are guaranteed to be unique. /// /// A `UniqueArena` holds a set of unique values of type `T`, each with an /// associated [`Span`]. Inserting a value returns a `Handle`, which can be /// used to index the `UniqueArena` and obtain shared access to the `T` element. /// Access via a `Handle` is an array lookup - no hash lookup is necessary. /// /// The element type must implement `Eq` and `Hash`. Insertions of equivalent /// elements, according to `Eq`, all return the same `Handle`. /// /// Once inserted, elements generally may not be mutated, although a `replace` /// method exists to support rare cases. /// /// `UniqueArena` is similar to [`Arena`]: If `Arena` is vector-like, /// `UniqueArena` is `HashSet`-like. /// /// [`Arena`]: super::Arena #[derive(Clone)] pub struct UniqueArena { set: FastIndexSet, /// Spans for the elements, indexed by handle. /// /// The length of this vector is always equal to `set.len()`. `FastIndexSet` /// promises that its elements "are indexed in a compact range, without /// holes in the range 0..set.len()", so we can always use the indices /// returned by insertion as indices into this vector. span_info: Vec, } impl UniqueArena { /// Create a new arena with no initial capacity allocated. pub fn new() -> Self { UniqueArena { set: FastIndexSet::default(), span_info: Vec::new(), } } /// Return the current number of items stored in this arena. pub fn len(&self) -> usize { self.set.len() } /// Return `true` if the arena contains no elements. pub fn is_empty(&self) -> bool { self.set.is_empty() } /// Clears the arena, keeping all allocations. pub fn clear(&mut self) { self.set.clear(); self.span_info.clear(); } /// Return the span associated with `handle`. /// /// If a value has been inserted multiple times, the span returned is the /// one provided with the first insertion. pub fn get_span(&self, handle: Handle) -> Span { *self .span_info .get(handle.index()) .unwrap_or(&Span::default()) } pub(crate) fn drain_all(&mut self) -> UniqueArenaDrain<'_, T> { UniqueArenaDrain { inner_elts: self.set.drain(..), inner_spans: self.span_info.drain(..), index: Index::new(0).unwrap(), } } } pub struct UniqueArenaDrain<'a, T> { inner_elts: indexmap::set::Drain<'a, T>, inner_spans: alloc::vec::Drain<'a, Span>, index: Index, } impl Iterator for UniqueArenaDrain<'_, T> { type Item = (Handle, T, Span); fn next(&mut self) -> Option { match self.inner_elts.next() { Some(elt) => { let handle = Handle::new(self.index); self.index = self.index.checked_add(1).unwrap(); let span = self.inner_spans.next().unwrap(); Some((handle, elt, span)) } None => None, } } } impl UniqueArena { /// Returns an iterator over the items stored in this arena, returning both /// the item's handle and a reference to it. pub fn iter(&self) -> impl DoubleEndedIterator, &T)> + ExactSizeIterator { self.set.iter().enumerate().map(|(i, v)| { let index = Index::new(i as u32).unwrap(); (Handle::new(index), v) }) } /// Insert a new value into the arena. /// /// Return a [`Handle`], which can be used to index this arena to get a /// shared reference to the element. /// /// If this arena already contains an element that is `Eq` to `value`, /// return a `Handle` to the existing element, and drop `value`. /// /// If `value` is inserted into the arena, associate `span` with /// it. An element's span can be retrieved with the [`get_span`] /// method. /// /// [`Handle`]: Handle /// [`get_span`]: UniqueArena::get_span pub fn insert(&mut self, value: T, span: Span) -> Handle { let (index, added) = self.set.insert_full(value); if added { debug_assert!(index == self.span_info.len()); self.span_info.push(span); } debug_assert!(self.set.len() == self.span_info.len()); Handle::from_usize(index) } /// Replace an old value with a new value. /// /// # Panics /// /// - if the old value is not in the arena /// - if the new value already exists in the arena pub fn replace(&mut self, old: Handle, new: T) { let (index, added) = self.set.insert_full(new); assert!(added && index == self.set.len() - 1); self.set.swap_remove_index(old.index()).unwrap(); } /// Return this arena's handle for `value`, if present. /// /// If this arena already contains an element equal to `value`, /// return its handle. Otherwise, return `None`. pub fn get(&self, value: &T) -> Option> { self.set .get_index_of(value) .map(|index| Handle::from_usize(index)) } /// Return this arena's value at `handle`, if that is a valid handle. pub fn get_handle(&self, handle: Handle) -> Result<&T, BadHandle> { self.set .get_index(handle.index()) .ok_or_else(|| BadHandle::new(handle)) } /// Assert that `handle` is valid for this arena. pub fn check_contains_handle(&self, handle: Handle) -> Result<(), BadHandle> { if handle.index() < self.set.len() { Ok(()) } else { Err(BadHandle::new(handle)) } } } impl Default for UniqueArena { fn default() -> Self { Self::new() } } impl fmt::Debug for UniqueArena { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_map().entries(self.iter()).finish() } } impl ops::Index> for UniqueArena { type Output = T; fn index(&self, handle: Handle) -> &T { &self.set[handle.index()] } } #[cfg(feature = "serialize")] impl serde::Serialize for UniqueArena where T: Eq + hash::Hash + serde::Serialize, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.set.serialize(serializer) } } #[cfg(feature = "deserialize")] impl<'de, T> serde::Deserialize<'de> for UniqueArena where T: Eq + hash::Hash + serde::Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let set = FastIndexSet::deserialize(deserializer)?; let span_info = core::iter::repeat_n(Span::default(), set.len()).collect(); Ok(Self { set, span_info }) } } //Note: largely borrowed from `HashSet` implementation #[cfg(feature = "arbitrary")] impl<'a, T> arbitrary::Arbitrary<'a> for UniqueArena where T: Eq + hash::Hash + arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let mut arena = Self::default(); for elem in u.arbitrary_iter()? { arena.set.insert(elem?); arena.span_info.push(Span::UNDEFINED); } Ok(arena) } fn arbitrary_take_rest(u: arbitrary::Unstructured<'a>) -> arbitrary::Result { let mut arena = Self::default(); for elem in u.arbitrary_take_rest_iter()? { arena.set.insert(elem?); arena.span_info.push(Span::UNDEFINED); } Ok(arena) } #[inline] fn size_hint(depth: usize) -> (usize, Option) { let depth_hint = ::size_hint(depth); arbitrary::size_hint::and(depth_hint, (0, None)) } } ================================================ FILE: naga/src/back/continue_forward.rs ================================================ //! Workarounds for platform bugs and limitations in switches and loops. //! //! In these docs, we use CamelCase links for Naga IR concepts, and ordinary //! `code` formatting for HLSL or GLSL concepts. //! //! ## Avoiding `continue` within `switch` //! //! As described in , the FXC HLSL //! compiler doesn't allow `continue` statements within `switch` statements, but //! Naga IR does. We work around this by introducing synthetic boolean local //! variables and branches. //! //! Specifically: //! //! - We generate code for [`Continue`] statements within [`SwitchCase`]s that //! sets an introduced `bool` local to `true` and does a `break`, jumping to //! immediately after the generated `switch`. //! //! - When generating code for a [`Switch`] statement, we conservatively assume //! it might contain such a [`Continue`] statement, so: //! //! - If it's the outermost such [`Switch`] within a [`Loop`], we declare the //! `bool` local ahead of the switch, initialized to `false`. Immediately //! after the `switch`, we check the local and do a `continue` if it's set. //! //! - If the [`Switch`] is nested within other [`Switch`]es, then after the //! generated `switch`, we check the local (which we know was declared //! before the surrounding `switch`) and do a `break` if it's set. //! //! - As an optimization, we only generate the check of the local if a //! [`Continue`] statement is encountered within the [`Switch`]. This may //! help drivers more easily identify that the `bool` is unused. //! //! So while we "weaken" the [`Continue`] statement by rendering it as a `break` //! statement, we also place checks immediately at the locations to which those //! `break` statements will jump, until we can be sure we've reached the //! intended target of the original [`Continue`]. //! //! In the case of nested [`Loop`] and [`Switch`] statements, there may be //! multiple introduced `bool` locals in scope, but there's no problem knowing //! which one to operate on. At any point, there is at most one [`Loop`] //! statement that could be targeted by a [`Continue`] statement, so the correct //! `bool` local to set and test is always the one introduced for the innermost //! enclosing [`Loop`]'s outermost [`Switch`]. //! //! # Avoiding single body `switch` statements //! //! As described in , some language //! front ends miscompile `switch` statements where all cases branch to the same //! body. Our HLSL and GLSL backends render [`Switch`] statements with a single //! [`SwitchCase`] as `do {} while(false);` loops. //! //! However, this rewriting introduces a new loop that could "capture" //! `continue` statements in its body. To avoid doing so, we apply the //! [`Continue`]-to-`break` transformation described above. //! //! [`Continue`]: crate::Statement::Continue //! [`Loop`]: crate::Statement::Loop //! [`Switch`]: crate::Statement::Switch //! [`SwitchCase`]: crate::SwitchCase use alloc::{rc::Rc, string::String, vec::Vec}; use crate::proc::Namer; /// A summary of the code surrounding a statement. enum Nesting { /// Currently nested in at least one [`Loop`] statement. /// /// [`Continue`] should apply to the innermost loop. /// /// When this entry is on the top of the stack: /// /// * When entering an inner [`Loop`] statement, push a [`Loop`][nl] state /// onto the stack. /// /// * When entering a nested [`Switch`] statement, push a [`Switch`][ns] /// state onto the stack with a new variable name. Before the generated /// `switch`, introduce a `bool` local with that name, initialized to /// `false`. /// /// When exiting the [`Loop`] for which this entry was pushed, pop it from /// the stack. /// /// [`Continue`]: crate::Statement::Continue /// [`Loop`]: crate::Statement::Loop /// [`Switch`]: crate::Statement::Switch /// [ns]: Nesting::Switch /// [nl]: Nesting::Loop Loop, /// Currently nested in at least one [`Switch`] that may need to forward /// [`Continue`]s. /// /// This includes [`Switch`]es rendered as `do {} while(false)` loops, but /// doesn't need to include regular [`Switch`]es in backends that can /// support `continue` within switches. /// /// [`Continue`] should be forwarded to the innermost surrounding [`Loop`]. /// /// When this entry is on the top of the stack: /// /// * When entering a nested [`Loop`], push a [`Loop`][nl] state onto the /// stack. /// /// * When entering a nested [`Switch`], push a [`Switch`][ns] state onto /// the stack with a clone of the introduced `bool` variable's name. /// /// * When encountering a [`Continue`] statement, render it as code to set /// the introduced `bool` local (whose name is held in [`variable`]) to /// `true`, and then `break`. Set [`continue_encountered`] to `true` to /// record that the [`Switch`] contains a [`Continue`]. /// /// * When exiting this [`Switch`], pop its entry from the stack. If /// [`continue_encountered`] is set, then we have rendered [`Continue`] /// statements as `break` statements that jump to this point. Generate /// code to check `variable`, and if it is `true`: /// /// * If there is another [`Switch`][ns] left on top of the stack, set /// its `continue_encountered` flag, and generate a `break`. (Both /// [`Switch`][ns]es are within the same [`Loop`] and share the same /// introduced variable, so there's no need to set another flag to /// continue to exit the `switch`es.) /// /// * Otherwise, `continue`. /// /// When we exit the [`Switch`] for which this entry was pushed, pop it. /// /// [`Continue`]: crate::Statement::Continue /// [`Loop`]: crate::Statement::Loop /// [`Switch`]: crate::Statement::Switch /// [`variable`]: Nesting::Switch::variable /// [`continue_encountered`]: Nesting::Switch::continue_encountered /// [ns]: Nesting::Switch /// [nl]: Nesting::Loop Switch { variable: Rc, /// Set if we've generated code for a [`Continue`] statement with this /// entry on the top of the stack. /// /// If this is still clear when we finish rendering the [`Switch`], then /// we know we don't need to generate branch forwarding code. Omitting /// that may make it easier for drivers to tell that the `bool` we /// introduced ahead of the [`Switch`] is actually unused. /// /// [`Continue`]: crate::Statement::Continue /// [`Switch`]: crate::Statement::Switch continue_encountered: bool, }, } /// A micro-IR for code a backend should generate after a [`Switch`]. /// /// [`Switch`]: crate::Statement::Switch pub(super) enum ExitControlFlow { None, /// Emit `if (continue_variable) { continue; }` Continue { variable: Rc, }, /// Emit `if (continue_variable) { break; }` /// /// Used after a [`Switch`] to exit from an enclosing [`Switch`]. /// /// After the enclosing switch, its associated check will consult this same /// variable, see that it is set, and exit early. /// /// [`Switch`]: crate::Statement::Switch Break { variable: Rc, }, } /// Utility for tracking nesting of loops and switches to orchestrate forwarding /// of continue statements inside of a switch to the enclosing loop. /// /// See [module docs](self) for why we need this. #[derive(Default)] pub(super) struct ContinueCtx { stack: Vec, } impl ContinueCtx { /// Resets internal state. /// /// Use this to reuse memory between writing sessions. #[allow(dead_code, reason = "only used by some backends")] pub fn clear(&mut self) { self.stack.clear(); } /// Updates internal state to record entering a [`Loop`] statement. /// /// [`Loop`]: crate::Statement::Loop pub fn enter_loop(&mut self) { self.stack.push(Nesting::Loop); } /// Updates internal state to record exiting a [`Loop`] statement. /// /// [`Loop`]: crate::Statement::Loop pub fn exit_loop(&mut self) { if !matches!(self.stack.pop(), Some(Nesting::Loop)) { unreachable!("ContinueCtx stack out of sync"); } } /// Updates internal state to record entering a [`Switch`] statement. /// /// Return `Some(variable)` if this [`Switch`] is nested within a [`Loop`], /// and the caller should introcue a new `bool` local variable named /// `variable` above the `switch`, for forwarding [`Continue`] statements. /// /// `variable` is guaranteed not to conflict with any names used by the /// program itself. /// /// [`Continue`]: crate::Statement::Continue /// [`Loop`]: crate::Statement::Loop /// [`Switch`]: crate::Statement::Switch pub fn enter_switch(&mut self, namer: &mut Namer) -> Option> { match self.stack.last() { // If the stack is empty, we are not in loop, so we don't need to // forward continue statements within this `Switch`. We can leave // the stack empty. None => None, Some(&Nesting::Loop) => { let variable = Rc::new(namer.call("should_continue")); self.stack.push(Nesting::Switch { variable: Rc::clone(&variable), continue_encountered: false, }); Some(variable) } Some(&Nesting::Switch { ref variable, .. }) => { self.stack.push(Nesting::Switch { variable: Rc::clone(variable), continue_encountered: false, }); // We have already declared the variable before some enclosing // `Switch`. None } } } /// Update internal state to record leaving a [`Switch`] statement. /// /// Return an [`ExitControlFlow`] value indicating what code should be /// introduced after the generated `switch` to forward continues. /// /// [`Switch`]: crate::Statement::Switch pub fn exit_switch(&mut self) -> ExitControlFlow { match self.stack.pop() { // This doesn't indicate a problem: we don't start pushing entries // for `Switch` statements unless we have an enclosing `Loop`. None => ExitControlFlow::None, Some(Nesting::Loop) => { unreachable!("Unexpected loop state when exiting switch"); } Some(Nesting::Switch { variable, continue_encountered: inner_continue, }) => { if !inner_continue { // No `Continue` statement was encountered, so we didn't // introduce any `break`s jumping to this point. ExitControlFlow::None } else if let Some(&mut Nesting::Switch { continue_encountered: ref mut outer_continue, .. }) = self.stack.last_mut() { // This is nested in another `Switch`. Propagate upwards // that there is a continue statement present. *outer_continue = true; ExitControlFlow::Break { variable } } else { ExitControlFlow::Continue { variable } } } } } /// Determine what to generate for a [`Continue`] statement. /// /// If we can generate an ordinary `continue` statement, return `None`. /// /// Otherwise, we're enclosed by a [`Switch`] that is itself enclosed by a /// [`Loop`]. Return `Some(variable)` to indicate that the [`Continue`] /// should be rendered as setting `variable` to `true`, and then doing a /// `break`. /// /// This also notes that we've encountered a [`Continue`] statement, so that /// we can generate the right code to forward the branch following the /// enclosing `switch`. /// /// [`Continue`]: crate::Statement::Continue /// [`Loop`]: crate::Statement::Loop /// [`Switch`]: crate::Statement::Switch pub fn continue_encountered(&mut self) -> Option<&str> { if let Some(&mut Nesting::Switch { ref variable, ref mut continue_encountered, }) = self.stack.last_mut() { *continue_encountered = true; Some(variable) } else { None } } } ================================================ FILE: naga/src/back/dot/mod.rs ================================================ /*! Backend for [DOT][dot] (Graphviz). This backend writes a graph in the DOT language, for the ease of IR inspection and debugging. [dot]: https://graphviz.org/doc/info/lang.html */ use alloc::{ borrow::Cow, format, string::{String, ToString}, vec::Vec, }; use core::fmt::{Error as FmtError, Write as _}; use crate::{ arena::Handle, valid::{FunctionInfo, ModuleInfo}, }; /// Configuration options for the dot backend #[derive(Clone, Default)] pub struct Options { /// Only emit function bodies pub cfg_only: bool, } /// Identifier used to address a graph node type NodeId = usize; /// Stores the target nodes for control flow statements #[derive(Default, Clone, Copy)] struct Targets { /// The node, if some, where continue operations will land continue_target: Option, /// The node, if some, where break operations will land break_target: Option, } /// Stores information about the graph of statements #[derive(Default)] struct StatementGraph { /// List of node names nodes: Vec<&'static str>, /// List of edges of the control flow, the items are defined as /// (from, to, label) flow: Vec<(NodeId, NodeId, &'static str)>, /// List of implicit edges of the control flow, used for jump /// operations such as continue or break, the items are defined as /// (from, to, label, color_id) jumps: Vec<(NodeId, NodeId, &'static str, usize)>, /// List of dependency relationships between a statement node and /// expressions dependencies: Vec<(NodeId, Handle, &'static str)>, /// List of expression emitted by statement node emits: Vec<(NodeId, Handle)>, /// List of function call by statement node calls: Vec<(NodeId, Handle)>, } impl StatementGraph { /// Adds a new block to the statement graph, returning the first and last node, respectively fn add(&mut self, block: &[crate::Statement], targets: Targets) -> (NodeId, NodeId) { use crate::Statement as S; // The first node of the block isn't a statement but a virtual node let root = self.nodes.len(); self.nodes.push(if root == 0 { "Root" } else { "Node" }); // Track the last placed node, this will be returned to the caller and // will also be used to generate the control flow edges let mut last_node = root; for statement in block { // Reserve a new node for the current statement and link it to the // node of the previous statement let id = self.nodes.len(); self.flow.push((last_node, id, "")); self.nodes.push(""); // reserve space // Track the node identifier for the merge node, the merge node is // the last node of a statement, normally this is the node itself, // but for control flow statements such as `if`s and `switch`s this // is a virtual node where all branches merge back. let mut merge_id = id; self.nodes[id] = match *statement { S::Emit(ref range) => { for handle in range.clone() { self.emits.push((id, handle)); } "Emit" } S::Kill => "Kill", //TODO: link to the beginning S::Break => { // Try to link to the break target, otherwise produce // a broken connection if let Some(target) = targets.break_target { self.jumps.push((id, target, "Break", 5)) } else { self.jumps.push((id, root, "Broken", 7)) } "Break" } S::Continue => { // Try to link to the continue target, otherwise produce // a broken connection if let Some(target) = targets.continue_target { self.jumps.push((id, target, "Continue", 5)) } else { self.jumps.push((id, root, "Broken", 7)) } "Continue" } S::ControlBarrier(_flags) => "ControlBarrier", S::MemoryBarrier(_flags) => "MemoryBarrier", S::Block(ref b) => { let (other, last) = self.add(b, targets); self.flow.push((id, other, "")); // All following nodes should connect to the end of the block // statement so change the merge id to it. merge_id = last; "Block" } S::If { condition, ref accept, ref reject, } => { self.dependencies.push((id, condition, "condition")); let (accept_id, accept_last) = self.add(accept, targets); self.flow.push((id, accept_id, "accept")); let (reject_id, reject_last) = self.add(reject, targets); self.flow.push((id, reject_id, "reject")); // Create a merge node, link the branches to it and set it // as the merge node to make the next statement node link to it merge_id = self.nodes.len(); self.nodes.push("Merge"); self.flow.push((accept_last, merge_id, "")); self.flow.push((reject_last, merge_id, "")); "If" } S::Switch { selector, ref cases, } => { self.dependencies.push((id, selector, "selector")); // Create a merge node and set it as the merge node to make // the next statement node link to it merge_id = self.nodes.len(); self.nodes.push("Merge"); // Create a new targets structure and set the break target // to the merge node let mut targets = targets; targets.break_target = Some(merge_id); for case in cases { let (case_id, case_last) = self.add(&case.body, targets); let label = match case.value { crate::SwitchValue::Default => "default", _ => "case", }; self.flow.push((id, case_id, label)); // Link the last node of the branch to the merge node self.flow.push((case_last, merge_id, "")); } "Switch" } S::Loop { ref body, ref continuing, break_if, } => { // Create a new targets structure and set the break target // to the merge node, this must happen before generating the // continuing block since it can break. let mut targets = targets; targets.break_target = Some(id); let (continuing_id, continuing_last) = self.add(continuing, targets); // Set the the continue target to the beginning // of the newly generated continuing block targets.continue_target = Some(continuing_id); let (body_id, body_last) = self.add(body, targets); self.flow.push((id, body_id, "body")); // Link the last node of the body to the continuing block self.flow.push((body_last, continuing_id, "continuing")); // Link the last node of the continuing block back to the // beginning of the loop body self.flow.push((continuing_last, body_id, "continuing")); if let Some(expr) = break_if { self.dependencies.push((continuing_id, expr, "break if")); } "Loop" } S::Return { value } => { if let Some(expr) = value { self.dependencies.push((id, expr, "value")); } "Return" } S::Store { pointer, value } => { self.dependencies.push((id, value, "value")); self.emits.push((id, pointer)); "Store" } S::ImageStore { image, coordinate, array_index, value, } => { self.dependencies.push((id, image, "image")); self.dependencies.push((id, coordinate, "coordinate")); if let Some(expr) = array_index { self.dependencies.push((id, expr, "array_index")); } self.dependencies.push((id, value, "value")); "ImageStore" } S::Call { function, ref arguments, result, } => { for &arg in arguments { self.dependencies.push((id, arg, "arg")); } if let Some(expr) = result { self.emits.push((id, expr)); } self.calls.push((id, function)); "Call" } S::Atomic { pointer, ref fun, value, result, } => { if let Some(result) = result { self.emits.push((id, result)); } self.dependencies.push((id, pointer, "pointer")); self.dependencies.push((id, value, "value")); if let crate::AtomicFunction::Exchange { compare: Some(cmp) } = *fun { self.dependencies.push((id, cmp, "cmp")); } "Atomic" } S::ImageAtomic { image, coordinate, array_index, fun: _, value, } => { self.dependencies.push((id, image, "image")); self.dependencies.push((id, coordinate, "coordinate")); if let Some(expr) = array_index { self.dependencies.push((id, expr, "array_index")); } self.dependencies.push((id, value, "value")); "ImageAtomic" } S::WorkGroupUniformLoad { pointer, result } => { self.emits.push((id, result)); self.dependencies.push((id, pointer, "pointer")); "WorkGroupUniformLoad" } S::RayQuery { query, ref fun } => { self.dependencies.push((id, query, "query")); match *fun { crate::RayQueryFunction::Initialize { acceleration_structure, descriptor, } => { self.dependencies.push(( id, acceleration_structure, "acceleration_structure", )); self.dependencies.push((id, descriptor, "descriptor")); "RayQueryInitialize" } crate::RayQueryFunction::Proceed { result } => { self.emits.push((id, result)); "RayQueryProceed" } crate::RayQueryFunction::GenerateIntersection { hit_t } => { self.dependencies.push((id, hit_t, "hit_t")); "RayQueryGenerateIntersection" } crate::RayQueryFunction::ConfirmIntersection => { "RayQueryConfirmIntersection" } crate::RayQueryFunction::Terminate => "RayQueryTerminate", } } S::SubgroupBallot { result, predicate } => { if let Some(predicate) = predicate { self.dependencies.push((id, predicate, "predicate")); } self.emits.push((id, result)); "SubgroupBallot" } S::SubgroupCollectiveOperation { op, collective_op, argument, result, } => { self.dependencies.push((id, argument, "arg")); self.emits.push((id, result)); match (collective_op, op) { (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::All) => { "SubgroupAll" } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Any) => { "SubgroupAny" } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Add) => { "SubgroupAdd" } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Mul) => { "SubgroupMul" } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Max) => { "SubgroupMax" } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Min) => { "SubgroupMin" } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::And) => { "SubgroupAnd" } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Or) => { "SubgroupOr" } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Xor) => { "SubgroupXor" } ( crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Add, ) => "SubgroupExclusiveAdd", ( crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Mul, ) => "SubgroupExclusiveMul", ( crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Add, ) => "SubgroupInclusiveAdd", ( crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Mul, ) => "SubgroupInclusiveMul", _ => unimplemented!(), } } S::SubgroupGather { mode, argument, result, } => { match mode { crate::GatherMode::BroadcastFirst => {} crate::GatherMode::Broadcast(index) | crate::GatherMode::Shuffle(index) | crate::GatherMode::ShuffleDown(index) | crate::GatherMode::ShuffleUp(index) | crate::GatherMode::ShuffleXor(index) | crate::GatherMode::QuadBroadcast(index) => { self.dependencies.push((id, index, "index")) } crate::GatherMode::QuadSwap(_) => {} } self.dependencies.push((id, argument, "arg")); self.emits.push((id, result)); match mode { crate::GatherMode::BroadcastFirst => "SubgroupBroadcastFirst", crate::GatherMode::Broadcast(_) => "SubgroupBroadcast", crate::GatherMode::Shuffle(_) => "SubgroupShuffle", crate::GatherMode::ShuffleDown(_) => "SubgroupShuffleDown", crate::GatherMode::ShuffleUp(_) => "SubgroupShuffleUp", crate::GatherMode::ShuffleXor(_) => "SubgroupShuffleXor", crate::GatherMode::QuadBroadcast(_) => "SubgroupQuadBroadcast", crate::GatherMode::QuadSwap(direction) => match direction { crate::Direction::X => "SubgroupQuadSwapX", crate::Direction::Y => "SubgroupQuadSwapY", crate::Direction::Diagonal => "SubgroupQuadSwapDiagonal", }, } } S::CooperativeStore { target, data } => { self.dependencies.push((id, target, "target")); self.dependencies.push((id, data.pointer, "pointer")); self.dependencies.push((id, data.stride, "stride")); if data.row_major { "CoopStoreT" } else { "CoopStore" } } S::RayPipelineFunction(func) => match func { crate::RayPipelineFunction::TraceRay { acceleration_structure, descriptor, payload, } => { self.dependencies.push(( id, acceleration_structure, "acceleration_structure", )); self.dependencies.push((id, descriptor, "descriptor")); self.dependencies.push((id, payload, "payload")); "TraceRay" } }, }; // Set the last node to the merge node last_node = merge_id; } (root, last_node) } } fn name(option: &Option) -> &str { option.as_deref().unwrap_or_default() } /// set39 color scheme from const COLORS: &[&str] = &[ "white", // pattern starts at 1 "#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#d9d9d9", ]; struct Prefixed(Handle); impl core::fmt::Display for Prefixed { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.0.write_prefixed(f, "e") } } impl core::fmt::Display for Prefixed { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.0.write_prefixed(f, "l") } } impl core::fmt::Display for Prefixed { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.0.write_prefixed(f, "g") } } impl core::fmt::Display for Prefixed { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.0.write_prefixed(f, "f") } } fn write_fun( output: &mut String, prefix: String, fun: &crate::Function, info: Option<&FunctionInfo>, options: &Options, ) -> Result<(), FmtError> { writeln!(output, "\t\tnode [ style=filled ]")?; if !options.cfg_only { for (handle, var) in fun.local_variables.iter() { writeln!( output, "\t\t{}_{} [ shape=hexagon label=\"{:?} '{}'\" ]", prefix, Prefixed(handle), handle, name(&var.name), )?; } write_function_expressions(output, &prefix, fun, info)?; } let mut sg = StatementGraph::default(); sg.add(&fun.body, Targets::default()); for (index, label) in sg.nodes.into_iter().enumerate() { writeln!( output, "\t\t{prefix}_s{index} [ shape=square label=\"{label}\" ]", )?; } for (from, to, label) in sg.flow { writeln!( output, "\t\t{prefix}_s{from} -> {prefix}_s{to} [ arrowhead=tee label=\"{label}\" ]", )?; } for (from, to, label, color_id) in sg.jumps { writeln!( output, "\t\t{}_s{} -> {}_s{} [ arrowhead=tee style=dashed color=\"{}\" label=\"{}\" ]", prefix, from, prefix, to, COLORS[color_id], label, )?; } if !options.cfg_only { for (to, expr, label) in sg.dependencies { writeln!( output, "\t\t{}_{} -> {}_s{} [ label=\"{}\" ]", prefix, Prefixed(expr), prefix, to, label, )?; } for (from, to) in sg.emits { writeln!( output, "\t\t{}_s{} -> {}_{} [ style=dotted ]", prefix, from, prefix, Prefixed(to), )?; } } assert!(sg.calls.is_empty()); for (from, function) in sg.calls { writeln!( output, "\t\t{}_s{} -> {}_s0", prefix, from, Prefixed(function), )?; } Ok(()) } fn write_function_expressions( output: &mut String, prefix: &str, fun: &crate::Function, info: Option<&FunctionInfo>, ) -> Result<(), FmtError> { enum Payload<'a> { Arguments(&'a [Handle]), Local(Handle), Global(Handle), } let mut edges = crate::FastHashMap::<&str, _>::default(); let mut payload = None; for (handle, expression) in fun.expressions.iter() { use crate::Expression as E; let (label, color_id) = match *expression { E::Literal(_) => ("Literal".into(), 2), E::Constant(_) => ("Constant".into(), 2), E::Override(_) => ("Override".into(), 2), E::ZeroValue(_) => ("ZeroValue".into(), 2), E::Compose { ref components, .. } => { payload = Some(Payload::Arguments(components)); ("Compose".into(), 3) } E::Access { base, index } => { edges.insert("base", base); edges.insert("index", index); ("Access".into(), 1) } E::AccessIndex { base, index } => { edges.insert("base", base); (format!("AccessIndex[{index}]").into(), 1) } E::Splat { size, value } => { edges.insert("value", value); (format!("Splat{size:?}").into(), 3) } E::Swizzle { size, vector, pattern, } => { edges.insert("vector", vector); (format!("Swizzle{:?}", &pattern[..size as usize]).into(), 3) } E::FunctionArgument(index) => (format!("Argument[{index}]").into(), 1), E::GlobalVariable(h) => { payload = Some(Payload::Global(h)); ("Global".into(), 2) } E::LocalVariable(h) => { payload = Some(Payload::Local(h)); ("Local".into(), 1) } E::Load { pointer } => { edges.insert("pointer", pointer); ("Load".into(), 4) } E::ImageSample { image, sampler, gather, coordinate, array_index, offset: _, level, depth_ref, clamp_to_edge: _, } => { edges.insert("image", image); edges.insert("sampler", sampler); edges.insert("coordinate", coordinate); if let Some(expr) = array_index { edges.insert("array_index", expr); } match level { crate::SampleLevel::Auto => {} crate::SampleLevel::Zero => {} crate::SampleLevel::Exact(expr) => { edges.insert("level", expr); } crate::SampleLevel::Bias(expr) => { edges.insert("bias", expr); } crate::SampleLevel::Gradient { x, y } => { edges.insert("grad_x", x); edges.insert("grad_y", y); } } if let Some(expr) = depth_ref { edges.insert("depth_ref", expr); } let string = match gather { Some(component) => Cow::Owned(format!("ImageGather{component:?}")), _ => Cow::Borrowed("ImageSample"), }; (string, 5) } E::ImageLoad { image, coordinate, array_index, sample, level, } => { edges.insert("image", image); edges.insert("coordinate", coordinate); if let Some(expr) = array_index { edges.insert("array_index", expr); } if let Some(sample) = sample { edges.insert("sample", sample); } if let Some(level) = level { edges.insert("level", level); } ("ImageLoad".into(), 5) } E::ImageQuery { image, query } => { edges.insert("image", image); let args = match query { crate::ImageQuery::Size { level } => { if let Some(expr) = level { edges.insert("level", expr); } Cow::from("ImageSize") } _ => Cow::Owned(format!("{query:?}")), }; (args, 7) } E::Unary { op, expr } => { edges.insert("expr", expr); (format!("{op:?}").into(), 6) } E::Binary { op, left, right } => { edges.insert("left", left); edges.insert("right", right); (format!("{op:?}").into(), 6) } E::Select { condition, accept, reject, } => { edges.insert("condition", condition); edges.insert("accept", accept); edges.insert("reject", reject); ("Select".into(), 3) } E::Derivative { axis, ctrl, expr } => { edges.insert("", expr); (format!("d{axis:?}{ctrl:?}").into(), 8) } E::Relational { fun, argument } => { edges.insert("arg", argument); (format!("{fun:?}").into(), 6) } E::Math { fun, arg, arg1, arg2, arg3, } => { edges.insert("arg", arg); if let Some(expr) = arg1 { edges.insert("arg1", expr); } if let Some(expr) = arg2 { edges.insert("arg2", expr); } if let Some(expr) = arg3 { edges.insert("arg3", expr); } (format!("{fun:?}").into(), 7) } E::As { kind, expr, convert, } => { edges.insert("", expr); let string = match convert { Some(width) => format!("Convert<{kind:?},{width}>"), None => format!("Bitcast<{kind:?}>"), }; (string.into(), 3) } E::CallResult(_function) => ("CallResult".into(), 4), E::AtomicResult { .. } => ("AtomicResult".into(), 4), E::WorkGroupUniformLoadResult { .. } => ("WorkGroupUniformLoadResult".into(), 4), E::ArrayLength(expr) => { edges.insert("", expr); ("ArrayLength".into(), 7) } E::RayQueryProceedResult => ("rayQueryProceedResult".into(), 4), E::RayQueryGetIntersection { query, committed } => { edges.insert("", query); let ty = if committed { "Committed" } else { "Candidate" }; (format!("rayQueryGet{ty}Intersection").into(), 4) } E::SubgroupBallotResult => ("SubgroupBallotResult".into(), 4), E::SubgroupOperationResult { .. } => ("SubgroupOperationResult".into(), 4), E::RayQueryVertexPositions { query, committed } => { edges.insert("", query); let ty = if committed { "Committed" } else { "Candidate" }; (format!("get{ty}HitVertexPositions").into(), 4) } E::CooperativeLoad { ref data, .. } => { edges.insert("pointer", data.pointer); edges.insert("stride", data.stride); let suffix = if data.row_major { "T " } else { "" }; (format!("coopLoad{suffix}").into(), 4) } E::CooperativeMultiplyAdd { a, b, c } => { edges.insert("a", a); edges.insert("b", b); edges.insert("c", c); ("cooperativeMultiplyAdd".into(), 4) } }; // give uniform expressions an outline let color_attr = match info { Some(info) if info[handle].uniformity.non_uniform_result.is_none() => "fillcolor", _ => "color", }; writeln!( output, "\t\t{}_{} [ {}=\"{}\" label=\"{:?} {}\" ]", prefix, Prefixed(handle), color_attr, COLORS[color_id], handle, label, )?; for (key, edge) in edges.drain() { writeln!( output, "\t\t{}_{} -> {}_{} [ label=\"{}\" ]", prefix, Prefixed(edge), prefix, Prefixed(handle), key, )?; } match payload.take() { Some(Payload::Arguments(list)) => { write!(output, "\t\t{{")?; for &comp in list { write!(output, " {}_{}", prefix, Prefixed(comp))?; } writeln!(output, " }} -> {}_{}", prefix, Prefixed(handle))?; } Some(Payload::Local(h)) => { writeln!( output, "\t\t{}_{} -> {}_{}", prefix, Prefixed(h), prefix, Prefixed(handle), )?; } Some(Payload::Global(h)) => { writeln!( output, "\t\t{} -> {}_{} [fillcolor=gray]", Prefixed(h), prefix, Prefixed(handle), )?; } None => {} } } Ok(()) } /// Write shader module to a [`String`]. pub fn write( module: &crate::Module, mod_info: Option<&ModuleInfo>, options: Options, ) -> Result { use core::fmt::Write as _; let mut output = String::new(); output += "digraph Module {\n"; if !options.cfg_only { writeln!(output, "\tsubgraph cluster_globals {{")?; writeln!(output, "\t\tlabel=\"Globals\"")?; for (handle, var) in module.global_variables.iter() { writeln!( output, "\t\t{} [ shape=hexagon label=\"{:?} {:?}/'{}'\" ]", Prefixed(handle), handle, var.space, name(&var.name), )?; } writeln!(output, "\t}}")?; } for (handle, fun) in module.functions.iter() { let prefix = Prefixed(handle).to_string(); writeln!(output, "\tsubgraph cluster_{prefix} {{")?; writeln!( output, "\t\tlabel=\"Function{:?}/'{}'\"", handle, name(&fun.name) )?; let info = mod_info.map(|a| &a[handle]); write_fun(&mut output, prefix, fun, info, &options)?; writeln!(output, "\t}}")?; } for (ep_index, ep) in module.entry_points.iter().enumerate() { let prefix = format!("ep{ep_index}"); writeln!(output, "\tsubgraph cluster_{prefix} {{")?; writeln!(output, "\t\tlabel=\"{:?}/'{}'\"", ep.stage, ep.name)?; let info = mod_info.map(|a| a.get_entry_point(ep_index)); write_fun(&mut output, prefix, &ep.function, info, &options)?; writeln!(output, "\t}}")?; } output += "}\n"; Ok(output) } ================================================ FILE: naga/src/back/glsl/conv.rs ================================================ use crate::back::glsl::{BackendResult, Error, VaryingOptions}; /// Structure returned by [`glsl_scalar`] /// /// It contains both a prefix used in other types and the full type name pub(in crate::back::glsl) struct ScalarString<'a> { /// The prefix used to compose other types pub prefix: &'a str, /// The name of the scalar type pub full: &'a str, } /// Helper function that returns scalar related strings /// /// Check [`ScalarString`] for the information provided /// /// # Errors /// If a [`Float`](crate::ScalarKind::Float) with an width that isn't 4 or 8 pub(in crate::back::glsl) const fn glsl_scalar( scalar: crate::Scalar, ) -> Result, Error> { use crate::ScalarKind as Sk; Ok(match scalar.kind { Sk::Sint => ScalarString { prefix: "i", full: "int", }, Sk::Uint => ScalarString { prefix: "u", full: "uint", }, Sk::Float => match scalar.width { 4 => ScalarString { prefix: "", full: "float", }, 8 => ScalarString { prefix: "d", full: "double", }, _ => return Err(Error::UnsupportedScalar(scalar)), }, Sk::Bool => ScalarString { prefix: "b", full: "bool", }, Sk::AbstractInt | Sk::AbstractFloat => { return Err(Error::UnsupportedScalar(scalar)); } }) } /// Helper function that returns the glsl variable name for a builtin pub(in crate::back::glsl) const fn glsl_built_in( built_in: crate::BuiltIn, options: VaryingOptions, ) -> &'static str { use crate::BuiltIn as Bi; match built_in { Bi::Position { .. } => { if options.output { "gl_Position" } else { "gl_FragCoord" } } Bi::ViewIndex => { if options.targeting_webgl { "gl_ViewID_OVR" } else { "uint(gl_ViewIndex)" } } // vertex Bi::BaseInstance => "uint(gl_BaseInstance)", Bi::BaseVertex => "uint(gl_BaseVertex)", Bi::ClipDistances => "gl_ClipDistance", Bi::CullDistance => "gl_CullDistance", Bi::InstanceIndex => { if options.draw_parameters { "(uint(gl_InstanceID) + uint(gl_BaseInstanceARB))" } else { // Must match FIRST_INSTANCE_BINDING "(uint(gl_InstanceID) + naga_vs_first_instance)" } } Bi::PointSize => "gl_PointSize", Bi::VertexIndex => "uint(gl_VertexID)", Bi::DrawIndex => "gl_DrawID", // fragment Bi::FragDepth => "gl_FragDepth", Bi::PointCoord => "gl_PointCoord", Bi::FrontFacing => "gl_FrontFacing", Bi::PrimitiveIndex => "uint(gl_PrimitiveID)", Bi::Barycentric { perspective: true } => "gl_BaryCoordEXT", Bi::Barycentric { perspective: false } => "gl_BaryCoordNoPerspEXT", Bi::SampleIndex => "gl_SampleID", Bi::SampleMask => { if options.output { "gl_SampleMask" } else { "gl_SampleMaskIn" } } // compute Bi::GlobalInvocationId => "gl_GlobalInvocationID", Bi::LocalInvocationId => "gl_LocalInvocationID", Bi::LocalInvocationIndex => "gl_LocalInvocationIndex", Bi::WorkGroupId => "gl_WorkGroupID", Bi::WorkGroupSize => "gl_WorkGroupSize", Bi::NumWorkGroups => "gl_NumWorkGroups", // subgroup Bi::NumSubgroups => "gl_NumSubgroups", Bi::SubgroupId => "gl_SubgroupID", Bi::SubgroupSize => "gl_SubgroupSize", Bi::SubgroupInvocationId => "gl_SubgroupInvocationID", // mesh // TODO: figure out how to map these to glsl things as glsl treats them as arrays Bi::CullPrimitive | Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices | Bi::MeshTaskSize | Bi::VertexCount | Bi::PrimitiveCount | Bi::Vertices | Bi::Primitives | Bi::RayInvocationId | Bi::NumRayInvocations | Bi::InstanceCustomData | Bi::GeometryIndex | Bi::WorldRayOrigin | Bi::WorldRayDirection | Bi::ObjectRayOrigin | Bi::ObjectRayDirection | Bi::RayTmin | Bi::RayTCurrentMax | Bi::ObjectToWorld | Bi::WorldToObject | Bi::HitKind => { unimplemented!() } } } /// Helper function that returns the string corresponding to the address space pub(in crate::back::glsl) const fn glsl_storage_qualifier( space: crate::AddressSpace, ) -> Option<&'static str> { use crate::AddressSpace as As; match space { As::Function => None, As::Private => None, As::Storage { .. } => Some("buffer"), As::Uniform => Some("uniform"), As::Handle => Some("uniform"), As::WorkGroup => Some("shared"), As::Immediate => Some("uniform"), As::TaskPayload | As::RayPayload | As::IncomingRayPayload => unreachable!(), } } /// Helper function that returns the string corresponding to the glsl interpolation qualifier pub(in crate::back::glsl) const fn glsl_interpolation( interpolation: crate::Interpolation, ) -> &'static str { use crate::Interpolation as I; match interpolation { I::Perspective => "smooth", I::Linear => "noperspective", I::Flat => "flat", I::PerVertex => unreachable!(), } } /// Return the GLSL auxiliary qualifier for the given sampling value. pub(in crate::back::glsl) const fn glsl_sampling( sampling: crate::Sampling, ) -> BackendResult> { use crate::Sampling as S; Ok(match sampling { S::First => return Err(Error::FirstSamplingNotSupported), S::Center | S::Either => None, S::Centroid => Some("centroid"), S::Sample => Some("sample"), }) } /// Helper function that returns the glsl dimension string of [`ImageDimension`](crate::ImageDimension) pub(in crate::back::glsl) const fn glsl_dimension(dim: crate::ImageDimension) -> &'static str { use crate::ImageDimension as IDim; match dim { IDim::D1 => "1D", IDim::D2 => "2D", IDim::D3 => "3D", IDim::Cube => "Cube", } } /// Helper function that returns the glsl storage format string of [`StorageFormat`](crate::StorageFormat) pub(in crate::back::glsl) fn glsl_storage_format( format: crate::StorageFormat, ) -> Result<&'static str, Error> { use crate::StorageFormat as Sf; Ok(match format { Sf::R8Unorm => "r8", Sf::R8Snorm => "r8_snorm", Sf::R8Uint => "r8ui", Sf::R8Sint => "r8i", Sf::R16Uint => "r16ui", Sf::R16Sint => "r16i", Sf::R16Float => "r16f", Sf::Rg8Unorm => "rg8", Sf::Rg8Snorm => "rg8_snorm", Sf::Rg8Uint => "rg8ui", Sf::Rg8Sint => "rg8i", Sf::R32Uint => "r32ui", Sf::R32Sint => "r32i", Sf::R32Float => "r32f", Sf::Rg16Uint => "rg16ui", Sf::Rg16Sint => "rg16i", Sf::Rg16Float => "rg16f", Sf::Rgba8Unorm => "rgba8", Sf::Rgba8Snorm => "rgba8_snorm", Sf::Rgba8Uint => "rgba8ui", Sf::Rgba8Sint => "rgba8i", Sf::Rgb10a2Uint => "rgb10_a2ui", Sf::Rgb10a2Unorm => "rgb10_a2", Sf::Rg11b10Ufloat => "r11f_g11f_b10f", Sf::R64Uint => "r64ui", Sf::Rg32Uint => "rg32ui", Sf::Rg32Sint => "rg32i", Sf::Rg32Float => "rg32f", Sf::Rgba16Uint => "rgba16ui", Sf::Rgba16Sint => "rgba16i", Sf::Rgba16Float => "rgba16f", Sf::Rgba32Uint => "rgba32ui", Sf::Rgba32Sint => "rgba32i", Sf::Rgba32Float => "rgba32f", Sf::R16Unorm => "r16", Sf::R16Snorm => "r16_snorm", Sf::Rg16Unorm => "rg16", Sf::Rg16Snorm => "rg16_snorm", Sf::Rgba16Unorm => "rgba16", Sf::Rgba16Snorm => "rgba16_snorm", Sf::Bgra8Unorm => { return Err(Error::Custom( "Support format BGRA8 is not implemented".into(), )) } }) } ================================================ FILE: naga/src/back/glsl/features.rs ================================================ use core::fmt::Write; use super::{BackendResult, Error, Version, Writer}; use crate::{ back::glsl::{Options, WriterFlags}, AddressSpace, Binding, Expression, Handle, ImageClass, ImageDimension, Interpolation, SampleLevel, Sampling, Scalar, ScalarKind, ShaderStage, StorageFormat, Type, TypeInner, }; bitflags::bitflags! { /// Structure used to encode additions to GLSL that aren't supported by all versions. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Features: u32 { /// Buffer address space support. const BUFFER_STORAGE = 1; const ARRAY_OF_ARRAYS = 1 << 1; /// 8 byte floats. const DOUBLE_TYPE = 1 << 2; /// More image formats. const FULL_IMAGE_FORMATS = 1 << 3; const MULTISAMPLED_TEXTURES = 1 << 4; const MULTISAMPLED_TEXTURE_ARRAYS = 1 << 5; const CUBE_TEXTURES_ARRAY = 1 << 6; const COMPUTE_SHADER = 1 << 7; /// Image load and early depth tests. const IMAGE_LOAD_STORE = 1 << 8; const CONSERVATIVE_DEPTH = 1 << 9; /// Interpolation and auxiliary qualifiers. /// /// Perspective, Flat, and Centroid are available in all GLSL versions we support. const NOPERSPECTIVE_QUALIFIER = 1 << 11; const SAMPLE_QUALIFIER = 1 << 12; const CLIP_DISTANCE = 1 << 13; const CULL_DISTANCE = 1 << 14; /// Sample ID. const SAMPLE_VARIABLES = 1 << 15; /// Arrays with a dynamic length. const DYNAMIC_ARRAY_SIZE = 1 << 16; const MULTI_VIEW = 1 << 17; /// Texture samples query const TEXTURE_SAMPLES = 1 << 18; /// Texture levels query const TEXTURE_LEVELS = 1 << 19; /// Image size query const IMAGE_SIZE = 1 << 20; /// Dual source blending const DUAL_SOURCE_BLENDING = 1 << 21; /// Instance index /// /// We can always support this, either through the language or a polyfill const INSTANCE_INDEX = 1 << 22; /// Sample specific LODs of cube / array shadow textures const TEXTURE_SHADOW_LOD = 1 << 23; /// Subgroup operations const SUBGROUP_OPERATIONS = 1 << 24; /// Image atomics const TEXTURE_ATOMICS = 1 << 25; /// Image atomics const SHADER_BARYCENTRICS = 1 << 26; /// Primitive index builtin const PRIMITIVE_INDEX = 1 << 27; } } /// Helper structure used to store the required [`Features`] needed to output a /// [`Module`](crate::Module) /// /// Provides helper methods to check for availability and writing required extensions pub(crate) struct FeaturesManager(Features); impl FeaturesManager { /// Creates a new [`FeaturesManager`] instance pub const fn new() -> Self { Self(Features::empty()) } /// Adds to the list of required [`Features`] pub fn request(&mut self, features: Features) { self.0 |= features } /// Checks if the list of features [`Features`] contains the specified [`Features`] pub const fn contains(&mut self, features: Features) -> bool { self.0.contains(features) } /// Checks that all required [`Features`] are available for the specified /// [`Version`] otherwise returns an [`Error::MissingFeatures`]. pub fn check_availability(&self, version: Version) -> BackendResult { // Will store all the features that are unavailable let mut missing = Features::empty(); // Helper macro to check for feature availability macro_rules! check_feature { // Used when only core glsl supports the feature ($feature:ident, $core:literal) => { if self.0.contains(Features::$feature) && (version < Version::Desktop($core) || version.is_es()) { missing |= Features::$feature; } }; // Used when both core and es support the feature ($feature:ident, $core:literal, $es:literal) => { if self.0.contains(Features::$feature) && (version < Version::Desktop($core) || version < Version::new_gles($es)) { missing |= Features::$feature; } }; } check_feature!(COMPUTE_SHADER, 420, 310); check_feature!(BUFFER_STORAGE, 400, 310); check_feature!(DOUBLE_TYPE, 150); check_feature!(CUBE_TEXTURES_ARRAY, 130, 310); check_feature!(MULTISAMPLED_TEXTURES, 150, 300); check_feature!(MULTISAMPLED_TEXTURE_ARRAYS, 150, 310); check_feature!(ARRAY_OF_ARRAYS, 120, 310); check_feature!(IMAGE_LOAD_STORE, 130, 310); check_feature!(CONSERVATIVE_DEPTH, 130, 300); check_feature!(NOPERSPECTIVE_QUALIFIER, 130); check_feature!(SAMPLE_QUALIFIER, 400, 320); check_feature!(CLIP_DISTANCE, 130, 300 /* with extension */); check_feature!(CULL_DISTANCE, 450, 300 /* with extension */); check_feature!(SAMPLE_VARIABLES, 400, 300); check_feature!(DYNAMIC_ARRAY_SIZE, 400 /* with extension */, 310); check_feature!(DUAL_SOURCE_BLENDING, 330, 300 /* with extension */); check_feature!(SUBGROUP_OPERATIONS, 430, 310); check_feature!(TEXTURE_ATOMICS, 420, 310); match version { Version::Embedded { is_webgl: true, .. } => check_feature!(MULTI_VIEW, 140, 300), _ => check_feature!(MULTI_VIEW, 140, 310), }; // Only available on glsl core, this means that opengl es can't query the number // of samples nor levels in a image and neither do bound checks on the sample nor // the level argument of texelFecth check_feature!(TEXTURE_SAMPLES, 150); check_feature!(TEXTURE_LEVELS, 130); check_feature!(IMAGE_SIZE, 430, 310); check_feature!(TEXTURE_SHADOW_LOD, 200, 300); // Return an error if there are missing features if missing.is_empty() { Ok(()) } else { Err(Error::MissingFeatures(missing)) } } /// Helper method used to write all needed extensions /// /// # Notes /// This won't check for feature availability so it might output extensions that aren't even /// supported.[`check_availability`](Self::check_availability) will check feature availability pub fn write(&self, options: &Options, mut out: impl Write) -> BackendResult { if self.0.contains(Features::COMPUTE_SHADER) && !options.version.is_es() { // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_compute_shader.txt writeln!(out, "#extension GL_ARB_compute_shader : require")?; } if self.0.contains(Features::BUFFER_STORAGE) && !options.version.is_es() { // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_storage_buffer_object.txt writeln!( out, "#extension GL_ARB_shader_storage_buffer_object : require" )?; } if self.0.contains(Features::DOUBLE_TYPE) && options.version < Version::Desktop(400) { // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_gpu_shader_fp64.txt writeln!(out, "#extension GL_ARB_gpu_shader_fp64 : require")?; } if self.0.contains(Features::CUBE_TEXTURES_ARRAY) { if options.version.is_es() { // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_cube_map_array.txt writeln!(out, "#extension GL_EXT_texture_cube_map_array : require")?; } else if options.version < Version::Desktop(400) { // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_cube_map_array.txt writeln!(out, "#extension GL_ARB_texture_cube_map_array : require")?; } } if self.0.contains(Features::MULTISAMPLED_TEXTURE_ARRAYS) && options.version.is_es() { // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_texture_storage_multisample_2d_array.txt writeln!( out, "#extension GL_OES_texture_storage_multisample_2d_array : require" )?; } if self.0.contains(Features::ARRAY_OF_ARRAYS) && options.version < Version::Desktop(430) { // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_arrays_of_arrays.txt writeln!(out, "#extension ARB_arrays_of_arrays : require")?; } if self.0.contains(Features::IMAGE_LOAD_STORE) { if self.0.contains(Features::FULL_IMAGE_FORMATS) && options.version.is_es() { // https://www.khronos.org/registry/OpenGL/extensions/NV/NV_image_formats.txt writeln!(out, "#extension GL_NV_image_formats : require")?; } if options.version < Version::Desktop(420) { // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_image_load_store.txt writeln!(out, "#extension GL_ARB_shader_image_load_store : require")?; } } if self.0.contains(Features::CONSERVATIVE_DEPTH) { if options.version.is_es() { // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_conservative_depth.txt writeln!(out, "#extension GL_EXT_conservative_depth : require")?; } if options.version < Version::Desktop(420) { // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_conservative_depth.txt writeln!(out, "#extension GL_ARB_conservative_depth : require")?; } } if (self.0.contains(Features::CLIP_DISTANCE) || self.0.contains(Features::CULL_DISTANCE)) && options.version.is_es() { // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_clip_cull_distance.txt writeln!(out, "#extension GL_EXT_clip_cull_distance : require")?; } if self.0.contains(Features::SAMPLE_VARIABLES) && options.version.is_es() { // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_sample_variables.txt writeln!(out, "#extension GL_OES_sample_variables : require")?; } if self.0.contains(Features::MULTI_VIEW) { if let Version::Embedded { is_webgl: true, .. } = options.version { // https://www.khronos.org/registry/OpenGL/extensions/OVR/OVR_multiview2.txt writeln!(out, "#extension GL_OVR_multiview2 : require")?; } else { // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_multiview.txt writeln!(out, "#extension GL_EXT_multiview : require")?; } } if self.0.contains(Features::TEXTURE_SAMPLES) { // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_texture_image_samples.txt writeln!( out, "#extension GL_ARB_shader_texture_image_samples : require" )?; } if self.0.contains(Features::TEXTURE_LEVELS) && options.version < Version::Desktop(430) { // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_query_levels.txt writeln!(out, "#extension GL_ARB_texture_query_levels : require")?; } if self.0.contains(Features::DUAL_SOURCE_BLENDING) && options.version.is_es() { // https://registry.khronos.org/OpenGL/extensions/EXT/EXT_blend_func_extended.txt writeln!(out, "#extension GL_EXT_blend_func_extended : require")?; } if self.0.contains(Features::INSTANCE_INDEX) { if options.writer_flags.contains(WriterFlags::DRAW_PARAMETERS) { // https://registry.khronos.org/OpenGL/extensions/ARB/ARB_shader_draw_parameters.txt writeln!(out, "#extension GL_ARB_shader_draw_parameters : require")?; } } if self.0.contains(Features::TEXTURE_SHADOW_LOD) { // https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_shadow_lod.txt writeln!(out, "#extension GL_EXT_texture_shadow_lod : require")?; } if self.0.contains(Features::SUBGROUP_OPERATIONS) { // https://registry.khronos.org/OpenGL/extensions/KHR/KHR_shader_subgroup.txt writeln!(out, "#extension GL_KHR_shader_subgroup_basic : require")?; writeln!(out, "#extension GL_KHR_shader_subgroup_vote : require")?; writeln!( out, "#extension GL_KHR_shader_subgroup_arithmetic : require" )?; writeln!(out, "#extension GL_KHR_shader_subgroup_ballot : require")?; writeln!(out, "#extension GL_KHR_shader_subgroup_shuffle : require")?; writeln!( out, "#extension GL_KHR_shader_subgroup_shuffle_relative : require" )?; writeln!(out, "#extension GL_KHR_shader_subgroup_quad : require")?; } if self.0.contains(Features::TEXTURE_ATOMICS) { // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_shader_image_atomic.txt writeln!(out, "#extension GL_OES_shader_image_atomic : require")?; } if self.0.contains(Features::SHADER_BARYCENTRICS) { // https://github.com/KhronosGroup/GLSL/blob/main/extensions/ext/GLSL_EXT_fragment_shader_barycentric.txt writeln!( out, "#extension GL_EXT_fragment_shader_barycentric : require" )?; } if self.0.contains(Features::PRIMITIVE_INDEX) { match options.version { Version::Embedded { version, .. } if version < 320 => { writeln!(out, "#extension GL_OES_geometry_shader : require")?; } Version::Desktop(version) if version < 150 => { writeln!(out, "#extension GL_ARB_geometry_shader4 : require")?; } _ => (), } } Ok(()) } } impl Writer<'_, W> { /// Helper method that searches the module for all the needed [`Features`] /// /// # Errors /// If the version doesn't support any of the needed [`Features`] a /// [`Error::MissingFeatures`] will be returned pub(super) fn collect_required_features(&mut self) -> BackendResult { let ep_info = self.info.get_entry_point(self.entry_point_idx as usize); if let Some(early_depth_test) = self.entry_point.early_depth_test { match early_depth_test { crate::EarlyDepthTest::Force => { if self.options.version.supports_early_depth_test() { self.features.request(Features::IMAGE_LOAD_STORE); } } crate::EarlyDepthTest::Allow { .. } => { self.features.request(Features::CONSERVATIVE_DEPTH); } } } for arg in self.entry_point.function.arguments.iter() { self.varying_required_features(arg.binding.as_ref(), arg.ty); } if let Some(ref result) = self.entry_point.function.result { self.varying_required_features(result.binding.as_ref(), result.ty); } if let ShaderStage::Compute = self.entry_point.stage { self.features.request(Features::COMPUTE_SHADER) } if self.multiview.is_some() { self.features.request(Features::MULTI_VIEW); } for (ty_handle, ty) in self.module.types.iter() { match ty.inner { TypeInner::Scalar(scalar) | TypeInner::Vector { scalar, .. } | TypeInner::Matrix { scalar, .. } => self.scalar_required_features(scalar), TypeInner::Array { base, size, .. } => { if let TypeInner::Array { .. } = self.module.types[base].inner { self.features.request(Features::ARRAY_OF_ARRAYS) } // If the array is dynamically sized if size == crate::ArraySize::Dynamic { let mut is_used = false; // Check if this type is used in a global that is needed by the current entrypoint for (global_handle, global) in self.module.global_variables.iter() { // Skip unused globals if ep_info[global_handle].is_empty() { continue; } // If this array is the type of a global, then this array is used if global.ty == ty_handle { is_used = true; break; } // If the type of this global is a struct if let TypeInner::Struct { ref members, .. } = self.module.types[global.ty].inner { // Check the last element of the struct to see if it's type uses // this array if let Some(last) = members.last() { if last.ty == ty_handle { is_used = true; break; } } } } // If this dynamically size array is used, we need dynamic array size support if is_used { self.features.request(Features::DYNAMIC_ARRAY_SIZE); } } } TypeInner::Image { dim, arrayed, class, } => { if arrayed && dim == ImageDimension::Cube { self.features.request(Features::CUBE_TEXTURES_ARRAY) } match class { ImageClass::Sampled { multi: true, .. } | ImageClass::Depth { multi: true } => { self.features.request(Features::MULTISAMPLED_TEXTURES); if arrayed { self.features.request(Features::MULTISAMPLED_TEXTURE_ARRAYS); } } ImageClass::Storage { format, .. } => match format { StorageFormat::R8Unorm | StorageFormat::R8Snorm | StorageFormat::R8Uint | StorageFormat::R8Sint | StorageFormat::R16Uint | StorageFormat::R16Sint | StorageFormat::R16Float | StorageFormat::Rg8Unorm | StorageFormat::Rg8Snorm | StorageFormat::Rg8Uint | StorageFormat::Rg8Sint | StorageFormat::Rg16Uint | StorageFormat::Rg16Sint | StorageFormat::Rg16Float | StorageFormat::Rgb10a2Uint | StorageFormat::Rgb10a2Unorm | StorageFormat::Rg11b10Ufloat | StorageFormat::R64Uint | StorageFormat::Rg32Uint | StorageFormat::Rg32Sint | StorageFormat::Rg32Float => { self.features.request(Features::FULL_IMAGE_FORMATS) } _ => {} }, ImageClass::Sampled { multi: false, .. } | ImageClass::Depth { multi: false } | ImageClass::External => {} } } _ => {} } } let mut immediates_used = false; for (handle, global) in self.module.global_variables.iter() { if ep_info[handle].is_empty() { continue; } match global.space { AddressSpace::WorkGroup => self.features.request(Features::COMPUTE_SHADER), AddressSpace::Storage { .. } => self.features.request(Features::BUFFER_STORAGE), AddressSpace::Immediate => { if immediates_used { return Err(Error::MultipleImmediateData); } immediates_used = true; } _ => {} } } // We will need to pass some of the members to a closure, so we need // to separate them otherwise the borrow checker will complain, this // shouldn't be needed in rust 2021 let &mut Self { module, info, ref mut features, entry_point, entry_point_idx, ref policies, .. } = self; // Loop through all expressions in both functions and the entry point // to check for needed features for (expressions, info) in module .functions .iter() .map(|(h, f)| (&f.expressions, &info[h])) .chain(core::iter::once(( &entry_point.function.expressions, info.get_entry_point(entry_point_idx as usize), ))) { for (_, expr) in expressions.iter() { match *expr { // Check for queries that need aditonal features Expression::ImageQuery { image, query, .. } => match query { // Storage images use `imageSize` which is only available // in glsl > 420 // // layers queries are also implemented as size queries crate::ImageQuery::Size { .. } | crate::ImageQuery::NumLayers => { if let TypeInner::Image { class: ImageClass::Storage { .. }, .. } = *info[image].ty.inner_with(&module.types) { features.request(Features::IMAGE_SIZE) } }, crate::ImageQuery::NumLevels => features.request(Features::TEXTURE_LEVELS), crate::ImageQuery::NumSamples => features.request(Features::TEXTURE_SAMPLES), } , // Check for image loads that needs bound checking on the sample // or level argument since this requires a feature Expression::ImageLoad { sample, level, .. } => { if policies.image_load != crate::proc::BoundsCheckPolicy::Unchecked { if sample.is_some() { features.request(Features::TEXTURE_SAMPLES) } if level.is_some() { features.request(Features::TEXTURE_LEVELS) } } } Expression::ImageSample { image, level, offset, .. } => { if let TypeInner::Image { dim, arrayed, class: ImageClass::Depth { .. }, } = *info[image].ty.inner_with(&module.types) { let lod = matches!(level, SampleLevel::Zero | SampleLevel::Exact(_)); let bias = matches!(level, SampleLevel::Bias(_)); let auto = matches!(level, SampleLevel::Auto); let cube = dim == ImageDimension::Cube; let array2d = dim == ImageDimension::D2 && arrayed; let gles = self.options.version.is_es(); // We have a workaround of using `textureGrad` instead of `textureLod` if the LOD is zero, // so we don't *need* this extension for those cases. // But if we're explicitly allowed to use the extension (`WriterFlags::TEXTURE_SHADOW_LOD`), // we always use it instead of the workaround. let grad_workaround_applicable = (array2d || (cube && !arrayed)) && level == SampleLevel::Zero; let prefer_grad_workaround = grad_workaround_applicable && !self.options.writer_flags.contains(WriterFlags::TEXTURE_SHADOW_LOD); let mut ext_used = false; // float texture(sampler2DArrayShadow sampler, vec4 P [, float bias]) // float texture(samplerCubeArrayShadow sampler, vec4 P, float compare [, float bias]) ext_used |= (array2d || cube && arrayed) && bias; // The non `bias` version of this was standardized in GL 4.3, but never in GLES. // float textureOffset(sampler2DArrayShadow sampler, vec4 P, ivec2 offset [, float bias]) ext_used |= array2d && (bias || (gles && auto)) && offset.is_some(); // float textureLod(sampler2DArrayShadow sampler, vec4 P, float lod) // float textureLodOffset(sampler2DArrayShadow sampler, vec4 P, float lod, ivec2 offset) // float textureLod(samplerCubeShadow sampler, vec4 P, float lod) // float textureLod(samplerCubeArrayShadow sampler, vec4 P, float compare, float lod) ext_used |= (cube || array2d) && lod && !prefer_grad_workaround; if ext_used { features.request(Features::TEXTURE_SHADOW_LOD); } } } Expression::SubgroupBallotResult | Expression::SubgroupOperationResult { .. } => { features.request(Features::SUBGROUP_OPERATIONS) } _ => {} } } } for blocks in module .functions .iter() .map(|(_, f)| &f.body) .chain(core::iter::once(&entry_point.function.body)) { for (stmt, _) in blocks.span_iter() { match *stmt { crate::Statement::ImageAtomic { .. } => { features.request(Features::TEXTURE_ATOMICS) } _ => {} } } } self.features.check_availability(self.options.version) } /// Helper method that checks the [`Features`] needed by a scalar fn scalar_required_features(&mut self, scalar: Scalar) { if scalar.kind == ScalarKind::Float && scalar.width == 8 { self.features.request(Features::DOUBLE_TYPE); } } fn varying_required_features(&mut self, binding: Option<&Binding>, ty: Handle) { if let TypeInner::Struct { ref members, .. } = self.module.types[ty].inner { for member in members { self.varying_required_features(member.binding.as_ref(), member.ty); } } else if let Some(binding) = binding { match *binding { Binding::BuiltIn(built_in) => match built_in { crate::BuiltIn::ClipDistances => self.features.request(Features::CLIP_DISTANCE), crate::BuiltIn::CullDistance => self.features.request(Features::CULL_DISTANCE), crate::BuiltIn::SampleIndex => { self.features.request(Features::SAMPLE_VARIABLES) } crate::BuiltIn::ViewIndex => self.features.request(Features::MULTI_VIEW), crate::BuiltIn::InstanceIndex | crate::BuiltIn::DrawIndex => { self.features.request(Features::INSTANCE_INDEX) } crate::BuiltIn::Barycentric { .. } => { self.features.request(Features::SHADER_BARYCENTRICS) } crate::BuiltIn::PrimitiveIndex => { self.features.request(Features::PRIMITIVE_INDEX) } _ => {} }, Binding::Location { location: _, interpolation, sampling, blend_src, per_primitive: _, } => { if interpolation == Some(Interpolation::Linear) { self.features.request(Features::NOPERSPECTIVE_QUALIFIER); } if sampling == Some(Sampling::Sample) { self.features.request(Features::SAMPLE_QUALIFIER); } if blend_src.is_some() { self.features.request(Features::DUAL_SOURCE_BLENDING); } } } } } } ================================================ FILE: naga/src/back/glsl/keywords.rs ================================================ use crate::proc::KeywordSet; use crate::racy_lock::RacyLock; pub const RESERVED_KEYWORDS: &[&str] = &[ // // GLSL 4.6 keywords, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L2004-L2322 // GLSL ES 3.2 keywords, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/es/3.2/GLSL_ES_Specification_3.20.html#L2166-L2478 // // Note: The GLSL ES 3.2 keywords are the same as GLSL 4.6 keywords with some residing in the reserved section. // The only exception are the missing Vulkan keywords which I think is an oversight (see https://github.com/KhronosGroup/OpenGL-Registry/issues/585). // "const", "uniform", "buffer", "shared", "attribute", "varying", "coherent", "volatile", "restrict", "readonly", "writeonly", "atomic_uint", "layout", "centroid", "flat", "smooth", "noperspective", "patch", "sample", "invariant", "precise", "break", "continue", "do", "for", "while", "switch", "case", "default", "if", "else", "subroutine", "in", "out", "inout", "int", "void", "bool", "true", "false", "float", "double", "discard", "return", "vec2", "vec3", "vec4", "ivec2", "ivec3", "ivec4", "bvec2", "bvec3", "bvec4", "uint", "uvec2", "uvec3", "uvec4", "dvec2", "dvec3", "dvec4", "mat2", "mat3", "mat4", "mat2x2", "mat2x3", "mat2x4", "mat3x2", "mat3x3", "mat3x4", "mat4x2", "mat4x3", "mat4x4", "dmat2", "dmat3", "dmat4", "dmat2x2", "dmat2x3", "dmat2x4", "dmat3x2", "dmat3x3", "dmat3x4", "dmat4x2", "dmat4x3", "dmat4x4", "lowp", "mediump", "highp", "precision", "sampler1D", "sampler1DShadow", "sampler1DArray", "sampler1DArrayShadow", "isampler1D", "isampler1DArray", "usampler1D", "usampler1DArray", "sampler2D", "sampler2DShadow", "sampler2DArray", "sampler2DArrayShadow", "isampler2D", "isampler2DArray", "usampler2D", "usampler2DArray", "sampler2DRect", "sampler2DRectShadow", "isampler2DRect", "usampler2DRect", "sampler2DMS", "isampler2DMS", "usampler2DMS", "sampler2DMSArray", "isampler2DMSArray", "usampler2DMSArray", "sampler3D", "isampler3D", "usampler3D", "samplerCube", "samplerCubeShadow", "isamplerCube", "usamplerCube", "samplerCubeArray", "samplerCubeArrayShadow", "isamplerCubeArray", "usamplerCubeArray", "samplerBuffer", "isamplerBuffer", "usamplerBuffer", "image1D", "iimage1D", "uimage1D", "image1DArray", "iimage1DArray", "uimage1DArray", "image2D", "iimage2D", "uimage2D", "image2DArray", "iimage2DArray", "uimage2DArray", "image2DRect", "iimage2DRect", "uimage2DRect", "image2DMS", "iimage2DMS", "uimage2DMS", "image2DMSArray", "iimage2DMSArray", "uimage2DMSArray", "image3D", "iimage3D", "uimage3D", "imageCube", "iimageCube", "uimageCube", "imageCubeArray", "iimageCubeArray", "uimageCubeArray", "imageBuffer", "iimageBuffer", "uimageBuffer", "struct", // Vulkan keywords "texture1D", "texture1DArray", "itexture1D", "itexture1DArray", "utexture1D", "utexture1DArray", "texture2D", "texture2DArray", "itexture2D", "itexture2DArray", "utexture2D", "utexture2DArray", "texture2DRect", "itexture2DRect", "utexture2DRect", "texture2DMS", "itexture2DMS", "utexture2DMS", "texture2DMSArray", "itexture2DMSArray", "utexture2DMSArray", "texture3D", "itexture3D", "utexture3D", "textureCube", "itextureCube", "utextureCube", "textureCubeArray", "itextureCubeArray", "utextureCubeArray", "textureBuffer", "itextureBuffer", "utextureBuffer", "sampler", "samplerShadow", "subpassInput", "isubpassInput", "usubpassInput", "subpassInputMS", "isubpassInputMS", "usubpassInputMS", // Reserved keywords "common", "partition", "active", "asm", "class", "union", "enum", "typedef", "template", "this", "resource", "goto", "inline", "noinline", "public", "static", "extern", "external", "interface", "long", "short", "half", "fixed", "unsigned", "superp", "input", "output", "hvec2", "hvec3", "hvec4", "fvec2", "fvec3", "fvec4", "filter", "sizeof", "cast", "namespace", "using", "sampler3DRect", // Reserved keywords that were unreserved in GLSL 4.2 "image1DArrayShadow", "image1DShadow", "image2DArrayShadow", "image2DShadow", // Reserved keywords that were unreserved in GLSL 4.4 "packed", "row_major", // // GLSL 4.6 Built-In Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L13314 // // Angle and Trigonometry Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L13469-L13561C5 "radians", "degrees", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "asinh", "acosh", "atanh", // Exponential Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L13569-L13620 "pow", "exp", "log", "exp2", "log2", "sqrt", "inversesqrt", // Common Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L13628-L13908 "abs", "sign", "floor", "trunc", "round", "roundEven", "ceil", "fract", "mod", "modf", "min", "max", "clamp", "mix", "step", "smoothstep", "isnan", "isinf", "floatBitsToInt", "floatBitsToUint", "intBitsToFloat", "uintBitsToFloat", "fma", "frexp", "ldexp", // Floating-Point Pack and Unpack Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L13916-L14007 "packUnorm2x16", "packSnorm2x16", "packUnorm4x8", "packSnorm4x8", "unpackUnorm2x16", "unpackSnorm2x16", "unpackUnorm4x8", "unpackSnorm4x8", "packHalf2x16", "unpackHalf2x16", "packDouble2x32", "unpackDouble2x32", // Geometric Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L14014-L14121 "length", "distance", "dot", "cross", "normalize", "ftransform", "faceforward", "reflect", "refract", // Matrix Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L14151-L14215 "matrixCompMult", "outerProduct", "transpose", "determinant", "inverse", // Vector Relational Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L14259-L14322 "lessThan", "lessThanEqual", "greaterThan", "greaterThanEqual", "equal", "notEqual", "any", "all", "not", // Integer Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L14335-L14432 "uaddCarry", "usubBorrow", "umulExtended", "imulExtended", "bitfieldExtract", "bitfieldInsert", "bitfieldReverse", "bitCount", "findLSB", "findMSB", // Texture Query Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L14645-L14732 "textureSize", "textureQueryLod", "textureQueryLevels", "textureSamples", // Texel Lookup Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L14736-L14997 "texture", "textureProj", "textureLod", "textureOffset", "texelFetch", "texelFetchOffset", "textureProjOffset", "textureLodOffset", "textureProjLod", "textureProjLodOffset", "textureGrad", "textureGradOffset", "textureProjGrad", "textureProjGradOffset", // Texture Gather Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L15077-L15154 "textureGather", "textureGatherOffset", "textureGatherOffsets", // Compatibility Profile Texture Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L15161-L15220 "texture1D", "texture1DProj", "texture1DLod", "texture1DProjLod", "texture2D", "texture2DProj", "texture2DLod", "texture2DProjLod", "texture3D", "texture3DProj", "texture3DLod", "texture3DProjLod", "textureCube", "textureCubeLod", "shadow1D", "shadow2D", "shadow1DProj", "shadow2DProj", "shadow1DLod", "shadow2DLod", "shadow1DProjLod", "shadow2DProjLod", // Atomic Counter Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L15241-L15531 "atomicCounterIncrement", "atomicCounterDecrement", "atomicCounter", "atomicCounterAdd", "atomicCounterSubtract", "atomicCounterMin", "atomicCounterMax", "atomicCounterAnd", "atomicCounterOr", "atomicCounterXor", "atomicCounterExchange", "atomicCounterCompSwap", // Atomic Memory Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L15563-L15624 "atomicAdd", "atomicMin", "atomicMax", "atomicAnd", "atomicOr", "atomicXor", "atomicExchange", "atomicCompSwap", // Image Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L15763-L15878 "imageSize", "imageSamples", "imageLoad", "imageStore", "imageAtomicAdd", "imageAtomicMin", "imageAtomicMax", "imageAtomicAnd", "imageAtomicOr", "imageAtomicXor", "imageAtomicExchange", "imageAtomicCompSwap", // Geometry Shader Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L15886-L15932 "EmitStreamVertex", "EndStreamPrimitive", "EmitVertex", "EndPrimitive", // Fragment Processing Functions, Derivative Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16041-L16114 "dFdx", "dFdy", "dFdxFine", "dFdyFine", "dFdxCoarse", "dFdyCoarse", "fwidth", "fwidthFine", "fwidthCoarse", // Fragment Processing Functions, Interpolation Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16150-L16198 "interpolateAtCentroid", "interpolateAtSample", "interpolateAtOffset", // Noise Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16214-L16243 "noise1", "noise2", "noise3", "noise4", // Shader Invocation Control Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16255-L16276 "barrier", // Shader Memory Control Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16336-L16382 "memoryBarrier", "memoryBarrierAtomicCounter", "memoryBarrierBuffer", "memoryBarrierShared", "memoryBarrierImage", "groupMemoryBarrier", // Subpass-Input Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16451-L16470 "subpassLoad", // Shader Invocation Group Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16483-L16511 "anyInvocation", "allInvocations", "allInvocationsEqual", // // entry point name (should not be shadowed) // "main", // Naga utilities: super::MODF_FUNCTION, super::FREXP_FUNCTION, super::FIRST_INSTANCE_BINDING, ]; /// The above set of reserved keywords, turned into a cached HashSet. This saves /// significant time during [`Namer::reset`](crate::proc::Namer::reset). /// /// See for benchmarks. pub static RESERVED_KEYWORD_SET: RacyLock = RacyLock::new(|| KeywordSet::from_iter(RESERVED_KEYWORDS)); ================================================ FILE: naga/src/back/glsl/mod.rs ================================================ /*! Backend for [GLSL][glsl] (OpenGL Shading Language). The main structure is [`Writer`], it maintains internal state that is used to output a [`Module`](crate::Module) into glsl # Supported versions ### Core - 330 - 400 - 410 - 420 - 430 - 450 ### ES - 300 - 310 [glsl]: https://www.khronos.org/registry/OpenGL/index_gl.php */ // GLSL is mostly a superset of C but it also removes some parts of it this is a list of relevant // aspects for this backend. // // The most notable change is the introduction of the version preprocessor directive that must // always be the first line of a glsl file and is written as // `#version number profile` // `number` is the version itself (i.e. 300) and `profile` is the // shader profile we only support "core" and "es", the former is used in desktop applications and // the later is used in embedded contexts, mobile devices and browsers. Each one as it's own // versions (at the time of writing this the latest version for "core" is 460 and for "es" is 320) // // Other important preprocessor addition is the extension directive which is written as // `#extension name: behaviour` // Extensions provide increased features in a plugin fashion but they aren't required to be // supported hence why they are called extensions, that's why `behaviour` is used it specifies // whether the extension is strictly required or if it should only be enabled if needed. In our case // when we use extensions we set behaviour to `require` always. // // The only thing that glsl removes that makes a difference are pointers. // // Additions that are relevant for the backend are the discard keyword, the introduction of // vector, matrices, samplers, image types and functions that provide common shader operations pub use features::Features; pub use writer::Writer; use alloc::{ borrow::ToOwned, format, string::{String, ToString}, vec, vec::Vec, }; use core::{ cmp::Ordering, fmt::{self, Error as FmtError, Write}, mem, }; use hashbrown::hash_map; use thiserror::Error; use crate::{ back::{self, Baked}, common, proc::{self, NameKey}, valid, Handle, ShaderStage, TypeInner, }; use conv::*; use features::FeaturesManager; /// Contains simple 1:1 conversion functions. mod conv; /// Contains the features related code and the features querying method mod features; /// Contains a constant with a slice of all the reserved keywords RESERVED_KEYWORDS mod keywords; /// Contains the [`Writer`] type. mod writer; /// List of supported `core` GLSL versions. pub const SUPPORTED_CORE_VERSIONS: &[u16] = &[140, 150, 330, 400, 410, 420, 430, 440, 450, 460]; /// List of supported `es` GLSL versions. pub const SUPPORTED_ES_VERSIONS: &[u16] = &[300, 310, 320]; /// The suffix of the variable that will hold the calculated clamped level /// of detail for bounds checking in `ImageLoad` const CLAMPED_LOD_SUFFIX: &str = "_clamped_lod"; pub(crate) const MODF_FUNCTION: &str = "naga_modf"; pub(crate) const FREXP_FUNCTION: &str = "naga_frexp"; // Must match code in glsl_built_in pub const FIRST_INSTANCE_BINDING: &str = "naga_vs_first_instance"; #[cfg(feature = "deserialize")] #[derive(serde::Deserialize)] struct BindingMapSerialization { resource_binding: crate::ResourceBinding, bind_target: u8, } #[cfg(feature = "deserialize")] fn deserialize_binding_map<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, { use serde::Deserialize; let vec = Vec::::deserialize(deserializer)?; let mut map = BindingMap::default(); for item in vec { map.insert(item.resource_binding, item.bind_target); } Ok(map) } /// Mapping between resources and bindings. pub type BindingMap = alloc::collections::BTreeMap; impl crate::AtomicFunction { const fn to_glsl(self) -> &'static str { match self { Self::Add | Self::Subtract => "Add", Self::And => "And", Self::InclusiveOr => "Or", Self::ExclusiveOr => "Xor", Self::Min => "Min", Self::Max => "Max", Self::Exchange { compare: None } => "Exchange", Self::Exchange { compare: Some(_) } => "", //TODO } } } impl crate::AddressSpace { /// Whether a variable with this address space can be initialized const fn initializable(&self) -> bool { match *self { crate::AddressSpace::Function | crate::AddressSpace::Private => true, crate::AddressSpace::WorkGroup | crate::AddressSpace::Uniform | crate::AddressSpace::Storage { .. } | crate::AddressSpace::Handle | crate::AddressSpace::Immediate | crate::AddressSpace::TaskPayload => false, crate::AddressSpace::RayPayload | crate::AddressSpace::IncomingRayPayload => { unreachable!() } } } } /// A GLSL version. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub enum Version { /// `core` GLSL. Desktop(u16), /// `es` GLSL. Embedded { version: u16, is_webgl: bool }, } impl Version { /// Create a new gles version pub const fn new_gles(version: u16) -> Self { Self::Embedded { version, is_webgl: false, } } /// Returns true if self is `Version::Embedded` (i.e. is a es version) const fn is_es(&self) -> bool { match *self { Version::Desktop(_) => false, Version::Embedded { .. } => true, } } /// Returns true if targeting WebGL const fn is_webgl(&self) -> bool { match *self { Version::Desktop(_) => false, Version::Embedded { is_webgl, .. } => is_webgl, } } /// Checks the list of currently supported versions and returns true if it contains the /// specified version /// /// # Notes /// As an invalid version number will never be added to the supported version list /// so this also checks for version validity fn is_supported(&self) -> bool { match *self { Version::Desktop(v) => SUPPORTED_CORE_VERSIONS.contains(&v), Version::Embedded { version: v, .. } => SUPPORTED_ES_VERSIONS.contains(&v), } } fn supports_io_locations(&self) -> bool { *self >= Version::Desktop(330) || *self >= Version::new_gles(300) } /// Checks if the version supports all of the explicit layouts: /// - `location=` qualifiers for bindings /// - `binding=` qualifiers for resources /// /// Note: `location=` for vertex inputs and fragment outputs is supported /// unconditionally for GLES 300. fn supports_explicit_locations(&self) -> bool { *self >= Version::Desktop(420) || *self >= Version::new_gles(310) } fn supports_early_depth_test(&self) -> bool { *self >= Version::Desktop(130) || *self >= Version::new_gles(310) } fn supports_std140_layout(&self) -> bool { *self >= Version::Desktop(140) || *self >= Version::new_gles(300) } fn supports_std430_layout(&self) -> bool { // std430 is available from 400 via GL_ARB_shader_storage_buffer_object. *self >= Version::Desktop(400) || *self >= Version::new_gles(310) } fn supports_fma_function(&self) -> bool { *self >= Version::Desktop(400) || *self >= Version::new_gles(320) } fn supports_integer_functions(&self) -> bool { *self >= Version::Desktop(400) || *self >= Version::new_gles(310) } fn supports_frexp_function(&self) -> bool { *self >= Version::Desktop(400) || *self >= Version::new_gles(310) } fn supports_derivative_control(&self) -> bool { *self >= Version::Desktop(450) } // For supports_pack_unpack_4x8, supports_pack_unpack_snorm_2x16, supports_pack_unpack_unorm_2x16 // see: // https://registry.khronos.org/OpenGL-Refpages/gl4/html/unpackUnorm.xhtml // https://registry.khronos.org/OpenGL-Refpages/es3/html/unpackUnorm.xhtml // https://registry.khronos.org/OpenGL-Refpages/gl4/html/packUnorm.xhtml // https://registry.khronos.org/OpenGL-Refpages/es3/html/packUnorm.xhtml fn supports_pack_unpack_4x8(&self) -> bool { *self >= Version::Desktop(400) || *self >= Version::new_gles(310) } fn supports_pack_unpack_snorm_2x16(&self) -> bool { *self >= Version::Desktop(420) || *self >= Version::new_gles(300) } fn supports_pack_unpack_unorm_2x16(&self) -> bool { *self >= Version::Desktop(400) || *self >= Version::new_gles(300) } // https://registry.khronos.org/OpenGL-Refpages/gl4/html/unpackHalf2x16.xhtml // https://registry.khronos.org/OpenGL-Refpages/gl4/html/packHalf2x16.xhtml // https://registry.khronos.org/OpenGL-Refpages/es3/html/unpackHalf2x16.xhtml // https://registry.khronos.org/OpenGL-Refpages/es3/html/packHalf2x16.xhtml fn supports_pack_unpack_half_2x16(&self) -> bool { *self >= Version::Desktop(420) || *self >= Version::new_gles(300) } } impl PartialOrd for Version { fn partial_cmp(&self, other: &Self) -> Option { match (*self, *other) { (Version::Desktop(x), Version::Desktop(y)) => Some(x.cmp(&y)), (Version::Embedded { version: x, .. }, Version::Embedded { version: y, .. }) => { Some(x.cmp(&y)) } _ => None, } } } impl fmt::Display for Version { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Version::Desktop(v) => write!(f, "{v} core"), Version::Embedded { version: v, .. } => write!(f, "{v} es"), } } } bitflags::bitflags! { /// Configuration flags for the [`Writer`]. #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct WriterFlags: u32 { /// Flip output Y and extend Z from (0, 1) to (-1, 1). const ADJUST_COORDINATE_SPACE = 0x1; /// Supports GL_EXT_texture_shadow_lod on the host, which provides /// additional functions on shadows and arrays of shadows. const TEXTURE_SHADOW_LOD = 0x2; /// Supports ARB_shader_draw_parameters on the host, which provides /// support for `gl_BaseInstanceARB`, `gl_BaseVertexARB`, `gl_DrawIDARB`, and `gl_DrawID`. const DRAW_PARAMETERS = 0x4; /// Include unused global variables, constants and functions. By default the output will exclude /// global variables that are not used in the specified entrypoint (including indirect use), /// all constant declarations, and functions that use excluded global variables. const INCLUDE_UNUSED_ITEMS = 0x10; /// Emit `PointSize` output builtin to vertex shaders, which is /// required for drawing with `PointList` topology. /// /// https://registry.khronos.org/OpenGL/specs/es/3.2/GLSL_ES_Specification_3.20.html#built-in-language-variables /// The variable gl_PointSize is intended for a shader to write the size of the point to be rasterized. It is measured in pixels. /// If gl_PointSize is not written to, its value is undefined in subsequent pipe stages. const FORCE_POINT_SIZE = 0x20; } } /// Configuration used in the [`Writer`]. #[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[cfg_attr(feature = "deserialize", serde(default))] pub struct Options { /// The GLSL version to be used. pub version: Version, /// Configuration flags for the [`Writer`]. pub writer_flags: WriterFlags, /// Map of resources association to binding locations. #[cfg_attr( feature = "deserialize", serde(deserialize_with = "deserialize_binding_map") )] pub binding_map: BindingMap, /// Should workgroup variables be zero initialized (by polyfilling)? pub zero_initialize_workgroup_memory: bool, } impl Default for Options { fn default() -> Self { Options { version: Version::new_gles(310), writer_flags: WriterFlags::ADJUST_COORDINATE_SPACE, binding_map: BindingMap::default(), zero_initialize_workgroup_memory: true, } } } /// A subset of options meant to be changed per pipeline. #[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct PipelineOptions { /// The stage of the entry point. pub shader_stage: ShaderStage, /// The name of the entry point. /// /// If no entry point that matches is found while creating a [`Writer`], an /// error will be thrown. pub entry_point: String, /// How many views to render to, if doing multiview rendering. pub multiview: Option, } #[derive(Debug)] pub struct VaryingLocation { /// The location of the global. /// This corresponds to `layout(location = ..)` in GLSL. pub location: u32, /// The index which can be used for dual source blending. /// This corresponds to `layout(index = ..)` in GLSL. pub index: u32, } /// Reflection info for texture mappings and uniforms. #[derive(Debug)] pub struct ReflectionInfo { /// Mapping between texture names and variables/samplers. pub texture_mapping: crate::FastHashMap, /// Mapping between uniform variables and names. pub uniforms: crate::FastHashMap, String>, /// Mapping between names and attribute locations. pub varying: crate::FastHashMap, /// List of immediate data items in the shader. pub immediates_items: Vec, /// Number of user-defined clip planes. Only applicable to vertex shaders. pub clip_distance_count: u32, } /// Mapping between a texture and its sampler, if it exists. /// /// GLSL pre-Vulkan has no concept of separate textures and samplers. Instead, everything is a /// `gsamplerN` where `g` is the scalar type and `N` is the dimension. But naga uses separate textures /// and samplers in the IR, so the backend produces a [`FastHashMap`](crate::FastHashMap) with the texture name /// as a key and a [`TextureMapping`] as a value. This way, the user knows where to bind. /// /// [`Storage`](crate::ImageClass::Storage) images produce `gimageN` and don't have an associated sampler, /// so the [`sampler`](Self::sampler) field will be [`None`]. #[derive(Debug, Clone)] pub struct TextureMapping { /// Handle to the image global variable. pub texture: Handle, /// Handle to the associated sampler global variable, if it exists. pub sampler: Option>, } /// All information to bind a single uniform value to the shader. /// /// Immediates are emulated using traditional uniforms in OpenGL. /// /// These are composed of a set of primitives (scalar, vector, matrix) that /// are given names. Because they are not backed by the concept of a buffer, /// we must do the work of calculating the offset of each primitive in the /// immediate data block. #[derive(Debug, Clone)] pub struct ImmediateItem { /// GL uniform name for the item. This name is the same as if you were /// to access it directly from a GLSL shader. /// /// The with the following example, the following names will be generated, /// one name per GLSL uniform. /// /// ```glsl /// struct InnerStruct { /// value: f32, /// } /// /// struct ImmediateData { /// InnerStruct inner; /// vec4 array[2]; /// } /// /// uniform ImmediateData _immediates_binding_cs; /// ``` /// /// ```text /// - _immediates_binding_cs.inner.value /// - _immediates_binding_cs.array[0] /// - _immediates_binding_cs.array[1] /// ``` /// pub access_path: String, /// Type of the uniform. This will only ever be a scalar, vector, or matrix. pub ty: Handle, /// The offset in the immediate data memory block this uniform maps to. /// /// The size of the uniform can be derived from the type. pub offset: u32, } /// Helper structure that generates a number #[derive(Default)] struct IdGenerator(u32); impl IdGenerator { /// Generates a number that's guaranteed to be unique for this `IdGenerator` const fn generate(&mut self) -> u32 { // It's just an increasing number but it does the job let ret = self.0; self.0 += 1; ret } } /// Assorted options needed for generating varyings. #[derive(Clone, Copy)] struct VaryingOptions { output: bool, targeting_webgl: bool, draw_parameters: bool, } impl VaryingOptions { const fn from_writer_options(options: &Options, output: bool) -> Self { Self { output, targeting_webgl: options.version.is_webgl(), draw_parameters: options.writer_flags.contains(WriterFlags::DRAW_PARAMETERS), } } } /// Helper wrapper used to get a name for a varying /// /// Varying have different naming schemes depending on their binding: /// - Varyings with builtin bindings get their name from [`glsl_built_in`]. /// - Varyings with location bindings are named `_S_location_X` where `S` is a /// prefix identifying which pipeline stage the varying connects, and `X` is /// the location. struct VaryingName<'a> { binding: &'a crate::Binding, stage: ShaderStage, options: VaryingOptions, } impl fmt::Display for VaryingName<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self.binding { crate::Binding::Location { blend_src: Some(1), .. } => { write!(f, "_fs2p_location1",) } crate::Binding::Location { location, .. } => { let prefix = match (self.stage, self.options.output) { (ShaderStage::Compute, _) => unreachable!(), // pipeline to vertex (ShaderStage::Vertex, false) => "p2vs", // vertex to fragment (ShaderStage::Vertex, true) | (ShaderStage::Fragment, false) => "vs2fs", // fragment to pipeline (ShaderStage::Fragment, true) => "fs2p", ( ShaderStage::Task | ShaderStage::Mesh | ShaderStage::RayGeneration | ShaderStage::AnyHit | ShaderStage::ClosestHit | ShaderStage::Miss, _, ) => unreachable!(), }; write!(f, "_{prefix}_location{location}",) } crate::Binding::BuiltIn(built_in) => { write!(f, "{}", glsl_built_in(built_in, self.options)) } } } } impl ShaderStage { const fn to_str(self) -> &'static str { match self { ShaderStage::Compute => "cs", ShaderStage::Fragment => "fs", ShaderStage::Vertex => "vs", ShaderStage::Task | ShaderStage::Mesh | ShaderStage::RayGeneration | ShaderStage::AnyHit | ShaderStage::ClosestHit | ShaderStage::Miss => unreachable!(), } } } /// Shorthand result used internally by the backend type BackendResult = Result; /// A GLSL compilation error. #[derive(Debug, Error)] pub enum Error { /// A error occurred while writing to the output. #[error("Format error")] FmtError(#[from] FmtError), /// The specified [`Version`] doesn't have all required [`Features`]. /// /// Contains the missing [`Features`]. #[error("The selected version doesn't support {0:?}")] MissingFeatures(Features), /// [`AddressSpace::Immediate`](crate::AddressSpace::Immediate) was used more than /// once in the entry point, which isn't supported. #[error("Multiple immediates aren't supported")] MultipleImmediateData, /// The specified [`Version`] isn't supported. #[error("The specified version isn't supported")] VersionNotSupported, /// The entry point couldn't be found. #[error("The requested entry point couldn't be found")] EntryPointNotFound, /// A call was made to an unsupported external. #[error("A call was made to an unsupported external: {0}")] UnsupportedExternal(String), /// A scalar with an unsupported width was requested. #[error("A scalar with an unsupported width was requested: {0:?}")] UnsupportedScalar(crate::Scalar), /// A image was used with multiple samplers, which isn't supported. #[error("A image was used with multiple samplers")] ImageMultipleSamplers, #[error("{0}")] Custom(String), #[error("overrides should not be present at this stage")] Override, /// [`crate::Sampling::First`] is unsupported. #[error("`{:?}` sampling is unsupported", crate::Sampling::First)] FirstSamplingNotSupported, #[error(transparent)] ResolveArraySizeError(#[from] proc::ResolveArraySizeError), } /// Binary operation with a different logic on the GLSL side. enum BinaryOperation { /// Vector comparison should use the function like `greaterThan()`, etc. VectorCompare, /// Vector component wise operation; used to polyfill unsupported ops like `|` and `&` for `bvecN`'s VectorComponentWise, /// GLSL `%` is SPIR-V `OpUMod/OpSMod` and `mod()` is `OpFMod`, but [`BinaryOperator::Modulo`](crate::BinaryOperator::Modulo) is `OpFRem`. Modulo, /// Any plain operation. No additional logic required. Other, } fn is_value_init_supported(module: &crate::Module, ty: Handle) -> bool { match module.types[ty].inner { TypeInner::Scalar { .. } | TypeInner::Vector { .. } | TypeInner::Matrix { .. } => true, TypeInner::Array { base, size, .. } => { size != crate::ArraySize::Dynamic && is_value_init_supported(module, base) } TypeInner::Struct { ref members, .. } => members .iter() .all(|member| is_value_init_supported(module, member.ty)), _ => false, } } pub fn supported_capabilities() -> valid::Capabilities { use valid::Capabilities as Caps; // Lots of these aren't supported on GLES in general, but naga is able to write them without panicking. Caps::IMMEDIATES | Caps::FLOAT64 | Caps::PRIMITIVE_INDEX | Caps::CLIP_DISTANCES | Caps::MULTIVIEW | Caps::EARLY_DEPTH_TEST | Caps::MULTISAMPLED_SHADING | Caps::DUAL_SOURCE_BLENDING | Caps::CUBE_ARRAY_TEXTURES | Caps::SHADER_INT64 | Caps::SHADER_INT64_ATOMIC_ALL_OPS | Caps::TEXTURE_ATOMIC | Caps::TEXTURE_INT64_ATOMIC | Caps::SUBGROUP | Caps::SUBGROUP_BARRIER | Caps::SHADER_FLOAT16 | Caps::SHADER_FLOAT16_IN_FLOAT32 | Caps::SHADER_BARYCENTRICS | Caps::DRAW_INDEX | Caps::MEMORY_DECORATION_COHERENT | Caps::MEMORY_DECORATION_VOLATILE } ================================================ FILE: naga/src/back/glsl/writer.rs ================================================ use super::*; /// Writer responsible for all code generation. pub struct Writer<'a, W> { // Inputs /// The module being written. pub(in crate::back::glsl) module: &'a crate::Module, /// The module analysis. pub(in crate::back::glsl) info: &'a valid::ModuleInfo, /// The output writer. out: W, /// User defined configuration to be used. pub(in crate::back::glsl) options: &'a Options, /// The bound checking policies to be used pub(in crate::back::glsl) policies: proc::BoundsCheckPolicies, // Internal State /// Features manager used to store all the needed features and write them. pub(in crate::back::glsl) features: FeaturesManager, namer: proc::Namer, /// A map with all the names needed for writing the module /// (generated by a [`Namer`](crate::proc::Namer)). names: crate::FastHashMap, /// A map with the names of global variables needed for reflections. reflection_names_globals: crate::FastHashMap, String>, /// The selected entry point. pub(in crate::back::glsl) entry_point: &'a crate::EntryPoint, /// The index of the selected entry point. pub(in crate::back::glsl) entry_point_idx: proc::EntryPointIndex, /// A generator for unique block numbers. block_id: IdGenerator, /// Set of expressions that have associated temporary variables. named_expressions: crate::NamedExpressions, /// Set of expressions that need to be baked to avoid unnecessary repetition in output need_bake_expressions: back::NeedBakeExpressions, /// Information about nesting of loops and switches. /// /// Used for forwarding continue statements in switches that have been /// transformed to `do {} while(false);` loops. continue_ctx: back::continue_forward::ContinueCtx, /// How many views to render to, if doing multiview rendering. pub(in crate::back::glsl) multiview: Option, /// Mapping of varying variables to their location. Needed for reflections. varying: crate::FastHashMap, /// Number of user-defined clip planes. Only non-zero for vertex shaders. clip_distance_count: u32, } impl<'a, W: Write> Writer<'a, W> { /// Creates a new [`Writer`] instance. /// /// # Errors /// - If the version specified is invalid or supported. /// - If the entry point couldn't be found in the module. /// - If the version specified doesn't support some used features. pub fn new( out: W, module: &'a crate::Module, info: &'a valid::ModuleInfo, options: &'a Options, pipeline_options: &'a PipelineOptions, policies: proc::BoundsCheckPolicies, ) -> Result { // Check if the requested version is supported if !options.version.is_supported() { log::error!("Version {}", options.version); return Err(Error::VersionNotSupported); } // Try to find the entry point and corresponding index let ep_idx = module .entry_points .iter() .position(|ep| { pipeline_options.shader_stage == ep.stage && pipeline_options.entry_point == ep.name }) .ok_or(Error::EntryPointNotFound)?; // Generate a map with names required to write the module let mut names = crate::FastHashMap::default(); let mut namer = proc::Namer::default(); namer.reset( module, &keywords::RESERVED_KEYWORD_SET, proc::KeywordSet::empty(), proc::CaseInsensitiveKeywordSet::empty(), &[ "gl_", // all GL built-in variables "_group", // all normal bindings "_immediates_binding_", // all immediate data bindings ], &mut names, ); // Build the instance let mut this = Self { module, info, out, options, policies, namer, features: FeaturesManager::new(), names, reflection_names_globals: crate::FastHashMap::default(), entry_point: &module.entry_points[ep_idx], entry_point_idx: ep_idx as u16, multiview: pipeline_options.multiview, block_id: IdGenerator::default(), named_expressions: Default::default(), need_bake_expressions: Default::default(), continue_ctx: back::continue_forward::ContinueCtx::default(), varying: Default::default(), clip_distance_count: 0, }; // Find all features required to print this module this.collect_required_features()?; Ok(this) } /// Writes the [`Module`](crate::Module) as glsl to the output /// /// # Notes /// If an error occurs while writing, the output might have been written partially /// /// # Panics /// Might panic if the module is invalid pub fn write(&mut self) -> Result { // We use `writeln!(self.out)` throughout the write to add newlines // to make the output more readable let es = self.options.version.is_es(); // Write the version (It must be the first thing or it isn't a valid glsl output) writeln!(self.out, "#version {}", self.options.version)?; // Write all the needed extensions // // This used to be the last thing being written as it allowed to search for features while // writing the module saving some loops but some older versions (420 or less) required the // extensions to appear before being used, even though extensions are part of the // preprocessor not the processor ¯\_(ツ)_/¯ self.features.write(self.options, &mut self.out)?; // glsl es requires a precision to be specified for floats and ints // TODO: Should this be user configurable? if es { writeln!(self.out)?; writeln!(self.out, "precision highp float;")?; writeln!(self.out, "precision highp int;")?; writeln!(self.out)?; } if self.entry_point.stage == ShaderStage::Compute { let workgroup_size = self.entry_point.workgroup_size; writeln!( self.out, "layout(local_size_x = {}, local_size_y = {}, local_size_z = {}) in;", workgroup_size[0], workgroup_size[1], workgroup_size[2] )?; writeln!(self.out)?; } if self.entry_point.stage == ShaderStage::Vertex && !self .options .writer_flags .contains(WriterFlags::DRAW_PARAMETERS) && self.features.contains(Features::INSTANCE_INDEX) { writeln!(self.out, "uniform uint {FIRST_INSTANCE_BINDING};")?; writeln!(self.out)?; } // Enable early depth tests if needed if let Some(early_depth_test) = self.entry_point.early_depth_test { // If early depth test is supported for this version of GLSL if self.options.version.supports_early_depth_test() { match early_depth_test { crate::EarlyDepthTest::Force => { writeln!(self.out, "layout(early_fragment_tests) in;")?; } crate::EarlyDepthTest::Allow { conservative, .. } => { use crate::ConservativeDepth as Cd; let depth = match conservative { Cd::GreaterEqual => "greater", Cd::LessEqual => "less", Cd::Unchanged => "unchanged", }; writeln!(self.out, "layout (depth_{depth}) out float gl_FragDepth;")?; } } } else { log::warn!( "Early depth testing is not supported for this version of GLSL: {}", self.options.version ); } } if self.entry_point.stage == ShaderStage::Vertex && self.options.version.is_webgl() { if let Some(multiview) = self.multiview.as_ref() { writeln!(self.out, "layout(num_views = {multiview}) in;")?; writeln!(self.out)?; } } // Write struct types. // // This are always ordered because the IR is structured in a way that // you can't make a struct without adding all of its members first. for (handle, ty) in self.module.types.iter() { if let TypeInner::Struct { ref members, .. } = ty.inner { let struct_name = &self.names[&NameKey::Type(handle)]; // Structures ending with runtime-sized arrays can only be // rendered as shader storage blocks in GLSL, not stand-alone // struct types. if !self.module.types[members.last().unwrap().ty] .inner .is_dynamically_sized(&self.module.types) { write!(self.out, "struct {struct_name} ")?; self.write_struct_body(handle, members)?; writeln!(self.out, ";")?; } } } // Write functions for special types. for (type_key, struct_ty) in self.module.special_types.predeclared_types.iter() { match type_key { &crate::PredeclaredType::ModfResult { size, scalar } | &crate::PredeclaredType::FrexpResult { size, scalar } => { let struct_name = &self.names[&NameKey::Type(*struct_ty)]; let arg_type_name_owner; let arg_type_name = if let Some(size) = size { arg_type_name_owner = format!( "{}vec{}", if scalar.width == 8 { "d" } else { "" }, size as u8 ); &arg_type_name_owner } else if scalar.width == 8 { "double" } else { "float" }; let other_type_name_owner; let (defined_func_name, called_func_name, other_type_name) = if matches!(type_key, &crate::PredeclaredType::ModfResult { .. }) { (MODF_FUNCTION, "modf", arg_type_name) } else { let other_type_name = if let Some(size) = size { other_type_name_owner = format!("ivec{}", size as u8); &other_type_name_owner } else { "int" }; (FREXP_FUNCTION, "frexp", other_type_name) }; writeln!(self.out)?; if !self.options.version.supports_frexp_function() && matches!(type_key, &crate::PredeclaredType::FrexpResult { .. }) { writeln!( self.out, "{struct_name} {defined_func_name}({arg_type_name} arg) {{ {other_type_name} other = arg == {arg_type_name}(0) ? {other_type_name}(0) : {other_type_name}({arg_type_name}(1) + log2(arg)); {arg_type_name} fract = arg * exp2({arg_type_name}(-other)); return {struct_name}(fract, other); }}", )?; } else { writeln!( self.out, "{struct_name} {defined_func_name}({arg_type_name} arg) {{ {other_type_name} other; {arg_type_name} fract = {called_func_name}(arg, other); return {struct_name}(fract, other); }}", )?; } } &crate::PredeclaredType::AtomicCompareExchangeWeakResult(_) => { // Handled by the general struct writing loop earlier. } } } // Write all named constants let mut constants = self .module .constants .iter() .filter(|&(_, c)| c.name.is_some()) .peekable(); while let Some((handle, _)) = constants.next() { self.write_global_constant(handle)?; // Add extra newline for readability on last iteration if constants.peek().is_none() { writeln!(self.out)?; } } let ep_info = self.info.get_entry_point(self.entry_point_idx as usize); // Write the globals // // Unless explicitly disabled with WriterFlags::INCLUDE_UNUSED_ITEMS, // we filter all globals that aren't used by the selected entry point as they might be // interfere with each other (i.e. two globals with the same location but different with // different classes) let include_unused = self .options .writer_flags .contains(WriterFlags::INCLUDE_UNUSED_ITEMS); for (handle, global) in self.module.global_variables.iter() { let is_unused = ep_info[handle].is_empty(); if !include_unused && is_unused { continue; } match self.module.types[global.ty].inner { // We treat images separately because they might require // writing the storage format TypeInner::Image { mut dim, arrayed, class, } => { // Gather the storage format if needed let storage_format_access = match self.module.types[global.ty].inner { TypeInner::Image { class: crate::ImageClass::Storage { format, access }, .. } => Some((format, access)), _ => None, }; if dim == crate::ImageDimension::D1 && es { dim = crate::ImageDimension::D2 } // Gether the location if needed let layout_binding = if self.options.version.supports_explicit_locations() { let br = global.binding.as_ref().unwrap(); self.options.binding_map.get(br).cloned() } else { None }; // Write all the layout qualifiers if layout_binding.is_some() || storage_format_access.is_some() { write!(self.out, "layout(")?; if let Some(binding) = layout_binding { write!(self.out, "binding = {binding}")?; } if let Some((format, _)) = storage_format_access { let format_str = glsl_storage_format(format)?; let separator = match layout_binding { Some(_) => ",", None => "", }; write!(self.out, "{separator}{format_str}")?; } write!(self.out, ") ")?; } if let Some((_, access)) = storage_format_access { self.write_storage_access(access)?; } // All images in glsl are `uniform` // The trailing space is important write!(self.out, "uniform ")?; // write the type // // This is way we need the leading space because `write_image_type` doesn't add // any spaces at the beginning or end self.write_image_type(dim, arrayed, class)?; // Finally write the name and end the global with a `;` // The leading space is important let global_name = self.get_global_name(handle, global); writeln!(self.out, " {global_name};")?; writeln!(self.out)?; self.reflection_names_globals.insert(handle, global_name); } // glsl has no concept of samplers so we just ignore it TypeInner::Sampler { .. } => continue, // All other globals are written by `write_global` _ => { self.write_global(handle, global)?; // Add a newline (only for readability) writeln!(self.out)?; } } } for arg in self.entry_point.function.arguments.iter() { self.write_varying(arg.binding.as_ref(), arg.ty, false)?; } if let Some(ref result) = self.entry_point.function.result { self.write_varying(result.binding.as_ref(), result.ty, true)?; } writeln!(self.out)?; // Write all regular functions for (handle, function) in self.module.functions.iter() { // Check that the function doesn't use globals that aren't supported // by the current entry point if !include_unused && !ep_info.dominates_global_use(&self.info[handle]) { continue; } let fun_info = &self.info[handle]; // Skip functions that that are not compatible with this entry point's stage. // // When validation is enabled, it rejects modules whose entry points try to call // incompatible functions, so if we got this far, then any functions incompatible // with our selected entry point must not be used. // // When validation is disabled, `fun_info.available_stages` is always just // `ShaderStages::all()`, so this will write all functions in the module, and // the downstream GLSL compiler will catch any problems. if !fun_info.available_stages.contains(ep_info.available_stages) { continue; } // Write the function self.write_function(back::FunctionType::Function(handle), function, fun_info)?; writeln!(self.out)?; } self.write_function( back::FunctionType::EntryPoint(self.entry_point_idx), &self.entry_point.function, ep_info, )?; // Add newline at the end of file writeln!(self.out)?; // Collect all reflection info and return it to the user self.collect_reflection_info() } fn write_array_size( &mut self, base: Handle, size: crate::ArraySize, ) -> BackendResult { write!(self.out, "[")?; // Write the array size // Writes nothing if `IndexableLength::Dynamic` match size.resolve(self.module.to_ctx())? { proc::IndexableLength::Known(size) => { write!(self.out, "{size}")?; } proc::IndexableLength::Dynamic => (), } write!(self.out, "]")?; if let TypeInner::Array { base: next_base, size: next_size, .. } = self.module.types[base].inner { self.write_array_size(next_base, next_size)?; } Ok(()) } /// Helper method used to write value types /// /// # Notes /// Adds no trailing or leading whitespace fn write_value_type(&mut self, inner: &TypeInner) -> BackendResult { match *inner { // Scalars are simple we just get the full name from `glsl_scalar` TypeInner::Scalar(scalar) | TypeInner::Atomic(scalar) | TypeInner::ValuePointer { size: None, scalar, space: _, } => write!(self.out, "{}", glsl_scalar(scalar)?.full)?, // Vectors are just `gvecN` where `g` is the scalar prefix and `N` is the vector size TypeInner::Vector { size, scalar } | TypeInner::ValuePointer { size: Some(size), scalar, space: _, } => write!(self.out, "{}vec{}", glsl_scalar(scalar)?.prefix, size as u8)?, // Matrices are written with `gmatMxN` where `g` is the scalar prefix (only floats and // doubles are allowed), `M` is the columns count and `N` is the rows count // // glsl supports a matrix shorthand `gmatN` where `N` = `M` but it doesn't justify the // extra branch to write matrices this way TypeInner::Matrix { columns, rows, scalar, } => write!( self.out, "{}mat{}x{}", glsl_scalar(scalar)?.prefix, columns as u8, rows as u8 )?, // GLSL arrays are written as `type name[size]` // Here we only write the size of the array i.e. `[size]` // Base `type` and `name` should be written outside TypeInner::Array { base, size, .. } => self.write_array_size(base, size)?, // Write all variants instead of `_` so that if new variants are added a // no exhaustiveness error is thrown TypeInner::Pointer { .. } | TypeInner::Struct { .. } | TypeInner::Image { .. } | TypeInner::Sampler { .. } | TypeInner::AccelerationStructure { .. } | TypeInner::RayQuery { .. } | TypeInner::BindingArray { .. } | TypeInner::CooperativeMatrix { .. } => { return Err(Error::Custom(format!("Unable to write type {inner:?}"))) } } Ok(()) } /// Helper method used to write non image/sampler types /// /// # Notes /// Adds no trailing or leading whitespace fn write_type(&mut self, ty: Handle) -> BackendResult { match self.module.types[ty].inner { // glsl has no pointer types so just write types as normal and loads are skipped TypeInner::Pointer { base, .. } => self.write_type(base), // glsl structs are written as just the struct name TypeInner::Struct { .. } => { // Get the struct name let name = &self.names[&NameKey::Type(ty)]; write!(self.out, "{name}")?; Ok(()) } // glsl array has the size separated from the base type TypeInner::Array { base, .. } => self.write_type(base), ref other => self.write_value_type(other), } } /// Helper method to write a image type /// /// # Notes /// Adds no leading or trailing whitespace fn write_image_type( &mut self, dim: crate::ImageDimension, arrayed: bool, class: crate::ImageClass, ) -> BackendResult { // glsl images consist of four parts the scalar prefix, the image "type", the dimensions // and modifiers // // There exists two image types // - sampler - for sampled images // - image - for storage images // // There are three possible modifiers that can be used together and must be written in // this order to be valid // - MS - used if it's a multisampled image // - Array - used if it's an image array // - Shadow - used if it's a depth image use crate::ImageClass as Ic; use crate::Scalar as S; let float = S { kind: crate::ScalarKind::Float, width: 4, }; let (base, scalar, ms, comparison) = match class { Ic::Sampled { kind, multi: true } => ("sampler", S { kind, width: 4 }, "MS", ""), Ic::Sampled { kind, multi: false } => ("sampler", S { kind, width: 4 }, "", ""), Ic::Depth { multi: true } => ("sampler", float, "MS", ""), Ic::Depth { multi: false } => ("sampler", float, "", "Shadow"), Ic::Storage { format, .. } => ("image", format.into(), "", ""), Ic::External => unimplemented!(), }; let precision = if self.options.version.is_es() { "highp " } else { "" }; write!( self.out, "{}{}{}{}{}{}{}", precision, glsl_scalar(scalar)?.prefix, base, glsl_dimension(dim), ms, if arrayed { "Array" } else { "" }, comparison )?; Ok(()) } /// Helper method used by [Self::write_global] to write just the layout part of /// a non image/sampler global variable, if applicable. /// /// # Notes /// /// Adds trailing whitespace if any layout qualifier is written fn write_global_layout(&mut self, global: &crate::GlobalVariable) -> BackendResult { // Determine which (if any) explicit memory layout to use, and whether we support it let layout = match global.space { crate::AddressSpace::Uniform => { if !self.options.version.supports_std140_layout() { return Err(Error::Custom( "Uniform address space requires std140 layout support".to_string(), )); } Some("std140") } crate::AddressSpace::Storage { .. } => { if !self.options.version.supports_std430_layout() { return Err(Error::Custom( "Storage address space requires std430 layout support".to_string(), )); } Some("std430") } _ => None, }; // If our version supports explicit layouts, we can also output the explicit binding // if we have it if self.options.version.supports_explicit_locations() { if let Some(ref br) = global.binding { match self.options.binding_map.get(br) { Some(binding) => { write!(self.out, "layout(")?; if let Some(layout) = layout { write!(self.out, "{layout}, ")?; } write!(self.out, "binding = {binding}) ")?; return Ok(()); } None => { log::debug!("unassigned binding for {:?}", global.name); } } } } // Either no explicit bindings are supported or we didn't have any. // Write just the memory layout. if let Some(layout) = layout { write!(self.out, "layout({layout}) ")?; } Ok(()) } /// Helper method used to write non images/sampler globals /// /// # Notes /// Adds a newline /// /// # Panics /// If the global has type sampler fn write_global( &mut self, handle: Handle, global: &crate::GlobalVariable, ) -> BackendResult { self.write_global_layout(global)?; if let crate::AddressSpace::Storage { access } = global.space { self.write_storage_access(access)?; if global .memory_decorations .contains(crate::MemoryDecorations::COHERENT) { write!(self.out, "coherent ")?; } if global .memory_decorations .contains(crate::MemoryDecorations::VOLATILE) { write!(self.out, "volatile ")?; } } if let Some(storage_qualifier) = glsl_storage_qualifier(global.space) { write!(self.out, "{storage_qualifier} ")?; } match global.space { crate::AddressSpace::Private => { self.write_simple_global(handle, global)?; } crate::AddressSpace::WorkGroup => { self.write_simple_global(handle, global)?; } crate::AddressSpace::Immediate => { self.write_simple_global(handle, global)?; } crate::AddressSpace::Uniform => { self.write_interface_block(handle, global)?; } crate::AddressSpace::Storage { .. } => { self.write_interface_block(handle, global)?; } crate::AddressSpace::TaskPayload => { self.write_interface_block(handle, global)?; } // A global variable in the `Function` address space is a // contradiction in terms. crate::AddressSpace::Function => unreachable!(), // Textures and samplers are handled directly in `Writer::write`. crate::AddressSpace::Handle => unreachable!(), // ray tracing pipelines unsupported crate::AddressSpace::RayPayload | crate::AddressSpace::IncomingRayPayload => { unreachable!() } } Ok(()) } fn write_simple_global( &mut self, handle: Handle, global: &crate::GlobalVariable, ) -> BackendResult { self.write_type(global.ty)?; write!(self.out, " ")?; self.write_global_name(handle, global)?; if let TypeInner::Array { base, size, .. } = self.module.types[global.ty].inner { self.write_array_size(base, size)?; } if global.space.initializable() && is_value_init_supported(self.module, global.ty) { write!(self.out, " = ")?; if let Some(init) = global.init { self.write_const_expr(init, &self.module.global_expressions)?; } else { self.write_zero_init_value(global.ty)?; } } writeln!(self.out, ";")?; if let crate::AddressSpace::Immediate = global.space { let global_name = self.get_global_name(handle, global); self.reflection_names_globals.insert(handle, global_name); } Ok(()) } /// Write an interface block for a single Naga global. /// /// Write `block_name { members }`. Since `block_name` must be unique /// between blocks and structs, we add `_block_ID` where `ID` is a /// `IdGenerator` generated number. Write `members` in the same way we write /// a struct's members. fn write_interface_block( &mut self, handle: Handle, global: &crate::GlobalVariable, ) -> BackendResult { // Write the block name, it's just the struct name appended with `_block_ID` let ty_name = &self.names[&NameKey::Type(global.ty)]; let block_name = format!( "{}_block_{}{:?}", // avoid double underscores as they are reserved in GLSL ty_name.trim_end_matches('_'), self.block_id.generate(), self.entry_point.stage, ); write!(self.out, "{block_name} ")?; self.reflection_names_globals.insert(handle, block_name); match self.module.types[global.ty].inner { TypeInner::Struct { ref members, .. } if self.module.types[members.last().unwrap().ty] .inner .is_dynamically_sized(&self.module.types) => { // Structs with dynamically sized arrays must have their // members lifted up as members of the interface block. GLSL // can't write such struct types anyway. self.write_struct_body(global.ty, members)?; write!(self.out, " ")?; self.write_global_name(handle, global)?; } _ => { // A global of any other type is written as the sole member // of the interface block. Since the interface block is // anonymous, this becomes visible in the global scope. write!(self.out, "{{ ")?; self.write_type(global.ty)?; write!(self.out, " ")?; self.write_global_name(handle, global)?; if let TypeInner::Array { base, size, .. } = self.module.types[global.ty].inner { self.write_array_size(base, size)?; } write!(self.out, "; }}")?; } } writeln!(self.out, ";")?; Ok(()) } /// Helper method used to find which expressions of a given function require baking /// /// # Notes /// Clears `need_bake_expressions` set before adding to it fn update_expressions_to_bake(&mut self, func: &crate::Function, info: &valid::FunctionInfo) { use crate::Expression; self.need_bake_expressions.clear(); for (fun_handle, expr) in func.expressions.iter() { let expr_info = &info[fun_handle]; let min_ref_count = func.expressions[fun_handle].bake_ref_count(); if min_ref_count <= expr_info.ref_count { self.need_bake_expressions.insert(fun_handle); } let inner = expr_info.ty.inner_with(&self.module.types); if let Expression::Math { fun, arg, arg1, arg2, .. } = *expr { match fun { crate::MathFunction::Dot => { // if the expression is a Dot product with integer arguments, // then the args needs baking as well if let TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, .. }) = *inner { self.need_bake_expressions.insert(arg); self.need_bake_expressions.insert(arg1.unwrap()); } } crate::MathFunction::Dot4U8Packed | crate::MathFunction::Dot4I8Packed => { self.need_bake_expressions.insert(arg); self.need_bake_expressions.insert(arg1.unwrap()); } crate::MathFunction::Pack4xI8 | crate::MathFunction::Pack4xU8 | crate::MathFunction::Pack4xI8Clamp | crate::MathFunction::Pack4xU8Clamp | crate::MathFunction::Unpack4xI8 | crate::MathFunction::Unpack4xU8 | crate::MathFunction::QuantizeToF16 => { self.need_bake_expressions.insert(arg); } /* crate::MathFunction::Pack4x8unorm | */ crate::MathFunction::Unpack4x8snorm if !self.options.version.supports_pack_unpack_4x8() => { // We have a fallback if the platform doesn't natively support these self.need_bake_expressions.insert(arg); } /* crate::MathFunction::Pack4x8unorm | */ crate::MathFunction::Unpack4x8unorm if !self.options.version.supports_pack_unpack_4x8() => { self.need_bake_expressions.insert(arg); } /* crate::MathFunction::Pack2x16snorm | */ crate::MathFunction::Unpack2x16snorm if !self.options.version.supports_pack_unpack_snorm_2x16() => { self.need_bake_expressions.insert(arg); } /* crate::MathFunction::Pack2x16unorm | */ crate::MathFunction::Unpack2x16unorm if !self.options.version.supports_pack_unpack_unorm_2x16() => { self.need_bake_expressions.insert(arg); } crate::MathFunction::ExtractBits => { // Only argument 1 is re-used. self.need_bake_expressions.insert(arg1.unwrap()); } crate::MathFunction::InsertBits => { // Only argument 2 is re-used. self.need_bake_expressions.insert(arg2.unwrap()); } crate::MathFunction::CountLeadingZeros => { if let Some(crate::ScalarKind::Sint) = inner.scalar_kind() { self.need_bake_expressions.insert(arg); } } _ => {} } } } for statement in func.body.iter() { match *statement { crate::Statement::Atomic { fun: crate::AtomicFunction::Exchange { compare: Some(cmp) }, .. } => { self.need_bake_expressions.insert(cmp); } _ => {} } } } /// Helper method used to get a name for a global /// /// Globals have different naming schemes depending on their binding: /// - Globals without bindings use the name from the [`Namer`](crate::proc::Namer) /// - Globals with resource binding are named `_group_X_binding_Y` where `X` /// is the group and `Y` is the binding fn get_global_name( &self, handle: Handle, global: &crate::GlobalVariable, ) -> String { match (&global.binding, global.space) { (&Some(ref br), _) => { format!( "_group_{}_binding_{}_{}", br.group, br.binding, self.entry_point.stage.to_str() ) } (&None, crate::AddressSpace::Immediate) => { format!("_immediates_binding_{}", self.entry_point.stage.to_str()) } (&None, _) => self.names[&NameKey::GlobalVariable(handle)].clone(), } } /// Helper method used to write a name for a global without additional heap allocation fn write_global_name( &mut self, handle: Handle, global: &crate::GlobalVariable, ) -> BackendResult { match (&global.binding, global.space) { (&Some(ref br), _) => write!( self.out, "_group_{}_binding_{}_{}", br.group, br.binding, self.entry_point.stage.to_str() )?, (&None, crate::AddressSpace::Immediate) => write!( self.out, "_immediates_binding_{}", self.entry_point.stage.to_str() )?, (&None, _) => write!( self.out, "{}", &self.names[&NameKey::GlobalVariable(handle)] )?, } Ok(()) } /// Write a GLSL global that will carry a Naga entry point's argument or return value. /// /// A Naga entry point's arguments and return value are rendered in GLSL as /// variables at global scope with the `in` and `out` storage qualifiers. /// The code we generate for `main` loads from all the `in` globals into /// appropriately named locals. Before it returns, `main` assigns the /// components of its return value into all the `out` globals. /// /// This function writes a declaration for one such GLSL global, /// representing a value passed into or returned from [`self.entry_point`] /// that has a [`Location`] binding. The global's name is generated based on /// the location index and the shader stages being connected; see /// [`VaryingName`]. This means we don't need to know the names of /// arguments, just their types and bindings. /// /// Emit nothing for entry point arguments or return values with [`BuiltIn`] /// bindings; `main` will read from or assign to the appropriate GLSL /// special variable; these are pre-declared. As an exception, we do declare /// `gl_Position` or `gl_FragCoord` with the `invariant` qualifier if /// needed. /// /// Use `output` together with [`self.entry_point.stage`] to determine which /// shader stages are being connected, and choose the `in` or `out` storage /// qualifier. /// /// [`self.entry_point`]: Writer::entry_point /// [`self.entry_point.stage`]: crate::EntryPoint::stage /// [`Location`]: crate::Binding::Location /// [`BuiltIn`]: crate::Binding::BuiltIn fn write_varying( &mut self, binding: Option<&crate::Binding>, ty: Handle, output: bool, ) -> Result<(), Error> { // For a struct, emit a separate global for each member with a binding. if let TypeInner::Struct { ref members, .. } = self.module.types[ty].inner { for member in members { self.write_varying(member.binding.as_ref(), member.ty, output)?; } return Ok(()); } let binding = match binding { None => return Ok(()), Some(binding) => binding, }; let (location, interpolation, sampling, blend_src) = match *binding { crate::Binding::Location { location, interpolation, sampling, blend_src, per_primitive: _, } => (location, interpolation, sampling, blend_src), crate::Binding::BuiltIn(built_in) => { match built_in { crate::BuiltIn::Position { invariant: true } => { match (self.options.version, self.entry_point.stage) { ( Version::Embedded { version: 300, is_webgl: true, }, ShaderStage::Fragment, ) => { // `invariant gl_FragCoord` is not allowed in WebGL2 and possibly // OpenGL ES in general (waiting on confirmation). // // See https://github.com/KhronosGroup/WebGL/issues/3518 } _ => { writeln!( self.out, "invariant {};", glsl_built_in( built_in, VaryingOptions::from_writer_options(self.options, output) ) )?; } } } crate::BuiltIn::ClipDistances => { // Re-declare `gl_ClipDistance` with number of clip planes. let TypeInner::Array { size, .. } = self.module.types[ty].inner else { unreachable!(); }; let proc::IndexableLength::Known(size) = size.resolve(self.module.to_ctx())? else { unreachable!(); }; self.clip_distance_count = size; writeln!(self.out, "out float gl_ClipDistance[{size}];")?; } _ => {} } return Ok(()); } }; // Write the interpolation modifier if needed // // We ignore all interpolation and auxiliary modifiers that aren't used in fragment // shaders' input globals or vertex shaders' output globals. let emit_interpolation_and_auxiliary = match self.entry_point.stage { ShaderStage::Vertex => output, ShaderStage::Fragment => !output, ShaderStage::Compute => false, ShaderStage::Task | ShaderStage::Mesh | ShaderStage::RayGeneration | ShaderStage::AnyHit | ShaderStage::ClosestHit | ShaderStage::Miss => unreachable!(), }; // Write the I/O locations, if allowed let io_location = if self.options.version.supports_explicit_locations() || !emit_interpolation_and_auxiliary { if self.options.version.supports_io_locations() { if let Some(blend_src) = blend_src { write!( self.out, "layout(location = {location}, index = {blend_src}) " )?; } else { write!(self.out, "layout(location = {location}) ")?; } None } else { Some(VaryingLocation { location, index: blend_src.unwrap_or(0), }) } } else { None }; // Write the interpolation qualifier. if let Some(interp) = interpolation { if emit_interpolation_and_auxiliary { write!(self.out, "{} ", glsl_interpolation(interp))?; } } // Write the sampling auxiliary qualifier. // // Before GLSL 4.2, the `centroid` and `sample` qualifiers were required to appear // immediately before the `in` / `out` qualifier, so we'll just follow that rule // here, regardless of the version. if let Some(sampling) = sampling { if emit_interpolation_and_auxiliary { if let Some(qualifier) = glsl_sampling(sampling)? { write!(self.out, "{qualifier} ")?; } } } // Write the input/output qualifier. write!(self.out, "{} ", if output { "out" } else { "in" })?; // Write the type // `write_type` adds no leading or trailing spaces self.write_type(ty)?; // Finally write the global name and end the global with a `;` and a newline // Leading space is important let vname = VaryingName { binding: &crate::Binding::Location { location, interpolation: None, sampling: None, blend_src, per_primitive: false, }, stage: self.entry_point.stage, options: VaryingOptions::from_writer_options(self.options, output), }; writeln!(self.out, " {vname};")?; if let Some(location) = io_location { self.varying.insert(vname.to_string(), location); } Ok(()) } /// Helper method used to write functions (both entry points and regular functions) /// /// # Notes /// Adds a newline fn write_function( &mut self, ty: back::FunctionType, func: &crate::Function, info: &valid::FunctionInfo, ) -> BackendResult { // Create a function context for the function being written let ctx = back::FunctionCtx { ty, info, expressions: &func.expressions, named_expressions: &func.named_expressions, }; self.named_expressions.clear(); self.update_expressions_to_bake(func, info); // Write the function header // // glsl headers are the same as in c: // `ret_type name(args)` // `ret_type` is the return type // `name` is the function name // `args` is a comma separated list of `type name` // | - `type` is the argument type // | - `name` is the argument name // Start by writing the return type if any otherwise write void // This is the only place where `void` is a valid type // (though it's more a keyword than a type) if let back::FunctionType::EntryPoint(_) = ctx.ty { write!(self.out, "void")?; } else if let Some(ref result) = func.result { self.write_type(result.ty)?; if let TypeInner::Array { base, size, .. } = self.module.types[result.ty].inner { self.write_array_size(base, size)? } } else { write!(self.out, "void")?; } // Write the function name and open parentheses for the argument list let function_name = match ctx.ty { back::FunctionType::Function(handle) => &self.names[&NameKey::Function(handle)], back::FunctionType::EntryPoint(_) => "main", }; write!(self.out, " {function_name}(")?; // Write the comma separated argument list // // We need access to `Self` here so we use the reference passed to the closure as an // argument instead of capturing as that would cause a borrow checker error let arguments = match ctx.ty { back::FunctionType::EntryPoint(_) => &[][..], back::FunctionType::Function(_) => &func.arguments, }; let arguments: Vec<_> = arguments .iter() .enumerate() .filter(|&(_, arg)| match self.module.types[arg.ty].inner { TypeInner::Sampler { .. } => false, _ => true, }) .collect(); self.write_slice(&arguments, |this, _, &(i, arg)| { // Write the argument type match this.module.types[arg.ty].inner { // We treat images separately because they might require // writing the storage format TypeInner::Image { dim, arrayed, class, } => { // Write the storage format if needed if let TypeInner::Image { class: crate::ImageClass::Storage { format, .. }, .. } = this.module.types[arg.ty].inner { write!(this.out, "layout({}) ", glsl_storage_format(format)?)?; } // write the type // // This is way we need the leading space because `write_image_type` doesn't add // any spaces at the beginning or end this.write_image_type(dim, arrayed, class)?; } TypeInner::Pointer { base, .. } => { // write parameter qualifiers write!(this.out, "inout ")?; this.write_type(base)?; } // All other types are written by `write_type` _ => { this.write_type(arg.ty)?; } } // Write the argument name // The leading space is important write!(this.out, " {}", &this.names[&ctx.argument_key(i as u32)])?; // Write array size match this.module.types[arg.ty].inner { TypeInner::Array { base, size, .. } => { this.write_array_size(base, size)?; } TypeInner::Pointer { base, .. } => { if let TypeInner::Array { base, size, .. } = this.module.types[base].inner { this.write_array_size(base, size)?; } } _ => {} } Ok(()) })?; // Close the parentheses and open braces to start the function body writeln!(self.out, ") {{")?; if self.options.zero_initialize_workgroup_memory && ctx.ty.is_compute_like_entry_point(self.module) { self.write_workgroup_variables_initialization(&ctx)?; } // Compose the function arguments from globals, in case of an entry point. if let back::FunctionType::EntryPoint(ep_index) = ctx.ty { let stage = self.module.entry_points[ep_index as usize].stage; for (index, arg) in func.arguments.iter().enumerate() { write!(self.out, "{}", back::INDENT)?; self.write_type(arg.ty)?; let name = &self.names[&NameKey::EntryPointArgument(ep_index, index as u32)]; write!(self.out, " {name}")?; write!(self.out, " = ")?; match self.module.types[arg.ty].inner { TypeInner::Struct { ref members, .. } => { self.write_type(arg.ty)?; write!(self.out, "(")?; for (index, member) in members.iter().enumerate() { let varying_name = VaryingName { binding: member.binding.as_ref().unwrap(), stage, options: VaryingOptions::from_writer_options(self.options, false), }; if index != 0 { write!(self.out, ", ")?; } write!(self.out, "{varying_name}")?; } writeln!(self.out, ");")?; } _ => { let varying_name = VaryingName { binding: arg.binding.as_ref().unwrap(), stage, options: VaryingOptions::from_writer_options(self.options, false), }; writeln!(self.out, "{varying_name};")?; } } } } // Write all function locals // Locals are `type name (= init)?;` where the init part (including the =) are optional // // Always adds a newline for (handle, local) in func.local_variables.iter() { // Write indentation (only for readability) and the type // `write_type` adds no trailing space write!(self.out, "{}", back::INDENT)?; self.write_type(local.ty)?; // Write the local name // The leading space is important write!(self.out, " {}", self.names[&ctx.name_key(handle)])?; // Write size for array type if let TypeInner::Array { base, size, .. } = self.module.types[local.ty].inner { self.write_array_size(base, size)?; } // Write the local initializer if needed if let Some(init) = local.init { // Put the equal signal only if there's a initializer // The leading and trailing spaces aren't needed but help with readability write!(self.out, " = ")?; // Write the constant // `write_constant` adds no trailing or leading space/newline self.write_expr(init, &ctx)?; } else if is_value_init_supported(self.module, local.ty) { write!(self.out, " = ")?; self.write_zero_init_value(local.ty)?; } // Finish the local with `;` and add a newline (only for readability) writeln!(self.out, ";")? } // Write the function body (statement list) for sta in func.body.iter() { // Write a statement, the indentation should always be 1 when writing the function body // `write_stmt` adds a newline self.write_stmt(sta, &ctx, back::Level(1))?; } // Close braces and add a newline writeln!(self.out, "}}")?; Ok(()) } fn write_workgroup_variables_initialization( &mut self, ctx: &back::FunctionCtx, ) -> BackendResult { let mut vars = self .module .global_variables .iter() .filter(|&(handle, var)| { !ctx.info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup }) .peekable(); if vars.peek().is_some() { let level = back::Level(1); writeln!(self.out, "{level}if (gl_LocalInvocationID == uvec3(0u)) {{")?; for (handle, var) in vars { let name = &self.names[&NameKey::GlobalVariable(handle)]; write!(self.out, "{}{} = ", level.next(), name)?; self.write_zero_init_value(var.ty)?; writeln!(self.out, ";")?; } writeln!(self.out, "{level}}}")?; self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?; } Ok(()) } /// Write a list of comma separated `T` values using a writer function `F`. /// /// The writer function `F` receives a mutable reference to `self` that if needed won't cause /// borrow checker issues (using for example a closure with `self` will cause issues), the /// second argument is the 0 based index of the element on the list, and the last element is /// a reference to the element `T` being written /// /// # Notes /// - Adds no newlines or leading/trailing whitespace /// - The last element won't have a trailing `,` fn write_slice BackendResult>( &mut self, data: &[T], mut f: F, ) -> BackendResult { // Loop through `data` invoking `f` for each element for (index, item) in data.iter().enumerate() { if index != 0 { write!(self.out, ", ")?; } f(self, index as u32, item)?; } Ok(()) } /// Helper method used to write global constants fn write_global_constant(&mut self, handle: Handle) -> BackendResult { write!(self.out, "const ")?; let constant = &self.module.constants[handle]; self.write_type(constant.ty)?; let name = &self.names[&NameKey::Constant(handle)]; write!(self.out, " {name}")?; if let TypeInner::Array { base, size, .. } = self.module.types[constant.ty].inner { self.write_array_size(base, size)?; } write!(self.out, " = ")?; self.write_const_expr(constant.init, &self.module.global_expressions)?; writeln!(self.out, ";")?; Ok(()) } /// Helper method used to output a dot product as an arithmetic expression /// fn write_dot_product( &mut self, arg: Handle, arg1: Handle, size: usize, ctx: &back::FunctionCtx, ) -> BackendResult { // Write parentheses around the dot product expression to prevent operators // with different precedences from applying earlier. write!(self.out, "(")?; // Cycle through all the components of the vector for index in 0..size { let component = back::COMPONENTS[index]; // Write the addition to the previous product // This will print an extra '+' at the beginning but that is fine in glsl write!(self.out, " + ")?; // Write the first vector expression, this expression is marked to be // cached so unless it can't be cached (for example, it's a Constant) // it shouldn't produce large expressions. self.write_expr(arg, ctx)?; // Access the current component on the first vector write!(self.out, ".{component} * ")?; // Write the second vector expression, this expression is marked to be // cached so unless it can't be cached (for example, it's a Constant) // it shouldn't produce large expressions. self.write_expr(arg1, ctx)?; // Access the current component on the second vector write!(self.out, ".{component}")?; } write!(self.out, ")")?; Ok(()) } /// Helper method used to write structs /// /// # Notes /// Ends in a newline fn write_struct_body( &mut self, handle: Handle, members: &[crate::StructMember], ) -> BackendResult { // glsl structs are written as in C // `struct name() { members };` // | `struct` is a keyword // | `name` is the struct name // | `members` is a semicolon separated list of `type name` // | `type` is the member type // | `name` is the member name writeln!(self.out, "{{")?; for (idx, member) in members.iter().enumerate() { // The indentation is only for readability write!(self.out, "{}", back::INDENT)?; match self.module.types[member.ty].inner { TypeInner::Array { base, size, stride: _, } => { self.write_type(base)?; write!( self.out, " {}", &self.names[&NameKey::StructMember(handle, idx as u32)] )?; // Write [size] self.write_array_size(base, size)?; // Newline is important writeln!(self.out, ";")?; } _ => { // Write the member type // Adds no trailing space self.write_type(member.ty)?; // Write the member name and put a semicolon // The leading space is important // All members must have a semicolon even the last one writeln!( self.out, " {};", &self.names[&NameKey::StructMember(handle, idx as u32)] )?; } } } write!(self.out, "}}")?; Ok(()) } /// Helper method used to write statements /// /// # Notes /// Always adds a newline fn write_stmt( &mut self, sta: &crate::Statement, ctx: &back::FunctionCtx, level: back::Level, ) -> BackendResult { use crate::Statement; match *sta { // This is where we can generate intermediate constants for some expression types. Statement::Emit(ref range) => { for handle in range.clone() { let ptr_class = ctx.resolve_type(handle, &self.module.types).pointer_space(); let expr_name = if ptr_class.is_some() { // GLSL can't save a pointer-valued expression in a variable, // but we shouldn't ever need to: they should never be named expressions, // and none of the expression types flagged by bake_ref_count can be pointer-valued. None } else if let Some(name) = ctx.named_expressions.get(&handle) { // Front end provides names for all variables at the start of writing. // But we write them to step by step. We need to recache them // Otherwise, we could accidentally write variable name instead of full expression. // Also, we use sanitized names! It defense backend from generating variable with name from reserved keywords. Some(self.namer.call(name)) } else if self.need_bake_expressions.contains(&handle) { Some(Baked(handle).to_string()) } else { None }; // If we are going to write an `ImageLoad` next and the target image // is sampled and we are using the `Restrict` policy for bounds // checking images we need to write a local holding the clamped lod. if let crate::Expression::ImageLoad { image, level: Some(level_expr), .. } = ctx.expressions[handle] { if let TypeInner::Image { class: crate::ImageClass::Sampled { .. }, .. } = *ctx.resolve_type(image, &self.module.types) { if let proc::BoundsCheckPolicy::Restrict = self.policies.image_load { write!(self.out, "{level}")?; self.write_clamped_lod(ctx, handle, image, level_expr)? } } } if let Some(name) = expr_name { write!(self.out, "{level}")?; self.write_named_expr(handle, name, handle, ctx)?; } } } // Blocks are simple we just need to write the block statements between braces // We could also just print the statements but this is more readable and maps more // closely to the IR Statement::Block(ref block) => { write!(self.out, "{level}")?; writeln!(self.out, "{{")?; for sta in block.iter() { // Increase the indentation to help with readability self.write_stmt(sta, ctx, level.next())? } writeln!(self.out, "{level}}}")? } // Ifs are written as in C: // ``` // if(condition) { // accept // } else { // reject // } // ``` Statement::If { condition, ref accept, ref reject, } => { write!(self.out, "{level}")?; write!(self.out, "if (")?; self.write_expr(condition, ctx)?; writeln!(self.out, ") {{")?; for sta in accept { // Increase indentation to help with readability self.write_stmt(sta, ctx, level.next())?; } // If there are no statements in the reject block we skip writing it // This is only for readability if !reject.is_empty() { writeln!(self.out, "{level}}} else {{")?; for sta in reject { // Increase indentation to help with readability self.write_stmt(sta, ctx, level.next())?; } } writeln!(self.out, "{level}}}")? } // Switch are written as in C: // ``` // switch (selector) { // // Fallthrough // case label: // block // // Non fallthrough // case label: // block // break; // default: // block // } // ``` // Where the `default` case happens isn't important but we put it last // so that we don't need to print a `break` for it Statement::Switch { selector, ref cases, } => { let l2 = level.next(); // Some GLSL consumers may not handle switches with a single // body correctly: See wgpu#4514. Write such switch statements // as a `do {} while(false);` loop instead. // // Since doing so may inadvertently capture `continue` // statements in the switch body, we must apply continue // forwarding. See the `naga::back::continue_forward` module // docs for details. let one_body = cases .iter() .rev() .skip(1) .all(|case| case.fall_through && case.body.is_empty()); if one_body { // Unlike HLSL, in GLSL `continue_ctx` only needs to know // about [`Switch`] statements that are being rendered as // `do-while` loops. if let Some(variable) = self.continue_ctx.enter_switch(&mut self.namer) { writeln!(self.out, "{level}bool {variable} = false;",)?; }; writeln!(self.out, "{level}do {{")?; // Note: Expressions have no side-effects so we don't need to emit selector expression. // Body if let Some(case) = cases.last() { for sta in case.body.iter() { self.write_stmt(sta, ctx, l2)?; } } // End do-while writeln!(self.out, "{level}}} while(false);")?; // Handle any forwarded continue statements. use back::continue_forward::ExitControlFlow; let op = match self.continue_ctx.exit_switch() { ExitControlFlow::None => None, ExitControlFlow::Continue { variable } => Some(("continue", variable)), ExitControlFlow::Break { variable } => Some(("break", variable)), }; if let Some((control_flow, variable)) = op { writeln!(self.out, "{level}if ({variable}) {{")?; writeln!(self.out, "{l2}{control_flow};")?; writeln!(self.out, "{level}}}")?; } } else { // Start the switch write!(self.out, "{level}")?; write!(self.out, "switch(")?; self.write_expr(selector, ctx)?; writeln!(self.out, ") {{")?; // Write all cases for case in cases { match case.value { crate::SwitchValue::I32(value) => { write!(self.out, "{l2}case {value}:")? } crate::SwitchValue::U32(value) => { write!(self.out, "{l2}case {value}u:")? } crate::SwitchValue::Default => write!(self.out, "{l2}default:")?, } let write_block_braces = !(case.fall_through && case.body.is_empty()); if write_block_braces { writeln!(self.out, " {{")?; } else { writeln!(self.out)?; } for sta in case.body.iter() { self.write_stmt(sta, ctx, l2.next())?; } if !case.fall_through && case.body.last().is_none_or(|s| !s.is_terminator()) { writeln!(self.out, "{}break;", l2.next())?; } if write_block_braces { writeln!(self.out, "{l2}}}")?; } } writeln!(self.out, "{level}}}")? } } // Loops in naga IR are based on wgsl loops, glsl can emulate the behaviour by using a // while true loop and appending the continuing block to the body resulting on: // ``` // bool loop_init = true; // while(true) { // if (!loop_init) { } // loop_init = false; // // } // ``` Statement::Loop { ref body, ref continuing, break_if, } => { self.continue_ctx.enter_loop(); if !continuing.is_empty() || break_if.is_some() { let gate_name = self.namer.call("loop_init"); writeln!(self.out, "{level}bool {gate_name} = true;")?; writeln!(self.out, "{level}while(true) {{")?; let l2 = level.next(); let l3 = l2.next(); writeln!(self.out, "{l2}if (!{gate_name}) {{")?; for sta in continuing { self.write_stmt(sta, ctx, l3)?; } if let Some(condition) = break_if { write!(self.out, "{l3}if (")?; self.write_expr(condition, ctx)?; writeln!(self.out, ") {{")?; writeln!(self.out, "{}break;", l3.next())?; writeln!(self.out, "{l3}}}")?; } writeln!(self.out, "{l2}}}")?; writeln!(self.out, "{}{} = false;", level.next(), gate_name)?; } else { writeln!(self.out, "{level}while(true) {{")?; } for sta in body { self.write_stmt(sta, ctx, level.next())?; } writeln!(self.out, "{level}}}")?; self.continue_ctx.exit_loop(); } // Break, continue and return as written as in C // `break;` Statement::Break => { write!(self.out, "{level}")?; writeln!(self.out, "break;")? } // `continue;` Statement::Continue => { // Sometimes we must render a `Continue` statement as a `break`. // See the docs for the `back::continue_forward` module. if let Some(variable) = self.continue_ctx.continue_encountered() { writeln!(self.out, "{level}{variable} = true;",)?; writeln!(self.out, "{level}break;")? } else { writeln!(self.out, "{level}continue;")? } } // `return expr;`, `expr` is optional Statement::Return { value } => { write!(self.out, "{level}")?; match ctx.ty { back::FunctionType::Function(_) => { write!(self.out, "return")?; // Write the expression to be returned if needed if let Some(expr) = value { write!(self.out, " ")?; self.write_expr(expr, ctx)?; } writeln!(self.out, ";")?; } back::FunctionType::EntryPoint(ep_index) => { let mut has_point_size = false; let ep = &self.module.entry_points[ep_index as usize]; if let Some(ref result) = ep.function.result { let value = value.unwrap(); match self.module.types[result.ty].inner { TypeInner::Struct { ref members, .. } => { let temp_struct_name = match ctx.expressions[value] { crate::Expression::Compose { .. } => { let return_struct = "_tmp_return"; write!( self.out, "{} {} = ", &self.names[&NameKey::Type(result.ty)], return_struct )?; self.write_expr(value, ctx)?; writeln!(self.out, ";")?; write!(self.out, "{level}")?; Some(return_struct) } _ => None, }; for (index, member) in members.iter().enumerate() { if let Some(crate::Binding::BuiltIn( crate::BuiltIn::PointSize, )) = member.binding { has_point_size = true; } let varying_name = VaryingName { binding: member.binding.as_ref().unwrap(), stage: ep.stage, options: VaryingOptions::from_writer_options( self.options, true, ), }; write!(self.out, "{varying_name} = ")?; if let Some(struct_name) = temp_struct_name { write!(self.out, "{struct_name}")?; } else { self.write_expr(value, ctx)?; } // Write field name writeln!( self.out, ".{};", &self.names [&NameKey::StructMember(result.ty, index as u32)] )?; write!(self.out, "{level}")?; } } _ => { let name = VaryingName { binding: result.binding.as_ref().unwrap(), stage: ep.stage, options: VaryingOptions::from_writer_options( self.options, true, ), }; write!(self.out, "{name} = ")?; self.write_expr(value, ctx)?; writeln!(self.out, ";")?; write!(self.out, "{level}")?; } } } let is_vertex_stage = self.module.entry_points[ep_index as usize].stage == ShaderStage::Vertex; if is_vertex_stage && self .options .writer_flags .contains(WriterFlags::ADJUST_COORDINATE_SPACE) { writeln!( self.out, "gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w);", )?; write!(self.out, "{level}")?; } if is_vertex_stage && self .options .writer_flags .contains(WriterFlags::FORCE_POINT_SIZE) && !has_point_size { writeln!(self.out, "gl_PointSize = 1.0;")?; write!(self.out, "{level}")?; } writeln!(self.out, "return;")?; } } } // This is one of the places were glsl adds to the syntax of C in this case the discard // keyword which ceases all further processing in a fragment shader, it's called OpKill // in spir-v that's why it's called `Statement::Kill` Statement::Kill => writeln!(self.out, "{level}discard;")?, Statement::ControlBarrier(flags) => { self.write_control_barrier(flags, level)?; } Statement::MemoryBarrier(flags) => { self.write_memory_barrier(flags, level)?; } // Stores in glsl are just variable assignments written as `pointer = value;` Statement::Store { pointer, value } => { write!(self.out, "{level}")?; self.write_expr(pointer, ctx)?; write!(self.out, " = ")?; self.write_expr(value, ctx)?; writeln!(self.out, ";")? } Statement::WorkGroupUniformLoad { pointer, result } => { // GLSL doesn't have pointers, which means that this backend needs to ensure that // the actual "loading" is happening between the two barriers. // This is done in `Emit` by never emitting a variable name for pointer variables self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?; let result_name = Baked(result).to_string(); write!(self.out, "{level}")?; // Expressions cannot have side effects, so just writing the expression here is fine. self.write_named_expr(pointer, result_name, result, ctx)?; self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?; } // Stores a value into an image. Statement::ImageStore { image, coordinate, array_index, value, } => { write!(self.out, "{level}")?; self.write_image_store(ctx, image, coordinate, array_index, value)? } // A `Call` is written `name(arguments)` where `arguments` is a comma separated expressions list Statement::Call { function, ref arguments, result, } => { write!(self.out, "{level}")?; if let Some(expr) = result { let name = Baked(expr).to_string(); let result = self.module.functions[function].result.as_ref().unwrap(); self.write_type(result.ty)?; write!(self.out, " {name}")?; if let TypeInner::Array { base, size, .. } = self.module.types[result.ty].inner { self.write_array_size(base, size)? } write!(self.out, " = ")?; self.named_expressions.insert(expr, name); } write!(self.out, "{}(", &self.names[&NameKey::Function(function)])?; let arguments: Vec<_> = arguments .iter() .enumerate() .filter_map(|(i, arg)| { let arg_ty = self.module.functions[function].arguments[i].ty; match self.module.types[arg_ty].inner { TypeInner::Sampler { .. } => None, _ => Some(*arg), } }) .collect(); self.write_slice(&arguments, |this, _, arg| this.write_expr(*arg, ctx))?; writeln!(self.out, ");")? } Statement::Atomic { pointer, ref fun, value, result, } => { write!(self.out, "{level}")?; match *fun { crate::AtomicFunction::Exchange { compare: Some(compare_expr), } => { let result_handle = result.expect("CompareExchange must have a result"); let res_name = Baked(result_handle).to_string(); self.write_type(ctx.info[result_handle].ty.handle().unwrap())?; write!(self.out, " {res_name};")?; write!(self.out, " {res_name}.old_value = atomicCompSwap(")?; self.write_expr(pointer, ctx)?; write!(self.out, ", ")?; self.write_expr(compare_expr, ctx)?; write!(self.out, ", ")?; self.write_expr(value, ctx)?; writeln!(self.out, ");")?; write!( self.out, "{level}{res_name}.exchanged = ({res_name}.old_value == " )?; self.write_expr(compare_expr, ctx)?; writeln!(self.out, ");")?; self.named_expressions.insert(result_handle, res_name); } _ => { if let Some(result) = result { let res_name = Baked(result).to_string(); self.write_type(ctx.info[result].ty.handle().unwrap())?; write!(self.out, " {res_name} = ")?; self.named_expressions.insert(result, res_name); } let fun_str = fun.to_glsl(); write!(self.out, "atomic{fun_str}(")?; self.write_expr(pointer, ctx)?; write!(self.out, ", ")?; if let crate::AtomicFunction::Subtract = *fun { // Emulate `atomicSub` with `atomicAdd` by negating the value. write!(self.out, "-")?; } self.write_expr(value, ctx)?; writeln!(self.out, ");")?; } } } // Stores a value into an image. Statement::ImageAtomic { image, coordinate, array_index, fun, value, } => { write!(self.out, "{level}")?; self.write_image_atomic(ctx, image, coordinate, array_index, fun, value)? } Statement::RayQuery { .. } => unreachable!(), Statement::SubgroupBallot { result, predicate } => { write!(self.out, "{level}")?; let res_name = Baked(result).to_string(); let res_ty = ctx.info[result].ty.inner_with(&self.module.types); self.write_value_type(res_ty)?; write!(self.out, " {res_name} = ")?; self.named_expressions.insert(result, res_name); write!(self.out, "subgroupBallot(")?; match predicate { Some(predicate) => self.write_expr(predicate, ctx)?, None => write!(self.out, "true")?, } writeln!(self.out, ");")?; } Statement::SubgroupCollectiveOperation { op, collective_op, argument, result, } => { write!(self.out, "{level}")?; let res_name = Baked(result).to_string(); let res_ty = ctx.info[result].ty.inner_with(&self.module.types); self.write_value_type(res_ty)?; write!(self.out, " {res_name} = ")?; self.named_expressions.insert(result, res_name); match (collective_op, op) { (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::All) => { write!(self.out, "subgroupAll(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Any) => { write!(self.out, "subgroupAny(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Add) => { write!(self.out, "subgroupAdd(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Mul) => { write!(self.out, "subgroupMul(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Max) => { write!(self.out, "subgroupMax(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Min) => { write!(self.out, "subgroupMin(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::And) => { write!(self.out, "subgroupAnd(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Or) => { write!(self.out, "subgroupOr(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Xor) => { write!(self.out, "subgroupXor(")? } (crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Add) => { write!(self.out, "subgroupExclusiveAdd(")? } (crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Mul) => { write!(self.out, "subgroupExclusiveMul(")? } (crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Add) => { write!(self.out, "subgroupInclusiveAdd(")? } (crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Mul) => { write!(self.out, "subgroupInclusiveMul(")? } _ => unimplemented!(), } self.write_expr(argument, ctx)?; writeln!(self.out, ");")?; } Statement::SubgroupGather { mode, argument, result, } => { write!(self.out, "{level}")?; let res_name = Baked(result).to_string(); let res_ty = ctx.info[result].ty.inner_with(&self.module.types); self.write_value_type(res_ty)?; write!(self.out, " {res_name} = ")?; self.named_expressions.insert(result, res_name); match mode { crate::GatherMode::BroadcastFirst => { write!(self.out, "subgroupBroadcastFirst(")?; } crate::GatherMode::Broadcast(_) => { write!(self.out, "subgroupBroadcast(")?; } crate::GatherMode::Shuffle(_) => { write!(self.out, "subgroupShuffle(")?; } crate::GatherMode::ShuffleDown(_) => { write!(self.out, "subgroupShuffleDown(")?; } crate::GatherMode::ShuffleUp(_) => { write!(self.out, "subgroupShuffleUp(")?; } crate::GatherMode::ShuffleXor(_) => { write!(self.out, "subgroupShuffleXor(")?; } crate::GatherMode::QuadBroadcast(_) => { write!(self.out, "subgroupQuadBroadcast(")?; } crate::GatherMode::QuadSwap(direction) => match direction { crate::Direction::X => { write!(self.out, "subgroupQuadSwapHorizontal(")?; } crate::Direction::Y => { write!(self.out, "subgroupQuadSwapVertical(")?; } crate::Direction::Diagonal => { write!(self.out, "subgroupQuadSwapDiagonal(")?; } }, } self.write_expr(argument, ctx)?; match mode { crate::GatherMode::BroadcastFirst => {} crate::GatherMode::Broadcast(index) | crate::GatherMode::Shuffle(index) | crate::GatherMode::ShuffleDown(index) | crate::GatherMode::ShuffleUp(index) | crate::GatherMode::ShuffleXor(index) | crate::GatherMode::QuadBroadcast(index) => { write!(self.out, ", ")?; self.write_expr(index, ctx)?; } crate::GatherMode::QuadSwap(_) => {} } writeln!(self.out, ");")?; } Statement::CooperativeStore { .. } => unimplemented!(), Statement::RayPipelineFunction(_) => unimplemented!(), } Ok(()) } /// Write a const expression. /// /// Write `expr`, a handle to an [`Expression`] in the current [`Module`]'s /// constant expression arena, as GLSL expression. /// /// # Notes /// Adds no newlines or leading/trailing whitespace /// /// [`Expression`]: crate::Expression /// [`Module`]: crate::Module fn write_const_expr( &mut self, expr: Handle, arena: &crate::Arena, ) -> BackendResult { self.write_possibly_const_expr( expr, arena, |expr| &self.info[expr], |writer, expr| writer.write_const_expr(expr, arena), ) } /// Write [`Expression`] variants that can occur in both runtime and const expressions. /// /// Write `expr`, a handle to an [`Expression`] in the arena `expressions`, /// as as GLSL expression. This must be one of the [`Expression`] variants /// that is allowed to occur in constant expressions. /// /// Use `write_expression` to write subexpressions. /// /// This is the common code for `write_expr`, which handles arbitrary /// runtime expressions, and `write_const_expr`, which only handles /// const-expressions. Each of those callers passes itself (essentially) as /// the `write_expression` callback, so that subexpressions are restricted /// to the appropriate variants. /// /// # Notes /// Adds no newlines or leading/trailing whitespace /// /// [`Expression`]: crate::Expression fn write_possibly_const_expr<'w, I, E>( &'w mut self, expr: Handle, expressions: &crate::Arena, info: I, write_expression: E, ) -> BackendResult where I: Fn(Handle) -> &'w proc::TypeResolution, E: Fn(&mut Self, Handle) -> BackendResult, { use crate::Expression; match expressions[expr] { Expression::Literal(literal) => { match literal { // Floats are written using `Debug` instead of `Display` because it always appends the // decimal part even it's zero which is needed for a valid glsl float constant crate::Literal::F64(value) => write!(self.out, "{value:?}LF")?, crate::Literal::F32(value) => write!(self.out, "{value:?}")?, crate::Literal::F16(_) => { return Err(Error::Custom("GLSL has no 16-bit float type".into())); } // Unsigned integers need a `u` at the end // // While `core` doesn't necessarily need it, it's allowed and since `es` needs it we // always write it as the extra branch wouldn't have any benefit in readability crate::Literal::U32(value) => write!(self.out, "{value}u")?, crate::Literal::I32(value) => write!(self.out, "{value}")?, crate::Literal::Bool(value) => write!(self.out, "{value}")?, crate::Literal::I64(_) => { return Err(Error::Custom("GLSL has no 64-bit integer type".into())); } crate::Literal::U64(_) => { return Err(Error::Custom("GLSL has no 64-bit integer type".into())); } crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { return Err(Error::Custom( "Abstract types should not appear in IR presented to backends".into(), )); } } } Expression::Constant(handle) => { let constant = &self.module.constants[handle]; if constant.name.is_some() { write!(self.out, "{}", self.names[&NameKey::Constant(handle)])?; } else { self.write_const_expr(constant.init, &self.module.global_expressions)?; } } Expression::ZeroValue(ty) => { self.write_zero_init_value(ty)?; } Expression::Compose { ty, ref components } => { self.write_type(ty)?; if let TypeInner::Array { base, size, .. } = self.module.types[ty].inner { self.write_array_size(base, size)?; } write!(self.out, "(")?; for (index, component) in components.iter().enumerate() { if index != 0 { write!(self.out, ", ")?; } write_expression(self, *component)?; } write!(self.out, ")")? } // `Splat` needs to actually write down a vector, it's not always inferred in GLSL. Expression::Splat { size: _, value } => { let resolved = info(expr).inner_with(&self.module.types); self.write_value_type(resolved)?; write!(self.out, "(")?; write_expression(self, value)?; write!(self.out, ")")? } _ => { return Err(Error::Override); } } Ok(()) } /// Helper method to write expressions /// /// # Notes /// Doesn't add any newlines or leading/trailing spaces fn write_expr( &mut self, expr: Handle, ctx: &back::FunctionCtx, ) -> BackendResult { use crate::Expression; if let Some(name) = self.named_expressions.get(&expr) { write!(self.out, "{name}")?; return Ok(()); } match ctx.expressions[expr] { Expression::Literal(_) | Expression::Constant(_) | Expression::ZeroValue(_) | Expression::Compose { .. } | Expression::Splat { .. } => { self.write_possibly_const_expr( expr, ctx.expressions, |expr| &ctx.info[expr].ty, |writer, expr| writer.write_expr(expr, ctx), )?; } Expression::Override(_) => return Err(Error::Override), // `Access` is applied to arrays, vectors and matrices and is written as indexing Expression::Access { base, index } => { self.write_expr(base, ctx)?; write!(self.out, "[")?; self.write_expr(index, ctx)?; write!(self.out, "]")? } // `AccessIndex` is the same as `Access` except that the index is a constant and it can // be applied to structs, in this case we need to find the name of the field at that // index and write `base.field_name` Expression::AccessIndex { base, index } => { self.write_expr(base, ctx)?; let base_ty_res = &ctx.info[base].ty; let mut resolved = base_ty_res.inner_with(&self.module.types); let base_ty_handle = match *resolved { TypeInner::Pointer { base, space: _ } => { resolved = &self.module.types[base].inner; Some(base) } _ => base_ty_res.handle(), }; match *resolved { TypeInner::Vector { .. } => { // Write vector access as a swizzle write!(self.out, ".{}", back::COMPONENTS[index as usize])? } TypeInner::Matrix { .. } | TypeInner::Array { .. } | TypeInner::ValuePointer { .. } => write!(self.out, "[{index}]")?, TypeInner::Struct { .. } => { // This will never panic in case the type is a `Struct`, this is not true // for other types so we can only check while inside this match arm let ty = base_ty_handle.unwrap(); write!( self.out, ".{}", &self.names[&NameKey::StructMember(ty, index)] )? } ref other => return Err(Error::Custom(format!("Cannot index {other:?}"))), } } // `Swizzle` adds a few letters behind the dot. Expression::Swizzle { size, vector, pattern, } => { self.write_expr(vector, ctx)?; write!(self.out, ".")?; for &sc in pattern[..size as usize].iter() { self.out.write_char(back::COMPONENTS[sc as usize])?; } } // Function arguments are written as the argument name Expression::FunctionArgument(pos) => { write!(self.out, "{}", &self.names[&ctx.argument_key(pos)])? } // Global variables need some special work for their name but // `get_global_name` does the work for us Expression::GlobalVariable(handle) => { let global = &self.module.global_variables[handle]; self.write_global_name(handle, global)? } // A local is written as it's name Expression::LocalVariable(handle) => { write!(self.out, "{}", self.names[&ctx.name_key(handle)])? } // glsl has no pointers so there's no load operation, just write the pointer expression Expression::Load { pointer } => self.write_expr(pointer, ctx)?, // `ImageSample` is a bit complicated compared to the rest of the IR. // // First there are three variations depending whether the sample level is explicitly set, // if it's automatic or it it's bias: // `texture(image, coordinate)` - Automatic sample level // `texture(image, coordinate, bias)` - Bias sample level // `textureLod(image, coordinate, level)` - Zero or Exact sample level // // Furthermore if `depth_ref` is some we need to append it to the coordinate vector Expression::ImageSample { image, sampler: _, //TODO? gather, coordinate, array_index, offset, level, depth_ref, clamp_to_edge: _, } => { let (dim, class, arrayed) = match *ctx.resolve_type(image, &self.module.types) { TypeInner::Image { dim, class, arrayed, .. } => (dim, class, arrayed), _ => unreachable!(), }; let mut err = None; if dim == crate::ImageDimension::Cube { if offset.is_some() { err = Some("gsamplerCube[Array][Shadow] doesn't support texture sampling with offsets"); } if arrayed && matches!(class, crate::ImageClass::Depth { .. }) && matches!(level, crate::SampleLevel::Gradient { .. }) { err = Some("samplerCubeArrayShadow don't support textureGrad"); } } if gather.is_some() && level != crate::SampleLevel::Zero { err = Some("textureGather doesn't support LOD parameters"); } if let Some(err) = err { return Err(Error::Custom(String::from(err))); } // `textureLod[Offset]` on `sampler2DArrayShadow` and `samplerCubeShadow` does not exist in GLSL, // unless `GL_EXT_texture_shadow_lod` is present. // But if the target LOD is zero, we can emulate that by using `textureGrad[Offset]` with a constant gradient of 0. let workaround_lod_with_grad = ((dim == crate::ImageDimension::Cube && !arrayed) || (dim == crate::ImageDimension::D2 && arrayed)) && level == crate::SampleLevel::Zero && matches!(class, crate::ImageClass::Depth { .. }) && !self.features.contains(Features::TEXTURE_SHADOW_LOD); // Write the function to be used depending on the sample level let fun_name = match level { crate::SampleLevel::Zero if gather.is_some() => "textureGather", crate::SampleLevel::Zero if workaround_lod_with_grad => "textureGrad", crate::SampleLevel::Auto | crate::SampleLevel::Bias(_) => "texture", crate::SampleLevel::Zero | crate::SampleLevel::Exact(_) => "textureLod", crate::SampleLevel::Gradient { .. } => "textureGrad", }; let offset_name = match offset { Some(_) => "Offset", None => "", }; write!(self.out, "{fun_name}{offset_name}(")?; // Write the image that will be used self.write_expr(image, ctx)?; // The space here isn't required but it helps with readability write!(self.out, ", ")?; // TODO: handle clamp_to_edge // https://github.com/gfx-rs/wgpu/issues/7791 // We need to get the coordinates vector size to later build a vector that's `size + 1` // if `depth_ref` is some, if it isn't a vector we panic as that's not a valid expression let mut coord_dim = match *ctx.resolve_type(coordinate, &self.module.types) { TypeInner::Vector { size, .. } => size as u8, TypeInner::Scalar { .. } => 1, _ => unreachable!(), }; if array_index.is_some() { coord_dim += 1; } let merge_depth_ref = depth_ref.is_some() && gather.is_none() && coord_dim < 4; if merge_depth_ref { coord_dim += 1; } let tex_1d_hack = dim == crate::ImageDimension::D1 && self.options.version.is_es(); let is_vec = tex_1d_hack || coord_dim != 1; // Compose a new texture coordinates vector if is_vec { write!(self.out, "vec{}(", coord_dim + tex_1d_hack as u8)?; } self.write_expr(coordinate, ctx)?; if tex_1d_hack { write!(self.out, ", 0.0")?; } if let Some(expr) = array_index { write!(self.out, ", ")?; self.write_expr(expr, ctx)?; } if merge_depth_ref { write!(self.out, ", ")?; self.write_expr(depth_ref.unwrap(), ctx)?; } if is_vec { write!(self.out, ")")?; } if let (Some(expr), false) = (depth_ref, merge_depth_ref) { write!(self.out, ", ")?; self.write_expr(expr, ctx)?; } match level { // Auto needs no more arguments crate::SampleLevel::Auto => (), // Zero needs level set to 0 crate::SampleLevel::Zero => { if workaround_lod_with_grad { let vec_dim = match dim { crate::ImageDimension::Cube => 3, _ => 2, }; write!(self.out, ", vec{vec_dim}(0.0), vec{vec_dim}(0.0)")?; } else if gather.is_none() { write!(self.out, ", 0.0")?; } } // Exact and bias require another argument crate::SampleLevel::Exact(expr) => { write!(self.out, ", ")?; self.write_expr(expr, ctx)?; } crate::SampleLevel::Bias(_) => { // This needs to be done after the offset writing } crate::SampleLevel::Gradient { x, y } => { // If we are using sampler2D to replace sampler1D, we also // need to make sure to use vec2 gradients if tex_1d_hack { write!(self.out, ", vec2(")?; self.write_expr(x, ctx)?; write!(self.out, ", 0.0)")?; write!(self.out, ", vec2(")?; self.write_expr(y, ctx)?; write!(self.out, ", 0.0)")?; } else { write!(self.out, ", ")?; self.write_expr(x, ctx)?; write!(self.out, ", ")?; self.write_expr(y, ctx)?; } } } if let Some(constant) = offset { write!(self.out, ", ")?; if tex_1d_hack { write!(self.out, "ivec2(")?; } self.write_const_expr(constant, ctx.expressions)?; if tex_1d_hack { write!(self.out, ", 0)")?; } } // Bias is always the last argument if let crate::SampleLevel::Bias(expr) = level { write!(self.out, ", ")?; self.write_expr(expr, ctx)?; } if let (Some(component), None) = (gather, depth_ref) { write!(self.out, ", {}", component as usize)?; } // End the function write!(self.out, ")")? } Expression::ImageLoad { image, coordinate, array_index, sample, level, } => self.write_image_load(expr, ctx, image, coordinate, array_index, sample, level)?, // Query translates into one of the: // - textureSize/imageSize // - textureQueryLevels // - textureSamples/imageSamples Expression::ImageQuery { image, query } => { use crate::ImageClass; // This will only panic if the module is invalid let (dim, class) = match *ctx.resolve_type(image, &self.module.types) { TypeInner::Image { dim, arrayed: _, class, } => (dim, class), _ => unreachable!(), }; let components = match dim { crate::ImageDimension::D1 => 1, crate::ImageDimension::D2 => 2, crate::ImageDimension::D3 => 3, crate::ImageDimension::Cube => 2, }; if let crate::ImageQuery::Size { .. } = query { match components { 1 => write!(self.out, "uint(")?, _ => write!(self.out, "uvec{components}(")?, } } else { write!(self.out, "uint(")?; } match query { crate::ImageQuery::Size { level } => { match class { ImageClass::Sampled { multi, .. } | ImageClass::Depth { multi } => { write!(self.out, "textureSize(")?; self.write_expr(image, ctx)?; if let Some(expr) = level { let cast_to_int = matches!( *ctx.resolve_type(expr, &self.module.types), TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Uint, .. }) ); write!(self.out, ", ")?; if cast_to_int { write!(self.out, "int(")?; } self.write_expr(expr, ctx)?; if cast_to_int { write!(self.out, ")")?; } } else if !multi { // All textureSize calls requires an lod argument // except for multisampled samplers write!(self.out, ", 0")?; } } ImageClass::Storage { .. } => { write!(self.out, "imageSize(")?; self.write_expr(image, ctx)?; } ImageClass::External => unimplemented!(), } write!(self.out, ")")?; if components != 1 || self.options.version.is_es() { write!(self.out, ".{}", &"xyz"[..components])?; } } crate::ImageQuery::NumLevels => { write!(self.out, "textureQueryLevels(",)?; self.write_expr(image, ctx)?; write!(self.out, ")",)?; } crate::ImageQuery::NumLayers => { let fun_name = match class { ImageClass::Sampled { .. } | ImageClass::Depth { .. } => "textureSize", ImageClass::Storage { .. } => "imageSize", ImageClass::External => unimplemented!(), }; write!(self.out, "{fun_name}(")?; self.write_expr(image, ctx)?; // All textureSize calls requires an lod argument // except for multisampled samplers if !class.is_multisampled() { write!(self.out, ", 0")?; } write!(self.out, ")")?; if components != 1 || self.options.version.is_es() { write!(self.out, ".{}", back::COMPONENTS[components])?; } } crate::ImageQuery::NumSamples => { let fun_name = match class { ImageClass::Sampled { .. } | ImageClass::Depth { .. } => { "textureSamples" } ImageClass::Storage { .. } => "imageSamples", ImageClass::External => unimplemented!(), }; write!(self.out, "{fun_name}(")?; self.write_expr(image, ctx)?; write!(self.out, ")",)?; } } write!(self.out, ")")?; } Expression::Unary { op, expr } => { let operator_or_fn = match op { crate::UnaryOperator::Negate => "-", crate::UnaryOperator::LogicalNot => { match *ctx.resolve_type(expr, &self.module.types) { TypeInner::Vector { .. } => "not", _ => "!", } } crate::UnaryOperator::BitwiseNot => "~", }; write!(self.out, "{operator_or_fn}(")?; self.write_expr(expr, ctx)?; write!(self.out, ")")? } // `Binary` we just write `left op right`, except when dealing with // comparison operations on vectors as they are implemented with // builtin functions. // Once again we wrap everything in parentheses to avoid precedence issues Expression::Binary { mut op, left, right, } => { // Holds `Some(function_name)` if the binary operation is // implemented as a function call use crate::{BinaryOperator as Bo, ScalarKind as Sk, TypeInner as Ti}; let left_inner = ctx.resolve_type(left, &self.module.types); let right_inner = ctx.resolve_type(right, &self.module.types); let function = match (left_inner, right_inner) { (&Ti::Vector { scalar, .. }, &Ti::Vector { .. }) => match op { Bo::Less | Bo::LessEqual | Bo::Greater | Bo::GreaterEqual | Bo::Equal | Bo::NotEqual => BinaryOperation::VectorCompare, Bo::Modulo if scalar.kind == Sk::Float => BinaryOperation::Modulo, Bo::And if scalar.kind == Sk::Bool => { op = crate::BinaryOperator::LogicalAnd; BinaryOperation::VectorComponentWise } Bo::InclusiveOr if scalar.kind == Sk::Bool => { op = crate::BinaryOperator::LogicalOr; BinaryOperation::VectorComponentWise } _ => BinaryOperation::Other, }, _ => match (left_inner.scalar_kind(), right_inner.scalar_kind()) { (Some(Sk::Float), _) | (_, Some(Sk::Float)) => match op { Bo::Modulo => BinaryOperation::Modulo, _ => BinaryOperation::Other, }, (Some(Sk::Bool), Some(Sk::Bool)) => match op { Bo::InclusiveOr => { op = crate::BinaryOperator::LogicalOr; BinaryOperation::Other } Bo::And => { op = crate::BinaryOperator::LogicalAnd; BinaryOperation::Other } _ => BinaryOperation::Other, }, _ => BinaryOperation::Other, }, }; match function { BinaryOperation::VectorCompare => { let op_str = match op { Bo::Less => "lessThan(", Bo::LessEqual => "lessThanEqual(", Bo::Greater => "greaterThan(", Bo::GreaterEqual => "greaterThanEqual(", Bo::Equal => "equal(", Bo::NotEqual => "notEqual(", _ => unreachable!(), }; write!(self.out, "{op_str}")?; self.write_expr(left, ctx)?; write!(self.out, ", ")?; self.write_expr(right, ctx)?; write!(self.out, ")")?; } BinaryOperation::VectorComponentWise => { self.write_value_type(left_inner)?; write!(self.out, "(")?; let size = match *left_inner { Ti::Vector { size, .. } => size, _ => unreachable!(), }; for i in 0..size as usize { if i != 0 { write!(self.out, ", ")?; } self.write_expr(left, ctx)?; write!(self.out, ".{}", back::COMPONENTS[i])?; write!(self.out, " {} ", back::binary_operation_str(op))?; self.write_expr(right, ctx)?; write!(self.out, ".{}", back::COMPONENTS[i])?; } write!(self.out, ")")?; } // TODO: handle undefined behavior of BinaryOperator::Modulo // // sint: // if right == 0 return 0 // if left == min(type_of(left)) && right == -1 return 0 // if sign(left) == -1 || sign(right) == -1 return result as defined by WGSL // // uint: // if right == 0 return 0 // // float: // if right == 0 return ? see https://github.com/gpuweb/gpuweb/issues/2798 BinaryOperation::Modulo => { write!(self.out, "(")?; // write `e1 - e2 * trunc(e1 / e2)` self.write_expr(left, ctx)?; write!(self.out, " - ")?; self.write_expr(right, ctx)?; write!(self.out, " * ")?; write!(self.out, "trunc(")?; self.write_expr(left, ctx)?; write!(self.out, " / ")?; self.write_expr(right, ctx)?; write!(self.out, ")")?; write!(self.out, ")")?; } BinaryOperation::Other => { write!(self.out, "(")?; self.write_expr(left, ctx)?; write!(self.out, " {} ", back::binary_operation_str(op))?; self.write_expr(right, ctx)?; write!(self.out, ")")?; } } } // `Select` is written as `condition ? accept : reject` // We wrap everything in parentheses to avoid precedence issues Expression::Select { condition, accept, reject, } => { let cond_ty = ctx.resolve_type(condition, &self.module.types); let vec_select = if let TypeInner::Vector { .. } = *cond_ty { true } else { false }; // TODO: Boolean mix on desktop required GL_EXT_shader_integer_mix if vec_select { // Glsl defines that for mix when the condition is a boolean the first element // is picked if condition is false and the second if condition is true write!(self.out, "mix(")?; self.write_expr(reject, ctx)?; write!(self.out, ", ")?; self.write_expr(accept, ctx)?; write!(self.out, ", ")?; self.write_expr(condition, ctx)?; } else { write!(self.out, "(")?; self.write_expr(condition, ctx)?; write!(self.out, " ? ")?; self.write_expr(accept, ctx)?; write!(self.out, " : ")?; self.write_expr(reject, ctx)?; } write!(self.out, ")")? } // `Derivative` is a function call to a glsl provided function Expression::Derivative { axis, ctrl, expr } => { use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; let fun_name = if self.options.version.supports_derivative_control() { match (axis, ctrl) { (Axis::X, Ctrl::Coarse) => "dFdxCoarse", (Axis::X, Ctrl::Fine) => "dFdxFine", (Axis::X, Ctrl::None) => "dFdx", (Axis::Y, Ctrl::Coarse) => "dFdyCoarse", (Axis::Y, Ctrl::Fine) => "dFdyFine", (Axis::Y, Ctrl::None) => "dFdy", (Axis::Width, Ctrl::Coarse) => "fwidthCoarse", (Axis::Width, Ctrl::Fine) => "fwidthFine", (Axis::Width, Ctrl::None) => "fwidth", } } else { match axis { Axis::X => "dFdx", Axis::Y => "dFdy", Axis::Width => "fwidth", } }; write!(self.out, "{fun_name}(")?; self.write_expr(expr, ctx)?; write!(self.out, ")")? } // `Relational` is a normal function call to some glsl provided functions Expression::Relational { fun, argument } => { use crate::RelationalFunction as Rf; let fun_name = match fun { Rf::IsInf => "isinf", Rf::IsNan => "isnan", Rf::All => "all", Rf::Any => "any", }; write!(self.out, "{fun_name}(")?; self.write_expr(argument, ctx)?; write!(self.out, ")")? } Expression::Math { fun, arg, arg1, arg2, arg3, } => { use crate::MathFunction as Mf; let fun_name = match fun { // comparison Mf::Abs => "abs", Mf::Min => "min", Mf::Max => "max", Mf::Clamp => { let scalar_kind = ctx .resolve_type(arg, &self.module.types) .scalar_kind() .unwrap(); match scalar_kind { crate::ScalarKind::Float => "clamp", // Clamp is undefined if min > max. In practice this means it can use a median-of-three // instruction to determine the value. This is fine according to the WGSL spec for float // clamp, but integer clamp _must_ use min-max. As such we write out min/max. _ => { write!(self.out, "min(max(")?; self.write_expr(arg, ctx)?; write!(self.out, ", ")?; self.write_expr(arg1.unwrap(), ctx)?; write!(self.out, "), ")?; self.write_expr(arg2.unwrap(), ctx)?; write!(self.out, ")")?; return Ok(()); } } } Mf::Saturate => { write!(self.out, "clamp(")?; self.write_expr(arg, ctx)?; match *ctx.resolve_type(arg, &self.module.types) { TypeInner::Vector { size, .. } => write!( self.out, ", vec{}(0.0), vec{0}(1.0)", common::vector_size_str(size) )?, _ => write!(self.out, ", 0.0, 1.0")?, } write!(self.out, ")")?; return Ok(()); } // trigonometry Mf::Cos => "cos", Mf::Cosh => "cosh", Mf::Sin => "sin", Mf::Sinh => "sinh", Mf::Tan => "tan", Mf::Tanh => "tanh", Mf::Acos => "acos", Mf::Asin => "asin", Mf::Atan => "atan", Mf::Asinh => "asinh", Mf::Acosh => "acosh", Mf::Atanh => "atanh", Mf::Radians => "radians", Mf::Degrees => "degrees", // glsl doesn't have atan2 function // use two-argument variation of the atan function Mf::Atan2 => "atan", // decomposition Mf::Ceil => "ceil", Mf::Floor => "floor", Mf::Round => "roundEven", Mf::Fract => "fract", Mf::Trunc => "trunc", Mf::Modf => MODF_FUNCTION, Mf::Frexp => FREXP_FUNCTION, Mf::Ldexp => "ldexp", // exponent Mf::Exp => "exp", Mf::Exp2 => "exp2", Mf::Log => "log", Mf::Log2 => "log2", Mf::Pow => "pow", // geometry Mf::Dot => match *ctx.resolve_type(arg, &self.module.types) { TypeInner::Vector { scalar: crate::Scalar { kind: crate::ScalarKind::Float, .. }, .. } => "dot", TypeInner::Vector { size, .. } => { return self.write_dot_product(arg, arg1.unwrap(), size as usize, ctx) } _ => unreachable!( "Correct TypeInner for dot product should be already validated" ), }, fun @ (Mf::Dot4I8Packed | Mf::Dot4U8Packed) => { let conversion = match fun { Mf::Dot4I8Packed => "int", Mf::Dot4U8Packed => "", _ => unreachable!(), }; let arg1 = arg1.unwrap(); // Write parentheses around the dot product expression to prevent operators // with different precedences from applying earlier. write!(self.out, "(")?; for i in 0..4 { // Since `bitfieldExtract` only sign extends if the value is signed, we // need to convert the inputs to `int` in case of `Dot4I8Packed`. For // `Dot4U8Packed`, the code below only introduces parenthesis around // each factor, which aren't strictly needed because both operands are // baked, but which don't hurt either. write!(self.out, "bitfieldExtract({conversion}(")?; self.write_expr(arg, ctx)?; write!(self.out, "), {}, 8)", i * 8)?; write!(self.out, " * bitfieldExtract({conversion}(")?; self.write_expr(arg1, ctx)?; write!(self.out, "), {}, 8)", i * 8)?; if i != 3 { write!(self.out, " + ")?; } } write!(self.out, ")")?; return Ok(()); } Mf::Outer => "outerProduct", Mf::Cross => "cross", Mf::Distance => "distance", Mf::Length => "length", Mf::Normalize => "normalize", Mf::FaceForward => "faceforward", Mf::Reflect => "reflect", Mf::Refract => "refract", // computational Mf::Sign => "sign", Mf::Fma => { if self.options.version.supports_fma_function() { // Use the fma function when available "fma" } else { // No fma support. Transform the function call into an arithmetic expression write!(self.out, "(")?; self.write_expr(arg, ctx)?; write!(self.out, " * ")?; let arg1 = arg1.ok_or_else(|| Error::Custom("Missing fma arg1".to_owned()))?; self.write_expr(arg1, ctx)?; write!(self.out, " + ")?; let arg2 = arg2.ok_or_else(|| Error::Custom("Missing fma arg2".to_owned()))?; self.write_expr(arg2, ctx)?; write!(self.out, ")")?; return Ok(()); } } Mf::Mix => "mix", Mf::Step => "step", Mf::SmoothStep => "smoothstep", Mf::Sqrt => "sqrt", Mf::InverseSqrt => "inversesqrt", Mf::Inverse => "inverse", Mf::Transpose => "transpose", Mf::Determinant => "determinant", Mf::QuantizeToF16 => match *ctx.resolve_type(arg, &self.module.types) { TypeInner::Scalar { .. } => { write!(self.out, "unpackHalf2x16(packHalf2x16(vec2(")?; self.write_expr(arg, ctx)?; write!(self.out, "))).x")?; return Ok(()); } TypeInner::Vector { size: crate::VectorSize::Bi, .. } => { write!(self.out, "unpackHalf2x16(packHalf2x16(")?; self.write_expr(arg, ctx)?; write!(self.out, "))")?; return Ok(()); } TypeInner::Vector { size: crate::VectorSize::Tri, .. } => { write!(self.out, "vec3(unpackHalf2x16(packHalf2x16(")?; self.write_expr(arg, ctx)?; write!(self.out, ".xy)), unpackHalf2x16(packHalf2x16(")?; self.write_expr(arg, ctx)?; write!(self.out, ".zz)).x)")?; return Ok(()); } TypeInner::Vector { size: crate::VectorSize::Quad, .. } => { write!(self.out, "vec4(unpackHalf2x16(packHalf2x16(")?; self.write_expr(arg, ctx)?; write!(self.out, ".xy)), unpackHalf2x16(packHalf2x16(")?; self.write_expr(arg, ctx)?; write!(self.out, ".zw)))")?; return Ok(()); } _ => unreachable!( "Correct TypeInner for QuantizeToF16 should be already validated" ), }, // bits Mf::CountTrailingZeros => { match *ctx.resolve_type(arg, &self.module.types) { TypeInner::Vector { size, scalar, .. } => { let s = common::vector_size_str(size); if let crate::ScalarKind::Uint = scalar.kind { write!(self.out, "min(uvec{s}(findLSB(")?; self.write_expr(arg, ctx)?; write!(self.out, ")), uvec{s}(32u))")?; } else { write!(self.out, "ivec{s}(min(uvec{s}(findLSB(")?; self.write_expr(arg, ctx)?; write!(self.out, ")), uvec{s}(32u)))")?; } } TypeInner::Scalar(scalar) => { if let crate::ScalarKind::Uint = scalar.kind { write!(self.out, "min(uint(findLSB(")?; self.write_expr(arg, ctx)?; write!(self.out, ")), 32u)")?; } else { write!(self.out, "int(min(uint(findLSB(")?; self.write_expr(arg, ctx)?; write!(self.out, ")), 32u))")?; } } _ => unreachable!(), }; return Ok(()); } Mf::CountLeadingZeros => { if self.options.version.supports_integer_functions() { match *ctx.resolve_type(arg, &self.module.types) { TypeInner::Vector { size, scalar } => { let s = common::vector_size_str(size); if let crate::ScalarKind::Uint = scalar.kind { write!(self.out, "uvec{s}(ivec{s}(31) - findMSB(")?; self.write_expr(arg, ctx)?; write!(self.out, "))")?; } else { write!(self.out, "mix(ivec{s}(31) - findMSB(")?; self.write_expr(arg, ctx)?; write!(self.out, "), ivec{s}(0), lessThan(")?; self.write_expr(arg, ctx)?; write!(self.out, ", ivec{s}(0)))")?; } } TypeInner::Scalar(scalar) => { if let crate::ScalarKind::Uint = scalar.kind { write!(self.out, "uint(31 - findMSB(")?; } else { write!(self.out, "(")?; self.write_expr(arg, ctx)?; write!(self.out, " < 0 ? 0 : 31 - findMSB(")?; } self.write_expr(arg, ctx)?; write!(self.out, "))")?; } _ => unreachable!(), }; } else { match *ctx.resolve_type(arg, &self.module.types) { TypeInner::Vector { size, scalar } => { let s = common::vector_size_str(size); if let crate::ScalarKind::Uint = scalar.kind { write!(self.out, "uvec{s}(")?; write!(self.out, "vec{s}(31.0) - floor(log2(vec{s}(")?; self.write_expr(arg, ctx)?; write!(self.out, ") + 0.5)))")?; } else { write!(self.out, "ivec{s}(")?; write!(self.out, "mix(vec{s}(31.0) - floor(log2(vec{s}(")?; self.write_expr(arg, ctx)?; write!(self.out, ") + 0.5)), ")?; write!(self.out, "vec{s}(0.0), lessThan(")?; self.write_expr(arg, ctx)?; write!(self.out, ", ivec{s}(0u))))")?; } } TypeInner::Scalar(scalar) => { if let crate::ScalarKind::Uint = scalar.kind { write!(self.out, "uint(31.0 - floor(log2(float(")?; self.write_expr(arg, ctx)?; write!(self.out, ") + 0.5)))")?; } else { write!(self.out, "(")?; self.write_expr(arg, ctx)?; write!(self.out, " < 0 ? 0 : int(")?; write!(self.out, "31.0 - floor(log2(float(")?; self.write_expr(arg, ctx)?; write!(self.out, ") + 0.5))))")?; } } _ => unreachable!(), }; } return Ok(()); } Mf::CountOneBits => "bitCount", Mf::ReverseBits => "bitfieldReverse", Mf::ExtractBits => { // The behavior of ExtractBits is undefined when offset + count > bit_width. We need // to first sanitize the offset and count first. If we don't do this, AMD and Intel chips // will return out-of-spec values if the extracted range is not within the bit width. // // This encodes the exact formula specified by the wgsl spec, without temporary values: // https://gpuweb.github.io/gpuweb/wgsl/#extractBits-unsigned-builtin // // w = sizeof(x) * 8 // o = min(offset, w) // c = min(count, w - o) // // bitfieldExtract(x, o, c) // // extract_bits(e, min(offset, w), min(count, w - min(offset, w)))) let scalar_bits = ctx .resolve_type(arg, &self.module.types) .scalar_width() .unwrap() * 8; write!(self.out, "bitfieldExtract(")?; self.write_expr(arg, ctx)?; write!(self.out, ", int(min(")?; self.write_expr(arg1.unwrap(), ctx)?; write!(self.out, ", {scalar_bits}u)), int(min(",)?; self.write_expr(arg2.unwrap(), ctx)?; write!(self.out, ", {scalar_bits}u - min(")?; self.write_expr(arg1.unwrap(), ctx)?; write!(self.out, ", {scalar_bits}u))))")?; return Ok(()); } Mf::InsertBits => { // InsertBits has the same considerations as ExtractBits above let scalar_bits = ctx .resolve_type(arg, &self.module.types) .scalar_width() .unwrap() * 8; write!(self.out, "bitfieldInsert(")?; self.write_expr(arg, ctx)?; write!(self.out, ", ")?; self.write_expr(arg1.unwrap(), ctx)?; write!(self.out, ", int(min(")?; self.write_expr(arg2.unwrap(), ctx)?; write!(self.out, ", {scalar_bits}u)), int(min(",)?; self.write_expr(arg3.unwrap(), ctx)?; write!(self.out, ", {scalar_bits}u - min(")?; self.write_expr(arg2.unwrap(), ctx)?; write!(self.out, ", {scalar_bits}u))))")?; return Ok(()); } Mf::FirstTrailingBit => "findLSB", Mf::FirstLeadingBit => "findMSB", // data packing Mf::Pack4x8snorm => { if self.options.version.supports_pack_unpack_4x8() { "packSnorm4x8" } else { // polyfill should go here. Needs a corresponding entry in `need_bake_expression` return Err(Error::UnsupportedExternal("packSnorm4x8".into())); } } Mf::Pack4x8unorm => { if self.options.version.supports_pack_unpack_4x8() { "packUnorm4x8" } else { return Err(Error::UnsupportedExternal("packUnorm4x8".to_owned())); } } Mf::Pack2x16snorm => { if self.options.version.supports_pack_unpack_snorm_2x16() { "packSnorm2x16" } else { return Err(Error::UnsupportedExternal("packSnorm2x16".to_owned())); } } Mf::Pack2x16unorm => { if self.options.version.supports_pack_unpack_unorm_2x16() { "packUnorm2x16" } else { return Err(Error::UnsupportedExternal("packUnorm2x16".to_owned())); } } Mf::Pack2x16float => { if self.options.version.supports_pack_unpack_half_2x16() { "packHalf2x16" } else { return Err(Error::UnsupportedExternal("packHalf2x16".to_owned())); } } fun @ (Mf::Pack4xI8 | Mf::Pack4xU8 | Mf::Pack4xI8Clamp | Mf::Pack4xU8Clamp) => { let was_signed = matches!(fun, Mf::Pack4xI8 | Mf::Pack4xI8Clamp); let clamp_bounds = match fun { Mf::Pack4xI8Clamp => Some(("-128", "127")), Mf::Pack4xU8Clamp => Some(("0", "255")), _ => None, }; let const_suffix = if was_signed { "" } else { "u" }; if was_signed { write!(self.out, "uint(")?; } let write_arg = |this: &mut Self| -> BackendResult { if let Some((min, max)) = clamp_bounds { write!(this.out, "clamp(")?; this.write_expr(arg, ctx)?; write!(this.out, ", {min}{const_suffix}, {max}{const_suffix})")?; } else { this.write_expr(arg, ctx)?; } Ok(()) }; write!(self.out, "(")?; write_arg(self)?; write!(self.out, "[0] & 0xFF{const_suffix}) | ((")?; write_arg(self)?; write!(self.out, "[1] & 0xFF{const_suffix}) << 8) | ((")?; write_arg(self)?; write!(self.out, "[2] & 0xFF{const_suffix}) << 16) | ((")?; write_arg(self)?; write!(self.out, "[3] & 0xFF{const_suffix}) << 24)")?; if was_signed { write!(self.out, ")")?; } return Ok(()); } // data unpacking Mf::Unpack2x16float => { if self.options.version.supports_pack_unpack_half_2x16() { "unpackHalf2x16" } else { return Err(Error::UnsupportedExternal("unpackHalf2x16".into())); } } Mf::Unpack2x16snorm => { if self.options.version.supports_pack_unpack_snorm_2x16() { "unpackSnorm2x16" } else { let scale = 32767; write!(self.out, "(vec2(ivec2(")?; self.write_expr(arg, ctx)?; write!(self.out, " << 16, ")?; self.write_expr(arg, ctx)?; write!(self.out, ") >> 16) / {scale}.0)")?; return Ok(()); } } Mf::Unpack2x16unorm => { if self.options.version.supports_pack_unpack_unorm_2x16() { "unpackUnorm2x16" } else { let scale = 65535; write!(self.out, "(vec2(")?; self.write_expr(arg, ctx)?; write!(self.out, " & 0xFFFFu, ")?; self.write_expr(arg, ctx)?; write!(self.out, " >> 16) / {scale}.0)")?; return Ok(()); } } Mf::Unpack4x8snorm => { if self.options.version.supports_pack_unpack_4x8() { "unpackSnorm4x8" } else { let scale = 127; write!(self.out, "(vec4(ivec4(")?; self.write_expr(arg, ctx)?; write!(self.out, " << 24, ")?; self.write_expr(arg, ctx)?; write!(self.out, " << 16, ")?; self.write_expr(arg, ctx)?; write!(self.out, " << 8, ")?; self.write_expr(arg, ctx)?; write!(self.out, ") >> 24) / {scale}.0)")?; return Ok(()); } } Mf::Unpack4x8unorm => { if self.options.version.supports_pack_unpack_4x8() { "unpackUnorm4x8" } else { let scale = 255; write!(self.out, "(vec4(")?; self.write_expr(arg, ctx)?; write!(self.out, " & 0xFFu, ")?; self.write_expr(arg, ctx)?; write!(self.out, " >> 8 & 0xFFu, ")?; self.write_expr(arg, ctx)?; write!(self.out, " >> 16 & 0xFFu, ")?; self.write_expr(arg, ctx)?; write!(self.out, " >> 24) / {scale}.0)")?; return Ok(()); } } fun @ (Mf::Unpack4xI8 | Mf::Unpack4xU8) => { let sign_prefix = match fun { Mf::Unpack4xI8 => 'i', Mf::Unpack4xU8 => 'u', _ => unreachable!(), }; write!(self.out, "{sign_prefix}vec4(")?; for i in 0..4 { write!(self.out, "bitfieldExtract(")?; // Since bitfieldExtract only sign extends if the value is signed, this // cast is needed match fun { Mf::Unpack4xI8 => { write!(self.out, "int(")?; self.write_expr(arg, ctx)?; write!(self.out, ")")?; } Mf::Unpack4xU8 => self.write_expr(arg, ctx)?, _ => unreachable!(), }; write!(self.out, ", {}, 8)", i * 8)?; if i != 3 { write!(self.out, ", ")?; } } write!(self.out, ")")?; return Ok(()); } }; let extract_bits = fun == Mf::ExtractBits; let insert_bits = fun == Mf::InsertBits; // Some GLSL functions always return signed integers (like findMSB), // so they need to be cast to uint if the argument is also an uint. let ret_might_need_int_to_uint = matches!( fun, Mf::FirstTrailingBit | Mf::FirstLeadingBit | Mf::CountOneBits | Mf::Abs ); // Some GLSL functions only accept signed integers (like abs), // so they need their argument cast from uint to int. let arg_might_need_uint_to_int = matches!(fun, Mf::Abs); // Check if the argument is an unsigned integer and return the vector size // in case it's a vector let maybe_uint_size = match *ctx.resolve_type(arg, &self.module.types) { TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Uint, .. }) => Some(None), TypeInner::Vector { scalar: crate::Scalar { kind: crate::ScalarKind::Uint, .. }, size, } => Some(Some(size)), _ => None, }; // Cast to uint if the function needs it if ret_might_need_int_to_uint { if let Some(maybe_size) = maybe_uint_size { match maybe_size { Some(size) => write!(self.out, "uvec{}(", size as u8)?, None => write!(self.out, "uint(")?, } } } write!(self.out, "{fun_name}(")?; // Cast to int if the function needs it if arg_might_need_uint_to_int { if let Some(maybe_size) = maybe_uint_size { match maybe_size { Some(size) => write!(self.out, "ivec{}(", size as u8)?, None => write!(self.out, "int(")?, } } } self.write_expr(arg, ctx)?; // Close the cast from uint to int if arg_might_need_uint_to_int && maybe_uint_size.is_some() { write!(self.out, ")")? } if let Some(arg) = arg1 { write!(self.out, ", ")?; if extract_bits { write!(self.out, "int(")?; self.write_expr(arg, ctx)?; write!(self.out, ")")?; } else { self.write_expr(arg, ctx)?; } } if let Some(arg) = arg2 { write!(self.out, ", ")?; if extract_bits || insert_bits { write!(self.out, "int(")?; self.write_expr(arg, ctx)?; write!(self.out, ")")?; } else { self.write_expr(arg, ctx)?; } } if let Some(arg) = arg3 { write!(self.out, ", ")?; if insert_bits { write!(self.out, "int(")?; self.write_expr(arg, ctx)?; write!(self.out, ")")?; } else { self.write_expr(arg, ctx)?; } } write!(self.out, ")")?; // Close the cast from int to uint if ret_might_need_int_to_uint && maybe_uint_size.is_some() { write!(self.out, ")")? } } // `As` is always a call. // If `convert` is true the function name is the type // Else the function name is one of the glsl provided bitcast functions Expression::As { expr, kind: target_kind, convert, } => { let inner = ctx.resolve_type(expr, &self.module.types); match convert { Some(width) => { // this is similar to `write_type`, but with the target kind let scalar = glsl_scalar(crate::Scalar { kind: target_kind, width, })?; match *inner { TypeInner::Matrix { columns, rows, .. } => write!( self.out, "{}mat{}x{}", scalar.prefix, columns as u8, rows as u8 )?, TypeInner::Vector { size, .. } => { write!(self.out, "{}vec{}", scalar.prefix, size as u8)? } _ => write!(self.out, "{}", scalar.full)?, } write!(self.out, "(")?; self.write_expr(expr, ctx)?; write!(self.out, ")")? } None => { use crate::ScalarKind as Sk; let target_vector_type = match *inner { TypeInner::Vector { size, scalar } => Some(TypeInner::Vector { size, scalar: crate::Scalar { kind: target_kind, width: scalar.width, }, }), _ => None, }; let source_kind = inner.scalar_kind().unwrap(); match (source_kind, target_kind, target_vector_type) { // No conversion needed (Sk::Sint, Sk::Sint, _) | (Sk::Uint, Sk::Uint, _) | (Sk::Float, Sk::Float, _) | (Sk::Bool, Sk::Bool, _) => { self.write_expr(expr, ctx)?; return Ok(()); } // Cast to/from floats (Sk::Float, Sk::Sint, _) => write!(self.out, "floatBitsToInt")?, (Sk::Float, Sk::Uint, _) => write!(self.out, "floatBitsToUint")?, (Sk::Sint, Sk::Float, _) => write!(self.out, "intBitsToFloat")?, (Sk::Uint, Sk::Float, _) => write!(self.out, "uintBitsToFloat")?, // Cast between vector types (_, _, Some(vector)) => { self.write_value_type(&vector)?; } // There is no way to bitcast between Uint/Sint in glsl. Use constructor conversion (Sk::Uint | Sk::Bool, Sk::Sint, None) => write!(self.out, "int")?, (Sk::Sint | Sk::Bool, Sk::Uint, None) => write!(self.out, "uint")?, (Sk::Bool, Sk::Float, None) => write!(self.out, "float")?, (Sk::Sint | Sk::Uint | Sk::Float, Sk::Bool, None) => { write!(self.out, "bool")? } (Sk::AbstractInt | Sk::AbstractFloat, _, _) | (_, Sk::AbstractInt | Sk::AbstractFloat, _) => unreachable!(), }; write!(self.out, "(")?; self.write_expr(expr, ctx)?; write!(self.out, ")")?; } } } // These expressions never show up in `Emit`. Expression::CallResult(_) | Expression::AtomicResult { .. } | Expression::RayQueryProceedResult | Expression::WorkGroupUniformLoadResult { .. } | Expression::SubgroupOperationResult { .. } | Expression::SubgroupBallotResult => unreachable!(), // `ArrayLength` is written as `expr.length()` and we convert it to a uint Expression::ArrayLength(expr) => { write!(self.out, "uint(")?; self.write_expr(expr, ctx)?; write!(self.out, ".length())")? } // not supported yet Expression::RayQueryGetIntersection { .. } | Expression::RayQueryVertexPositions { .. } | Expression::CooperativeLoad { .. } | Expression::CooperativeMultiplyAdd { .. } => unreachable!(), } Ok(()) } /// Helper function to write the local holding the clamped lod fn write_clamped_lod( &mut self, ctx: &back::FunctionCtx, expr: Handle, image: Handle, level_expr: Handle, ) -> Result<(), Error> { // Define our local and start a call to `clamp` write!( self.out, "int {}{} = clamp(", Baked(expr), CLAMPED_LOD_SUFFIX )?; // Write the lod that will be clamped self.write_expr(level_expr, ctx)?; // Set the min value to 0 and start a call to `textureQueryLevels` to get // the maximum value write!(self.out, ", 0, textureQueryLevels(")?; // Write the target image as an argument to `textureQueryLevels` self.write_expr(image, ctx)?; // Close the call to `textureQueryLevels` subtract 1 from it since // the lod argument is 0 based, close the `clamp` call and end the // local declaration statement. writeln!(self.out, ") - 1);")?; Ok(()) } // Helper method used to retrieve how many elements a coordinate vector // for the images operations need. fn get_coordinate_vector_size(&self, dim: crate::ImageDimension, arrayed: bool) -> u8 { // openGL es doesn't have 1D images so we need workaround it let tex_1d_hack = dim == crate::ImageDimension::D1 && self.options.version.is_es(); // Get how many components the coordinate vector needs for the dimensions only let tex_coord_size = match dim { crate::ImageDimension::D1 => 1, crate::ImageDimension::D2 => 2, crate::ImageDimension::D3 => 3, crate::ImageDimension::Cube => 2, }; // Calculate the true size of the coordinate vector by adding 1 for arrayed images // and another 1 if we need to workaround 1D images by making them 2D tex_coord_size + tex_1d_hack as u8 + arrayed as u8 } /// Helper method to write the coordinate vector for image operations fn write_texture_coord( &mut self, ctx: &back::FunctionCtx, vector_size: u8, coordinate: Handle, array_index: Option>, // Emulate 1D images as 2D for profiles that don't support it (glsl es) tex_1d_hack: bool, ) -> Result<(), Error> { match array_index { // If the image needs an array indice we need to add it to the end of our // coordinate vector, to do so we will use the `ivec(ivec, scalar)` // constructor notation (NOTE: the inner `ivec` can also be a scalar, this // is important for 1D arrayed images). Some(layer_expr) => { write!(self.out, "ivec{vector_size}(")?; self.write_expr(coordinate, ctx)?; write!(self.out, ", ")?; // If we are replacing sampler1D with sampler2D we also need // to add another zero to the coordinates vector for the y component if tex_1d_hack { write!(self.out, "0, ")?; } self.write_expr(layer_expr, ctx)?; write!(self.out, ")")?; } // Otherwise write just the expression (and the 1D hack if needed) None => { let uvec_size = match *ctx.resolve_type(coordinate, &self.module.types) { TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Uint, .. }) => Some(None), TypeInner::Vector { size, scalar: crate::Scalar { kind: crate::ScalarKind::Uint, .. }, } => Some(Some(size as u32)), _ => None, }; if tex_1d_hack { write!(self.out, "ivec2(")?; } else if uvec_size.is_some() { match uvec_size { Some(None) => write!(self.out, "int(")?, Some(Some(size)) => write!(self.out, "ivec{size}(")?, _ => {} } } self.write_expr(coordinate, ctx)?; if tex_1d_hack { write!(self.out, ", 0)")?; } else if uvec_size.is_some() { write!(self.out, ")")?; } } } Ok(()) } /// Helper method to write the `ImageStore` statement fn write_image_store( &mut self, ctx: &back::FunctionCtx, image: Handle, coordinate: Handle, array_index: Option>, value: Handle, ) -> Result<(), Error> { use crate::ImageDimension as IDim; // NOTE: openGL requires that `imageStore`s have no effects when the texel is invalid // so we don't need to generate bounds checks (OpenGL 4.2 Core §3.9.20) // This will only panic if the module is invalid let dim = match *ctx.resolve_type(image, &self.module.types) { TypeInner::Image { dim, .. } => dim, _ => unreachable!(), }; // Begin our call to `imageStore` write!(self.out, "imageStore(")?; self.write_expr(image, ctx)?; // Separate the image argument from the coordinates write!(self.out, ", ")?; // openGL es doesn't have 1D images so we need workaround it let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); // Write the coordinate vector self.write_texture_coord( ctx, // Get the size of the coordinate vector self.get_coordinate_vector_size(dim, array_index.is_some()), coordinate, array_index, tex_1d_hack, )?; // Separate the coordinate from the value to write and write the expression // of the value to write. write!(self.out, ", ")?; self.write_expr(value, ctx)?; // End the call to `imageStore` and the statement. writeln!(self.out, ");")?; Ok(()) } /// Helper method to write the `ImageAtomic` statement fn write_image_atomic( &mut self, ctx: &back::FunctionCtx, image: Handle, coordinate: Handle, array_index: Option>, fun: crate::AtomicFunction, value: Handle, ) -> Result<(), Error> { use crate::ImageDimension as IDim; // NOTE: openGL requires that `imageAtomic`s have no effects when the texel is invalid // so we don't need to generate bounds checks (OpenGL 4.2 Core §3.9.20) // This will only panic if the module is invalid let dim = match *ctx.resolve_type(image, &self.module.types) { TypeInner::Image { dim, .. } => dim, _ => unreachable!(), }; // Begin our call to `imageAtomic` let fun_str = fun.to_glsl(); write!(self.out, "imageAtomic{fun_str}(")?; self.write_expr(image, ctx)?; // Separate the image argument from the coordinates write!(self.out, ", ")?; // openGL es doesn't have 1D images so we need workaround it let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); // Write the coordinate vector self.write_texture_coord( ctx, // Get the size of the coordinate vector self.get_coordinate_vector_size(dim, false), coordinate, array_index, tex_1d_hack, )?; // Separate the coordinate from the value to write and write the expression // of the value to write. write!(self.out, ", ")?; self.write_expr(value, ctx)?; // End the call to `imageAtomic` and the statement. writeln!(self.out, ");")?; Ok(()) } /// Helper method for writing an `ImageLoad` expression. #[allow(clippy::too_many_arguments)] fn write_image_load( &mut self, handle: Handle, ctx: &back::FunctionCtx, image: Handle, coordinate: Handle, array_index: Option>, sample: Option>, level: Option>, ) -> Result<(), Error> { use crate::ImageDimension as IDim; // `ImageLoad` is a bit complicated. // There are two functions one for sampled // images another for storage images, the former uses `texelFetch` and the // latter uses `imageLoad`. // // Furthermore we have `level` which is always `Some` for sampled images // and `None` for storage images, so we end up with two functions: // - `texelFetch(image, coordinate, level)` for sampled images // - `imageLoad(image, coordinate)` for storage images // // Finally we also have to consider bounds checking, for storage images // this is easy since openGL requires that invalid texels always return // 0, for sampled images we need to either verify that all arguments are // in bounds (`ReadZeroSkipWrite`) or make them a valid texel (`Restrict`). // This will only panic if the module is invalid let (dim, class) = match *ctx.resolve_type(image, &self.module.types) { TypeInner::Image { dim, arrayed: _, class, } => (dim, class), _ => unreachable!(), }; // Get the name of the function to be used for the load operation // and the policy to be used with it. let (fun_name, policy) = match class { // Sampled images inherit the policy from the user passed policies crate::ImageClass::Sampled { .. } => ("texelFetch", self.policies.image_load), crate::ImageClass::Storage { .. } => { // OpenGL ES 3.1 mentions in Chapter "8.22 Texture Image Loads and Stores" that: // "Invalid image loads will return a vector where the value of R, G, and B components // is 0 and the value of the A component is undefined." // // OpenGL 4.2 Core mentions in Chapter "3.9.20 Texture Image Loads and Stores" that: // "Invalid image loads will return zero." // // So, we only inject bounds checks for ES let policy = if self.options.version.is_es() { self.policies.image_load } else { proc::BoundsCheckPolicy::Unchecked }; ("imageLoad", policy) } // TODO: Is there even a function for this? crate::ImageClass::Depth { multi: _ } => { return Err(Error::Custom( "WGSL `textureLoad` from depth textures is not supported in GLSL".to_string(), )) } crate::ImageClass::External => unimplemented!(), }; // openGL es doesn't have 1D images so we need workaround it let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); // Get the size of the coordinate vector let vector_size = self.get_coordinate_vector_size(dim, array_index.is_some()); if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy { // To write the bounds checks for `ReadZeroSkipWrite` we will use a // ternary operator since we are in the middle of an expression and // need to return a value. // // NOTE: glsl does short circuit when evaluating logical // expressions so we can be sure that after we test a // condition it will be true for the next ones // Write parentheses around the ternary operator to prevent problems with // expressions emitted before or after it having more precedence write!(self.out, "(",)?; // The lod check needs to precede the size check since we need // to use the lod to get the size of the image at that level. if let Some(level_expr) = level { self.write_expr(level_expr, ctx)?; write!(self.out, " < textureQueryLevels(",)?; self.write_expr(image, ctx)?; // Chain the next check write!(self.out, ") && ")?; } // Check that the sample arguments doesn't exceed the number of samples if let Some(sample_expr) = sample { self.write_expr(sample_expr, ctx)?; write!(self.out, " < textureSamples(",)?; self.write_expr(image, ctx)?; // Chain the next check write!(self.out, ") && ")?; } // We now need to write the size checks for the coordinates and array index // first we write the comparison function in case the image is 1D non arrayed // (and no 1D to 2D hack was needed) we are comparing scalars so the less than // operator will suffice, but otherwise we'll be comparing two vectors so we'll // need to use the `lessThan` function but it returns a vector of booleans (one // for each comparison) so we need to fold it all in one scalar boolean, since // we want all comparisons to pass we use the `all` function which will only // return `true` if all the elements of the boolean vector are also `true`. // // So we'll end with one of the following forms // - `coord < textureSize(image, lod)` for 1D images // - `all(lessThan(coord, textureSize(image, lod)))` for normal images // - `all(lessThan(ivec(coord, array_index), textureSize(image, lod)))` // for arrayed images // - `all(lessThan(coord, textureSize(image)))` for multi sampled images if vector_size != 1 { write!(self.out, "all(lessThan(")?; } // Write the coordinate vector self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?; if vector_size != 1 { // If we used the `lessThan` function we need to separate the // coordinates from the image size. write!(self.out, ", ")?; } else { // If we didn't use it (ie. 1D images) we perform the comparison // using the less than operator. write!(self.out, " < ")?; } // Call `textureSize` to get our image size write!(self.out, "textureSize(")?; self.write_expr(image, ctx)?; // `textureSize` uses the lod as a second argument for mipmapped images if let Some(level_expr) = level { // Separate the image from the lod write!(self.out, ", ")?; self.write_expr(level_expr, ctx)?; } // Close the `textureSize` call write!(self.out, ")")?; if vector_size != 1 { // Close the `all` and `lessThan` calls write!(self.out, "))")?; } // Finally end the condition part of the ternary operator write!(self.out, " ? ")?; } // Begin the call to the function used to load the texel write!(self.out, "{fun_name}(")?; self.write_expr(image, ctx)?; write!(self.out, ", ")?; // If we are using `Restrict` bounds checking we need to pass valid texel // coordinates, to do so we use the `clamp` function to get a value between // 0 and the image size - 1 (indexing begins at 0) if let proc::BoundsCheckPolicy::Restrict = policy { write!(self.out, "clamp(")?; } // Write the coordinate vector self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?; // If we are using `Restrict` bounds checking we need to write the rest of the // clamp we initiated before writing the coordinates. if let proc::BoundsCheckPolicy::Restrict = policy { // Write the min value 0 if vector_size == 1 { write!(self.out, ", 0")?; } else { write!(self.out, ", ivec{vector_size}(0)")?; } // Start the `textureSize` call to use as the max value. write!(self.out, ", textureSize(")?; self.write_expr(image, ctx)?; // If the image is mipmapped we need to add the lod argument to the // `textureSize` call, but this needs to be the clamped lod, this should // have been generated earlier and put in a local. if class.is_mipmapped() { write!(self.out, ", {}{}", Baked(handle), CLAMPED_LOD_SUFFIX)?; } // Close the `textureSize` call write!(self.out, ")")?; // Subtract 1 from the `textureSize` call since the coordinates are zero based. if vector_size == 1 { write!(self.out, " - 1")?; } else { write!(self.out, " - ivec{vector_size}(1)")?; } // Close the `clamp` call write!(self.out, ")")?; // Add the clamped lod (if present) as the second argument to the // image load function. if level.is_some() { write!(self.out, ", {}{}", Baked(handle), CLAMPED_LOD_SUFFIX)?; } // If a sample argument is needed we need to clamp it between 0 and // the number of samples the image has. if let Some(sample_expr) = sample { write!(self.out, ", clamp(")?; self.write_expr(sample_expr, ctx)?; // Set the min value to 0 and start the call to `textureSamples` write!(self.out, ", 0, textureSamples(")?; self.write_expr(image, ctx)?; // Close the `textureSamples` call, subtract 1 from it since the sample // argument is zero based, and close the `clamp` call writeln!(self.out, ") - 1)")?; } } else if let Some(sample_or_level) = sample.or(level) { // GLSL only support SInt on this field while WGSL support also UInt let cast_to_int = matches!( *ctx.resolve_type(sample_or_level, &self.module.types), TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Uint, .. }) ); // If no bounds checking is need just add the sample or level argument // after the coordinates write!(self.out, ", ")?; if cast_to_int { write!(self.out, "int(")?; } self.write_expr(sample_or_level, ctx)?; if cast_to_int { write!(self.out, ")")?; } } // Close the image load function. write!(self.out, ")")?; // If we were using the `ReadZeroSkipWrite` policy we need to end the first branch // (which is taken if the condition is `true`) with a colon (`:`) and write the // second branch which is just a 0 value. if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy { // Get the kind of the output value. let kind = match class { // Only sampled images can reach here since storage images // don't need bounds checks and depth images aren't implemented crate::ImageClass::Sampled { kind, .. } => kind, _ => unreachable!(), }; // End the first branch write!(self.out, " : ")?; // Write the 0 value write!( self.out, "{}vec4(", glsl_scalar(crate::Scalar { kind, width: 4 })?.prefix, )?; self.write_zero_init_scalar(kind)?; // Close the zero value constructor write!(self.out, ")")?; // Close the parentheses surrounding our ternary write!(self.out, ")")?; } Ok(()) } fn write_named_expr( &mut self, handle: Handle, name: String, // The expression which is being named. // Generally, this is the same as handle, except in WorkGroupUniformLoad named: Handle, ctx: &back::FunctionCtx, ) -> BackendResult { match ctx.info[named].ty { proc::TypeResolution::Handle(ty_handle) => match self.module.types[ty_handle].inner { TypeInner::Struct { .. } => { let ty_name = &self.names[&NameKey::Type(ty_handle)]; write!(self.out, "{ty_name}")?; } _ => { self.write_type(ty_handle)?; } }, proc::TypeResolution::Value(ref inner) => { self.write_value_type(inner)?; } } let resolved = ctx.resolve_type(named, &self.module.types); write!(self.out, " {name}")?; if let TypeInner::Array { base, size, .. } = *resolved { self.write_array_size(base, size)?; } write!(self.out, " = ")?; self.write_expr(handle, ctx)?; writeln!(self.out, ";")?; self.named_expressions.insert(named, name); Ok(()) } /// Helper function that write string with default zero initialization for supported types fn write_zero_init_value(&mut self, ty: Handle) -> BackendResult { let inner = &self.module.types[ty].inner; match *inner { TypeInner::Scalar(scalar) | TypeInner::Atomic(scalar) => { self.write_zero_init_scalar(scalar.kind)?; } TypeInner::Vector { scalar, .. } => { self.write_value_type(inner)?; write!(self.out, "(")?; self.write_zero_init_scalar(scalar.kind)?; write!(self.out, ")")?; } TypeInner::Matrix { .. } => { self.write_value_type(inner)?; write!(self.out, "(")?; self.write_zero_init_scalar(crate::ScalarKind::Float)?; write!(self.out, ")")?; } TypeInner::Array { base, size, .. } => { let count = match size.resolve(self.module.to_ctx())? { proc::IndexableLength::Known(count) => count, proc::IndexableLength::Dynamic => return Ok(()), }; self.write_type(base)?; self.write_array_size(base, size)?; write!(self.out, "(")?; for _ in 1..count { self.write_zero_init_value(base)?; write!(self.out, ", ")?; } // write last parameter without comma and space self.write_zero_init_value(base)?; write!(self.out, ")")?; } TypeInner::Struct { ref members, .. } => { let name = &self.names[&NameKey::Type(ty)]; write!(self.out, "{name}(")?; for (index, member) in members.iter().enumerate() { if index != 0 { write!(self.out, ", ")?; } self.write_zero_init_value(member.ty)?; } write!(self.out, ")")?; } _ => unreachable!(), } Ok(()) } /// Helper function that write string with zero initialization for scalar fn write_zero_init_scalar(&mut self, kind: crate::ScalarKind) -> BackendResult { match kind { crate::ScalarKind::Bool => write!(self.out, "false")?, crate::ScalarKind::Uint => write!(self.out, "0u")?, crate::ScalarKind::Float => write!(self.out, "0.0")?, crate::ScalarKind::Sint => write!(self.out, "0")?, crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => { return Err(Error::Custom( "Abstract types should not appear in IR presented to backends".to_string(), )) } } Ok(()) } /// Issue a control barrier. fn write_control_barrier( &mut self, flags: crate::Barrier, level: back::Level, ) -> BackendResult { self.write_memory_barrier(flags, level)?; writeln!(self.out, "{level}barrier();")?; Ok(()) } /// Issue a memory barrier. fn write_memory_barrier(&mut self, flags: crate::Barrier, level: back::Level) -> BackendResult { if flags.contains(crate::Barrier::STORAGE) { writeln!(self.out, "{level}memoryBarrierBuffer();")?; } if flags.contains(crate::Barrier::WORK_GROUP) { writeln!(self.out, "{level}memoryBarrierShared();")?; } if flags.contains(crate::Barrier::SUB_GROUP) { writeln!(self.out, "{level}subgroupMemoryBarrier();")?; } if flags.contains(crate::Barrier::TEXTURE) { writeln!(self.out, "{level}memoryBarrierImage();")?; } Ok(()) } /// Helper function that return the glsl storage access string of [`StorageAccess`](crate::StorageAccess) /// /// glsl allows adding both `readonly` and `writeonly` but this means that /// they can only be used to query information about the resource which isn't what /// we want here so when storage access is both `LOAD` and `STORE` add no modifiers fn write_storage_access(&mut self, storage_access: crate::StorageAccess) -> BackendResult { if storage_access.contains(crate::StorageAccess::ATOMIC) { return Ok(()); } if !storage_access.contains(crate::StorageAccess::STORE) { write!(self.out, "readonly ")?; } if !storage_access.contains(crate::StorageAccess::LOAD) { write!(self.out, "writeonly ")?; } Ok(()) } /// Helper method used to produce the reflection info that's returned to the user fn collect_reflection_info(&mut self) -> Result { let info = self.info.get_entry_point(self.entry_point_idx as usize); let mut texture_mapping = crate::FastHashMap::default(); let mut uniforms = crate::FastHashMap::default(); for sampling in info.sampling_set.iter() { let tex_name = self.reflection_names_globals[&sampling.image].clone(); match texture_mapping.entry(tex_name) { hash_map::Entry::Vacant(v) => { v.insert(TextureMapping { texture: sampling.image, sampler: Some(sampling.sampler), }); } hash_map::Entry::Occupied(e) => { if e.get().sampler != Some(sampling.sampler) { log::error!("Conflicting samplers for {}", e.key()); return Err(Error::ImageMultipleSamplers); } } } } let mut immediates_info = None; for (handle, var) in self.module.global_variables.iter() { if info[handle].is_empty() { continue; } match self.module.types[var.ty].inner { TypeInner::Image { .. } => { let tex_name = self.reflection_names_globals[&handle].clone(); match texture_mapping.entry(tex_name) { hash_map::Entry::Vacant(v) => { v.insert(TextureMapping { texture: handle, sampler: None, }); } hash_map::Entry::Occupied(_) => { // already used with a sampler, do nothing } } } _ => match var.space { crate::AddressSpace::Uniform | crate::AddressSpace::Storage { .. } => { let name = self.reflection_names_globals[&handle].clone(); uniforms.insert(handle, name); } crate::AddressSpace::Immediate => { let name = self.reflection_names_globals[&handle].clone(); immediates_info = Some((name, var.ty)); } _ => (), }, } } let mut immediates_segments = Vec::new(); let mut immediates_items = vec![]; if let Some((name, ty)) = immediates_info { // We don't have a layouter available to us, so we need to create one. // // This is potentially a bit wasteful, but the set of types in the program // shouldn't be too large. let mut layouter = proc::Layouter::default(); layouter.update(self.module.to_ctx()).unwrap(); // We start with the name of the binding itself. immediates_segments.push(name); // We then recursively collect all the uniform fields of the immediate data. self.collect_immediates_items( ty, &mut immediates_segments, &layouter, &mut 0, &mut immediates_items, ); } Ok(ReflectionInfo { texture_mapping, uniforms, varying: mem::take(&mut self.varying), immediates_items, clip_distance_count: self.clip_distance_count, }) } fn collect_immediates_items( &mut self, ty: Handle, segments: &mut Vec, layouter: &proc::Layouter, offset: &mut u32, items: &mut Vec, ) { // At this point in the recursion, `segments` contains the path // needed to access `ty` from the root. let layout = &layouter[ty]; *offset = layout.alignment.round_up(*offset); match self.module.types[ty].inner { // All these types map directly to GL uniforms. TypeInner::Scalar { .. } | TypeInner::Vector { .. } | TypeInner::Matrix { .. } => { // Build the full name, by combining all current segments. let name: String = segments.iter().map(String::as_str).collect(); items.push(ImmediateItem { access_path: name, offset: *offset, ty, }); *offset += layout.size; } // Arrays are recursed into. TypeInner::Array { base, size, .. } => { let crate::ArraySize::Constant(count) = size else { unreachable!("Cannot have dynamic arrays in immediates"); }; for i in 0..count.get() { // Add the array accessor and recurse. segments.push(format!("[{i}]")); self.collect_immediates_items(base, segments, layouter, offset, items); segments.pop(); } // Ensure the stride is kept by rounding up to the alignment. *offset = layout.alignment.round_up(*offset) } TypeInner::Struct { ref members, .. } => { for (index, member) in members.iter().enumerate() { // Add struct accessor and recurse. segments.push(format!( ".{}", self.names[&NameKey::StructMember(ty, index as u32)] )); self.collect_immediates_items(member.ty, segments, layouter, offset, items); segments.pop(); } // Ensure ending padding is kept by rounding up to the alignment. *offset = layout.alignment.round_up(*offset) } _ => unreachable!(), } } } ================================================ FILE: naga/src/back/hlsl/conv.rs ================================================ use crate::common; use alloc::{borrow::Cow, format, string::String}; use super::Error; use crate::proc::Alignment; impl crate::ScalarKind { pub(super) fn to_hlsl_cast(self) -> &'static str { match self { Self::Float => "asfloat", Self::Sint => "asint", Self::Uint => "asuint", Self::Bool | Self::AbstractInt | Self::AbstractFloat => unreachable!(), } } } impl crate::Scalar { /// Helper function that returns scalar related strings /// /// pub(super) const fn to_hlsl_str(self) -> Result<&'static str, Error> { match self.kind { crate::ScalarKind::Sint => match self.width { 4 => Ok("int"), 8 => Ok("int64_t"), _ => Err(Error::UnsupportedScalar(self)), }, crate::ScalarKind::Uint => match self.width { 4 => Ok("uint"), 8 => Ok("uint64_t"), _ => Err(Error::UnsupportedScalar(self)), }, crate::ScalarKind::Float => match self.width { 2 => Ok("half"), 4 => Ok("float"), 8 => Ok("double"), _ => Err(Error::UnsupportedScalar(self)), }, crate::ScalarKind::Bool => Ok("bool"), crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => { Err(Error::UnsupportedScalar(self)) } } } } impl crate::TypeInner { pub(super) const fn is_matrix(&self) -> bool { match *self { Self::Matrix { .. } => true, _ => false, } } pub(super) fn size_hlsl(&self, gctx: crate::proc::GlobalCtx) -> Result { match *self { Self::Matrix { columns, rows, scalar, } => { let stride = Alignment::from(rows) * scalar.width as u32; let last_row_size = rows as u32 * scalar.width as u32; Ok(((columns as u32 - 1) * stride) + last_row_size) } Self::Array { base, size, stride } => { let count = match size.resolve(gctx)? { crate::proc::IndexableLength::Known(size) => size, // A dynamically-sized array has to have at least one element crate::proc::IndexableLength::Dynamic => 1, }; let last_el_size = gctx.types[base].inner.size_hlsl(gctx)?; Ok(((count - 1) * stride) + last_el_size) } _ => Ok(self.size(gctx)), } } /// Used to generate the name of the wrapped type constructor pub(super) fn hlsl_type_id<'a>( base: crate::Handle, gctx: crate::proc::GlobalCtx, names: &'a crate::FastHashMap, ) -> Result, Error> { Ok(match gctx.types[base].inner { crate::TypeInner::Scalar(scalar) => Cow::Borrowed(scalar.to_hlsl_str()?), crate::TypeInner::Vector { size, scalar } => Cow::Owned(format!( "{}{}", scalar.to_hlsl_str()?, common::vector_size_str(size) )), crate::TypeInner::Matrix { columns, rows, scalar, } => Cow::Owned(format!( "{}{}x{}", scalar.to_hlsl_str()?, common::vector_size_str(columns), common::vector_size_str(rows), )), crate::TypeInner::Array { base, size: crate::ArraySize::Constant(size), .. } => Cow::Owned(format!( "array{size}_{}_", Self::hlsl_type_id(base, gctx, names)? )), crate::TypeInner::Struct { .. } => { Cow::Borrowed(&names[&crate::proc::NameKey::Type(base)]) } _ => unreachable!(), }) } } impl crate::StorageFormat { pub(super) const fn to_hlsl_str(self) -> &'static str { match self { Self::R16Float | Self::R32Float => "float", Self::R8Unorm | Self::R16Unorm => "unorm float", Self::R8Snorm | Self::R16Snorm => "snorm float", Self::R8Uint | Self::R16Uint | Self::R32Uint => "uint", Self::R8Sint | Self::R16Sint | Self::R32Sint => "int", Self::R64Uint => "uint64_t", Self::Rg16Float | Self::Rg32Float => "float4", Self::Rg8Unorm | Self::Rg16Unorm => "unorm float4", Self::Rg8Snorm | Self::Rg16Snorm => "snorm float4", Self::Rg8Sint | Self::Rg16Sint | Self::Rg32Uint => "int4", Self::Rg8Uint | Self::Rg16Uint | Self::Rg32Sint => "uint4", Self::Rg11b10Ufloat => "float4", Self::Rgba16Float | Self::Rgba32Float => "float4", Self::Rgba8Unorm | Self::Bgra8Unorm | Self::Rgba16Unorm | Self::Rgb10a2Unorm => { "unorm float4" } Self::Rgba8Snorm | Self::Rgba16Snorm => "snorm float4", Self::Rgba8Uint | Self::Rgba16Uint | Self::Rgba32Uint | Self::Rgb10a2Uint => "uint4", Self::Rgba8Sint | Self::Rgba16Sint | Self::Rgba32Sint => "int4", } } } impl crate::BuiltIn { pub(super) fn to_hlsl_str(self) -> Result<&'static str, Error> { Ok(match self { Self::Position { .. } => "SV_Position", // vertex Self::ClipDistances => "SV_ClipDistance", Self::CullDistance => "SV_CullDistance", Self::InstanceIndex => "SV_InstanceID", Self::VertexIndex => "SV_VertexID", // fragment Self::FragDepth => "SV_Depth", Self::FrontFacing => "SV_IsFrontFace", Self::PrimitiveIndex => "SV_PrimitiveID", Self::Barycentric { .. } => "SV_Barycentrics", Self::SampleIndex => "SV_SampleIndex", Self::SampleMask => "SV_Coverage", // compute Self::GlobalInvocationId => "SV_DispatchThreadID", Self::LocalInvocationId => "SV_GroupThreadID", Self::LocalInvocationIndex => "SV_GroupIndex", Self::WorkGroupId => "SV_GroupID", // The specific semantic we use here doesn't matter, because references // to this field will get replaced with references to `SPECIAL_CBUF_VAR` // in `Writer::write_expr`. Self::NumWorkGroups => "SV_GroupID", Self::ViewIndex => "SV_ViewID", // These builtins map to functions Self::SubgroupSize | Self::SubgroupInvocationId | Self::NumSubgroups | Self::SubgroupId => unreachable!(), Self::BaseInstance | Self::BaseVertex | Self::WorkGroupSize => { return Err(Error::Unimplemented(format!("builtin {self:?}"))) } Self::PointSize | Self::PointCoord | Self::DrawIndex => { return Err(Error::Custom(format!("Unsupported builtin {self:?}"))) } Self::CullPrimitive => "SV_CullPrimitive", Self::PointIndex | Self::LineIndices | Self::TriangleIndices => unimplemented!(), Self::MeshTaskSize | Self::VertexCount | Self::PrimitiveCount | Self::Vertices | Self::Primitives => unreachable!(), Self::RayInvocationId | Self::NumRayInvocations | Self::InstanceCustomData | Self::GeometryIndex | Self::WorldRayOrigin | Self::WorldRayDirection | Self::ObjectRayOrigin | Self::ObjectRayDirection | Self::RayTmin | Self::RayTCurrentMax | Self::ObjectToWorld | Self::WorldToObject | Self::HitKind => unreachable!(), }) } } impl crate::Interpolation { /// Return the string corresponding to the HLSL interpolation qualifier. pub(super) const fn to_hlsl_str(self) -> Option<&'static str> { match self { // Would be "linear", but it's the default interpolation in SM4 and up // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-struct#interpolation-modifiers-introduced-in-shader-model-4 Self::Perspective => None, Self::Linear => Some("noperspective"), Self::Flat => Some("nointerpolation"), Self::PerVertex => unreachable!(), } } } impl crate::Sampling { /// Return the HLSL auxiliary qualifier for the given sampling value. pub(super) const fn to_hlsl_str(self) -> Option<&'static str> { match self { Self::Center | Self::First | Self::Either => None, Self::Centroid => Some("centroid"), Self::Sample => Some("sample"), } } } impl crate::AtomicFunction { /// Return the HLSL suffix for the `InterlockedXxx` method. pub(super) const fn to_hlsl_suffix(self) -> &'static str { match self { Self::Add | Self::Subtract => "Add", Self::And => "And", Self::InclusiveOr => "Or", Self::ExclusiveOr => "Xor", Self::Min => "Min", Self::Max => "Max", Self::Exchange { compare: None } => "Exchange", Self::Exchange { .. } => "CompareExchange", } } } ================================================ FILE: naga/src/back/hlsl/help.rs ================================================ /*! Helpers for the hlsl backend Important note about `Expression::ImageQuery`/`Expression::ArrayLength` and hlsl backend: Due to implementation of `GetDimensions` function in hlsl () backend can't work with it as an expression. Instead, it generates a unique wrapped function per `Expression::ImageQuery`, based on texture info and query function. See `WrappedImageQuery` struct that represents a unique function and will be generated before writing all statements and expressions. This allowed to works with `Expression::ImageQuery` as expression and write wrapped function. For example: ```wgsl let dim_1d = textureDimensions(image_1d); ``` ```hlsl int NagaDimensions1D(Texture1D) { uint4 ret; image_1d.GetDimensions(ret.x); return ret.x; } int dim_1d = NagaDimensions1D(image_1d); ``` */ use alloc::format; use core::fmt::Write; use super::{ super::FunctionCtx, writer::{ ABS_FUNCTION, DIV_FUNCTION, EXTRACT_BITS_FUNCTION, F2I32_FUNCTION, F2I64_FUNCTION, F2U32_FUNCTION, F2U64_FUNCTION, IMAGE_LOAD_EXTERNAL_FUNCTION, IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION, INSERT_BITS_FUNCTION, MOD_FUNCTION, NEG_FUNCTION, }, BackendResult, WrappedType, }; use crate::{arena::Handle, proc::NameKey, ScalarKind}; #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedArrayLength { pub(super) writable: bool, } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedImageLoad { pub(super) class: crate::ImageClass, } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedImageSample { pub(super) class: crate::ImageClass, pub(super) clamp_to_edge: bool, } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedImageQuery { pub(super) dim: crate::ImageDimension, pub(super) arrayed: bool, pub(super) class: crate::ImageClass, pub(super) query: ImageQuery, } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedConstructor { pub(super) ty: Handle, } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedStructMatrixAccess { pub(super) ty: Handle, pub(super) index: u32, } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedMatCx2 { pub(super) columns: crate::VectorSize, } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedMath { pub(super) fun: crate::MathFunction, pub(super) scalar: crate::Scalar, pub(super) components: Option, } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedZeroValue { pub(super) ty: Handle, } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedUnaryOp { pub(super) op: crate::UnaryOperator, // This can only represent scalar or vector types. If we ever need to wrap // unary ops with other types, we'll need a better representation. pub(super) ty: (Option, crate::Scalar), } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedBinaryOp { pub(super) op: crate::BinaryOperator, // This can only represent scalar or vector types. If we ever need to wrap // binary ops with other types, we'll need a better representation. pub(super) left_ty: (Option, crate::Scalar), pub(super) right_ty: (Option, crate::Scalar), } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct WrappedCast { // This can only represent scalar or vector types. If we ever need to wrap // casts with other types, we'll need a better representation. pub(super) vector_size: Option, pub(super) src_scalar: crate::Scalar, pub(super) dst_scalar: crate::Scalar, } /// HLSL backend requires its own `ImageQuery` enum. /// /// It is used inside `WrappedImageQuery` and should be unique per ImageQuery function. /// IR version can't be unique per function, because it's store mipmap level as an expression. /// /// For example: /// ```wgsl /// let dim_cube_array_lod = textureDimensions(image_cube_array, 1); /// let dim_cube_array_lod2 = textureDimensions(image_cube_array, 1); /// ``` /// /// ```ir /// ImageQuery { /// image: [1], /// query: Size { /// level: Some( /// [1], /// ), /// }, /// }, /// ImageQuery { /// image: [1], /// query: Size { /// level: Some( /// [2], /// ), /// }, /// }, /// ``` /// /// HLSL should generate only 1 function for this case. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub(super) enum ImageQuery { Size, SizeLevel, NumLevels, NumLayers, NumSamples, } impl From for ImageQuery { fn from(q: crate::ImageQuery) -> Self { use crate::ImageQuery as Iq; match q { Iq::Size { level: Some(_) } => ImageQuery::SizeLevel, Iq::Size { level: None } => ImageQuery::Size, Iq::NumLevels => ImageQuery::NumLevels, Iq::NumLayers => ImageQuery::NumLayers, Iq::NumSamples => ImageQuery::NumSamples, } } } pub(super) const IMAGE_STORAGE_LOAD_SCALAR_WRAPPER: &str = "LoadedStorageValueFrom"; impl super::Writer<'_, W> { pub(super) fn write_image_type( &mut self, dim: crate::ImageDimension, arrayed: bool, class: crate::ImageClass, ) -> BackendResult { let access_str = match class { crate::ImageClass::Storage { .. } => "RW", _ => "", }; let dim_str = dim.to_hlsl_str(); let arrayed_str = if arrayed { "Array" } else { "" }; write!(self.out, "{access_str}Texture{dim_str}{arrayed_str}")?; match class { crate::ImageClass::Depth { multi } => { let multi_str = if multi { "MS" } else { "" }; write!(self.out, "{multi_str}")? } crate::ImageClass::Sampled { kind, multi } => { let multi_str = if multi { "MS" } else { "" }; let scalar_kind_str = crate::Scalar { kind, width: 4 }.to_hlsl_str()?; write!(self.out, "{multi_str}<{scalar_kind_str}4>")? } crate::ImageClass::Storage { format, .. } => { let storage_format_str = format.to_hlsl_str(); write!(self.out, "<{storage_format_str}>")? } crate::ImageClass::External => { unreachable!( "external images should be handled by `write_global_external_texture`" ); } } Ok(()) } pub(super) fn write_wrapped_array_length_function_name( &mut self, query: WrappedArrayLength, ) -> BackendResult { let access_str = if query.writable { "RW" } else { "" }; write!(self.out, "NagaBufferLength{access_str}",)?; Ok(()) } /// Helper function that write wrapped function for `Expression::ArrayLength` /// /// pub(super) fn write_wrapped_array_length_function( &mut self, wal: WrappedArrayLength, ) -> BackendResult { use crate::back::INDENT; const ARGUMENT_VARIABLE_NAME: &str = "buffer"; const RETURN_VARIABLE_NAME: &str = "ret"; // Write function return type and name write!(self.out, "uint ")?; self.write_wrapped_array_length_function_name(wal)?; // Write function parameters write!(self.out, "(")?; let access_str = if wal.writable { "RW" } else { "" }; writeln!( self.out, "{access_str}ByteAddressBuffer {ARGUMENT_VARIABLE_NAME})" )?; // Write function body writeln!(self.out, "{{")?; // Write `GetDimensions` function. writeln!(self.out, "{INDENT}uint {RETURN_VARIABLE_NAME};")?; writeln!( self.out, "{INDENT}{ARGUMENT_VARIABLE_NAME}.GetDimensions({RETURN_VARIABLE_NAME});" )?; // Write return value writeln!(self.out, "{INDENT}return {RETURN_VARIABLE_NAME};")?; // End of function body writeln!(self.out, "}}")?; // Write extra new line writeln!(self.out)?; Ok(()) } /// Helper function used by [`Self::write_wrapped_image_load_function`] and /// [`Self::write_wrapped_image_sample_function`] to write the shared YUV /// to RGB conversion code for external textures. Expects the preceding /// code to declare the Y component as a `float` variable of name `y`, the /// UV components as a `float2` variable of name `uv`, and the external /// texture params as a variable of name `params`. The emitted code will /// return the result. fn write_convert_yuv_to_rgb_and_return( &mut self, level: crate::back::Level, y: &str, uv: &str, params: &str, ) -> BackendResult { let l1 = level; let l2 = l1.next(); // Convert from YUV to non-linear RGB in the source color space. We // declare our matrices as row_major in HLSL, therefore we must reverse // the order of this multiplication writeln!( self.out, "{l1}float3 srcGammaRgb = mul(float4({y}, {uv}, 1.0), {params}.yuv_conversion_matrix).rgb;" )?; // Apply the inverse of the source transfer function to convert to // linear RGB in the source color space. writeln!( self.out, "{l1}float3 srcLinearRgb = srcGammaRgb < {params}.src_tf.k * {params}.src_tf.b ?" )?; writeln!(self.out, "{l2}srcGammaRgb / {params}.src_tf.k :")?; writeln!(self.out, "{l2}pow((srcGammaRgb + {params}.src_tf.a - 1.0) / {params}.src_tf.a, {params}.src_tf.g);")?; // Multiply by the gamut conversion matrix to convert to linear RGB in // the destination color space. We declare our matrices as row_major in // HLSL, therefore we must reverse the order of this multiplication. writeln!( self.out, "{l1}float3 dstLinearRgb = mul(srcLinearRgb, {params}.gamut_conversion_matrix);" )?; // Finally, apply the dest transfer function to convert to non-linear // RGB in the destination color space, and return the result. writeln!( self.out, "{l1}float3 dstGammaRgb = dstLinearRgb < {params}.dst_tf.b ?" )?; writeln!(self.out, "{l2}{params}.dst_tf.k * dstLinearRgb :")?; writeln!(self.out, "{l2}{params}.dst_tf.a * pow(dstLinearRgb, 1.0 / {params}.dst_tf.g) - ({params}.dst_tf.a - 1);")?; writeln!(self.out, "{l1}return float4(dstGammaRgb, 1.0);")?; Ok(()) } pub(super) fn write_wrapped_image_load_function( &mut self, module: &crate::Module, load: WrappedImageLoad, ) -> BackendResult { match load { WrappedImageLoad { class: crate::ImageClass::External, } => { let l1 = crate::back::Level(1); let l2 = l1.next(); let l3 = l2.next(); let params_ty_name = &self.names [&NameKey::Type(module.special_types.external_texture_params.unwrap())]; writeln!(self.out, "float4 {IMAGE_LOAD_EXTERNAL_FUNCTION}(")?; writeln!(self.out, "{l1}Texture2D plane0,")?; writeln!(self.out, "{l1}Texture2D plane1,")?; writeln!(self.out, "{l1}Texture2D plane2,")?; writeln!(self.out, "{l1}{params_ty_name} params,")?; writeln!(self.out, "{l1}uint2 coords)")?; writeln!(self.out, "{{")?; writeln!(self.out, "{l1}uint2 plane0_size;")?; writeln!( self.out, "{l1}plane0.GetDimensions(plane0_size.x, plane0_size.y);" )?; // Clamp coords to provided size of external texture to prevent OOB read. // If params.size is zero then clamp to the actual size of the texture. writeln!( self.out, "{l1}uint2 cropped_size = any(params.size) ? params.size : plane0_size;" )?; writeln!(self.out, "{l1}coords = min(coords, cropped_size - 1);")?; // Apply load transformation. We declare our matrices as row_major in // HLSL, therefore we must reverse the order of this multiplication writeln!(self.out, "{l1}float3x2 load_transform = float3x2(")?; writeln!(self.out, "{l2}params.load_transform_0,")?; writeln!(self.out, "{l2}params.load_transform_1,")?; writeln!(self.out, "{l2}params.load_transform_2")?; writeln!(self.out, "{l1});")?; writeln!(self.out, "{l1}uint2 plane0_coords = uint2(round(mul(float3(coords, 1.0), load_transform)));")?; writeln!(self.out, "{l1}if (params.num_planes == 1u) {{")?; // For single plane, simply read from plane0 writeln!( self.out, "{l2}return plane0.Load(uint3(plane0_coords, 0u));" )?; writeln!(self.out, "{l1}}} else {{")?; // Chroma planes may be subsampled so we must scale the coords accordingly. writeln!(self.out, "{l2}uint2 plane1_size;")?; writeln!( self.out, "{l2}plane1.GetDimensions(plane1_size.x, plane1_size.y);" )?; writeln!(self.out, "{l2}uint2 plane1_coords = uint2(floor(float2(plane0_coords) * float2(plane1_size) / float2(plane0_size)));")?; // For multi-plane, read the Y value from plane 0 writeln!( self.out, "{l2}float y = plane0.Load(uint3(plane0_coords, 0u)).x;" )?; writeln!(self.out, "{l2}float2 uv;")?; writeln!(self.out, "{l2}if (params.num_planes == 2u) {{")?; // Read UV from interleaved plane 1 writeln!( self.out, "{l3}uv = plane1.Load(uint3(plane1_coords, 0u)).xy;" )?; writeln!(self.out, "{l2}}} else {{")?; // Read U and V from planes 1 and 2 respectively writeln!(self.out, "{l3}uint2 plane2_size;")?; writeln!( self.out, "{l3}plane2.GetDimensions(plane2_size.x, plane2_size.y);" )?; writeln!(self.out, "{l3}uint2 plane2_coords = uint2(floor(float2(plane0_coords) * float2(plane2_size) / float2(plane0_size)));")?; writeln!(self.out, "{l3}uv = float2(plane1.Load(uint3(plane1_coords, 0u)).x, plane2.Load(uint3(plane2_coords, 0u)).x);")?; writeln!(self.out, "{l2}}}")?; self.write_convert_yuv_to_rgb_and_return(l2, "y", "uv", "params")?; writeln!(self.out, "{l1}}}")?; writeln!(self.out, "}}")?; writeln!(self.out)?; } _ => {} } Ok(()) } pub(super) fn write_wrapped_image_sample_function( &mut self, module: &crate::Module, sample: WrappedImageSample, ) -> BackendResult { match sample { WrappedImageSample { class: crate::ImageClass::External, clamp_to_edge: true, } => { let l1 = crate::back::Level(1); let l2 = l1.next(); let l3 = l2.next(); let params_ty_name = &self.names [&NameKey::Type(module.special_types.external_texture_params.unwrap())]; writeln!( self.out, "float4 {IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION}(" )?; writeln!(self.out, "{l1}Texture2D plane0,")?; writeln!(self.out, "{l1}Texture2D plane1,")?; writeln!(self.out, "{l1}Texture2D plane2,")?; writeln!(self.out, "{l1}{params_ty_name} params,")?; writeln!(self.out, "{l1}SamplerState samp,")?; writeln!(self.out, "{l1}float2 coords)")?; writeln!(self.out, "{{")?; writeln!(self.out, "{l1}float2 plane0_size;")?; writeln!( self.out, "{l1}plane0.GetDimensions(plane0_size.x, plane0_size.y);" )?; writeln!(self.out, "{l1}float3x2 sample_transform = float3x2(")?; writeln!(self.out, "{l2}params.sample_transform_0,")?; writeln!(self.out, "{l2}params.sample_transform_1,")?; writeln!(self.out, "{l2}params.sample_transform_2")?; writeln!(self.out, "{l1});")?; // Apply sample transformation. We declare our matrices as row_major in // HLSL, therefore we must reverse the order of this multiplication writeln!( self.out, "{l1}coords = mul(float3(coords, 1.0), sample_transform);" )?; // Calculate the sample bounds. The purported size of the texture // (params.size) is irrelevant here as we are dealing with normalized // coordinates. Usually we would clamp to (0,0)..(1,1). However, we must // apply the sample transformation to that, also bearing in mind that it // may contain a flip on either axis. We calculate and adjust for the // half-texel separately for each plane as it depends on the actual // texture size which may vary between planes. writeln!( self.out, "{l1}float2 bounds_min = mul(float3(0.0, 0.0, 1.0), sample_transform);" )?; writeln!( self.out, "{l1}float2 bounds_max = mul(float3(1.0, 1.0, 1.0), sample_transform);" )?; writeln!(self.out, "{l1}float4 bounds = float4(min(bounds_min, bounds_max), max(bounds_min, bounds_max));")?; writeln!( self.out, "{l1}float2 plane0_half_texel = float2(0.5, 0.5) / plane0_size;" )?; writeln!( self.out, "{l1}float2 plane0_coords = clamp(coords, bounds.xy + plane0_half_texel, bounds.zw - plane0_half_texel);" )?; writeln!(self.out, "{l1}if (params.num_planes == 1u) {{")?; // For single plane, simply sample from plane0 writeln!( self.out, "{l2}return plane0.SampleLevel(samp, plane0_coords, 0.0f);" )?; writeln!(self.out, "{l1}}} else {{")?; writeln!(self.out, "{l2}float2 plane1_size;")?; writeln!( self.out, "{l2}plane1.GetDimensions(plane1_size.x, plane1_size.y);" )?; writeln!( self.out, "{l2}float2 plane1_half_texel = float2(0.5, 0.5) / plane1_size;" )?; writeln!( self.out, "{l2}float2 plane1_coords = clamp(coords, bounds.xy + plane1_half_texel, bounds.zw - plane1_half_texel);" )?; // For multi-plane, sample the Y value from plane 0 writeln!( self.out, "{l2}float y = plane0.SampleLevel(samp, plane0_coords, 0.0f).x;" )?; writeln!(self.out, "{l2}float2 uv;")?; writeln!(self.out, "{l2}if (params.num_planes == 2u) {{")?; // Sample UV from interleaved plane 1 writeln!( self.out, "{l3}uv = plane1.SampleLevel(samp, plane1_coords, 0.0f).xy;" )?; writeln!(self.out, "{l2}}} else {{")?; // Sample U and V from planes 1 and 2 respectively writeln!(self.out, "{l3}float2 plane2_size;")?; writeln!( self.out, "{l3}plane2.GetDimensions(plane2_size.x, plane2_size.y);" )?; writeln!( self.out, "{l3}float2 plane2_half_texel = float2(0.5, 0.5) / plane2_size;" )?; writeln!(self.out, "{l3}float2 plane2_coords = clamp(coords, bounds.xy + plane2_half_texel, bounds.zw - plane2_half_texel);")?; writeln!(self.out, "{l3}uv = float2(plane1.SampleLevel(samp, plane1_coords, 0.0f).x, plane2.SampleLevel(samp, plane2_coords, 0.0f).x);")?; writeln!(self.out, "{l2}}}")?; self.write_convert_yuv_to_rgb_and_return(l2, "y", "uv", "params")?; writeln!(self.out, "{l1}}}")?; writeln!(self.out, "}}")?; writeln!(self.out)?; } WrappedImageSample { class: crate::ImageClass::Sampled { kind: ScalarKind::Float, multi: false, }, clamp_to_edge: true, } => { writeln!(self.out, "float4 {IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION}(Texture2D tex, SamplerState samp, float2 coords) {{")?; let l1 = crate::back::Level(1); writeln!(self.out, "{l1}float2 size;")?; writeln!(self.out, "{l1}tex.GetDimensions(size.x, size.y);")?; writeln!(self.out, "{l1}float2 half_texel = float2(0.5, 0.5) / size;")?; writeln!( self.out, "{l1}return tex.SampleLevel(samp, clamp(coords, half_texel, 1.0 - half_texel), 0.0);" )?; writeln!(self.out, "}}")?; writeln!(self.out)?; } _ => {} } Ok(()) } pub(super) fn write_wrapped_image_query_function_name( &mut self, query: WrappedImageQuery, ) -> BackendResult { let dim_str = query.dim.to_hlsl_str(); let class_str = match query.class { crate::ImageClass::Sampled { multi: true, .. } => "MS", crate::ImageClass::Depth { multi: true } => "DepthMS", crate::ImageClass::Depth { multi: false } => "Depth", crate::ImageClass::Sampled { multi: false, .. } => "", crate::ImageClass::Storage { .. } => "RW", crate::ImageClass::External => "External", }; let arrayed_str = if query.arrayed { "Array" } else { "" }; let query_str = match query.query { ImageQuery::Size => "Dimensions", ImageQuery::SizeLevel => "MipDimensions", ImageQuery::NumLevels => "NumLevels", ImageQuery::NumLayers => "NumLayers", ImageQuery::NumSamples => "NumSamples", }; write!(self.out, "Naga{class_str}{query_str}{dim_str}{arrayed_str}")?; Ok(()) } /// Helper function that write wrapped function for `Expression::ImageQuery` /// /// pub(super) fn write_wrapped_image_query_function( &mut self, module: &crate::Module, wiq: WrappedImageQuery, expr_handle: Handle, func_ctx: &FunctionCtx, ) -> BackendResult { use crate::{ back::{COMPONENTS, INDENT}, ImageDimension as IDim, }; match wiq.class { crate::ImageClass::External => { if wiq.query != ImageQuery::Size { return Err(super::Error::Custom( "External images only support `Size` queries".into(), )); } write!(self.out, "uint2 ")?; self.write_wrapped_image_query_function_name(wiq)?; let params_name = &self.names [&NameKey::Type(module.special_types.external_texture_params.unwrap())]; // Only plane0 and params are used by this implementation, but it's easier to // always take all of them as arguments so that we can unconditionally expand an // external texture expression each of its parts. writeln!(self.out, "(Texture2D plane0, Texture2D plane1, Texture2D plane2, {params_name} params) {{")?; let l1 = crate::back::Level(1); let l2 = l1.next(); writeln!(self.out, "{l1}if (any(params.size)) {{")?; writeln!(self.out, "{l2}return params.size;")?; writeln!(self.out, "{l1}}} else {{")?; // params.size == (0, 0) indicates to query and return plane 0's actual size writeln!(self.out, "{l2}uint2 ret;")?; writeln!(self.out, "{l2}plane0.GetDimensions(ret.x, ret.y);")?; writeln!(self.out, "{l2}return ret;")?; writeln!(self.out, "{l1}}}")?; writeln!(self.out, "}}")?; writeln!(self.out)?; } _ => { const ARGUMENT_VARIABLE_NAME: &str = "tex"; const RETURN_VARIABLE_NAME: &str = "ret"; const MIP_LEVEL_PARAM: &str = "mip_level"; // Write function return type and name let ret_ty = func_ctx.resolve_type(expr_handle, &module.types); self.write_value_type(module, ret_ty)?; write!(self.out, " ")?; self.write_wrapped_image_query_function_name(wiq)?; // Write function parameters write!(self.out, "(")?; // Texture always first parameter self.write_image_type(wiq.dim, wiq.arrayed, wiq.class)?; write!(self.out, " {ARGUMENT_VARIABLE_NAME}")?; // Mipmap is a second parameter if exists if let ImageQuery::SizeLevel = wiq.query { write!(self.out, ", uint {MIP_LEVEL_PARAM}")?; } writeln!(self.out, ")")?; // Write function body writeln!(self.out, "{{")?; let array_coords = usize::from(wiq.arrayed); // extra parameter is the mip level count or the sample count let extra_coords = match wiq.class { crate::ImageClass::Storage { .. } => 0, crate::ImageClass::Sampled { .. } | crate::ImageClass::Depth { .. } => 1, crate::ImageClass::External => unreachable!(), }; // GetDimensions Overloaded Methods // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-to-getdimensions#overloaded-methods let (ret_swizzle, number_of_params) = match wiq.query { ImageQuery::Size | ImageQuery::SizeLevel => { let ret = match wiq.dim { IDim::D1 => "x", IDim::D2 => "xy", IDim::D3 => "xyz", IDim::Cube => "xy", }; (ret, ret.len() + array_coords + extra_coords) } ImageQuery::NumLevels | ImageQuery::NumSamples | ImageQuery::NumLayers => { if wiq.arrayed || wiq.dim == IDim::D3 { ("w", 4) } else { ("z", 3) } } }; // Write `GetDimensions` function. writeln!(self.out, "{INDENT}uint4 {RETURN_VARIABLE_NAME};")?; write!(self.out, "{INDENT}{ARGUMENT_VARIABLE_NAME}.GetDimensions(")?; match wiq.query { ImageQuery::SizeLevel => { write!(self.out, "{MIP_LEVEL_PARAM}, ")?; } _ => match wiq.class { crate::ImageClass::Sampled { multi: true, .. } | crate::ImageClass::Depth { multi: true } | crate::ImageClass::Storage { .. } => {} _ => { // Write zero mipmap level for supported types write!(self.out, "0, ")?; } }, } for component in COMPONENTS[..number_of_params - 1].iter() { write!(self.out, "{RETURN_VARIABLE_NAME}.{component}, ")?; } // write last parameter without comma and space for last parameter write!( self.out, "{}.{}", RETURN_VARIABLE_NAME, COMPONENTS[number_of_params - 1] )?; writeln!(self.out, ");")?; // Write return value writeln!( self.out, "{INDENT}return {RETURN_VARIABLE_NAME}.{ret_swizzle};" )?; // End of function body writeln!(self.out, "}}")?; // Write extra new line writeln!(self.out)?; } } Ok(()) } pub(super) fn write_wrapped_constructor_function_name( &mut self, module: &crate::Module, constructor: WrappedConstructor, ) -> BackendResult { let name = crate::TypeInner::hlsl_type_id(constructor.ty, module.to_ctx(), &self.names)?; write!(self.out, "Construct{name}")?; Ok(()) } /// Helper function that write wrapped function for `Expression::Compose` for structures. fn write_wrapped_constructor_function( &mut self, module: &crate::Module, constructor: WrappedConstructor, ) -> BackendResult { use crate::back::INDENT; const ARGUMENT_VARIABLE_NAME: &str = "arg"; const RETURN_VARIABLE_NAME: &str = "ret"; // Write function return type and name if let crate::TypeInner::Array { base, size, .. } = module.types[constructor.ty].inner { write!(self.out, "typedef ")?; self.write_type(module, constructor.ty)?; write!(self.out, " ret_")?; self.write_wrapped_constructor_function_name(module, constructor)?; self.write_array_size(module, base, size)?; writeln!(self.out, ";")?; write!(self.out, "ret_")?; self.write_wrapped_constructor_function_name(module, constructor)?; } else { self.write_type(module, constructor.ty)?; } write!(self.out, " ")?; self.write_wrapped_constructor_function_name(module, constructor)?; // Write function parameters write!(self.out, "(")?; let mut write_arg = |i, ty| -> BackendResult { if i != 0 { write!(self.out, ", ")?; } self.write_type(module, ty)?; write!(self.out, " {ARGUMENT_VARIABLE_NAME}{i}")?; if let crate::TypeInner::Array { base, size, .. } = module.types[ty].inner { self.write_array_size(module, base, size)?; } Ok(()) }; match module.types[constructor.ty].inner { crate::TypeInner::Struct { ref members, .. } => { for (i, member) in members.iter().enumerate() { write_arg(i, member.ty)?; } } crate::TypeInner::Array { base, size: crate::ArraySize::Constant(size), .. } => { for i in 0..size.get() as usize { write_arg(i, base)?; } } _ => unreachable!(), }; write!(self.out, ")")?; // Write function body writeln!(self.out, " {{")?; match module.types[constructor.ty].inner { crate::TypeInner::Struct { ref members, .. } => { let struct_name = &self.names[&NameKey::Type(constructor.ty)]; writeln!( self.out, "{INDENT}{struct_name} {RETURN_VARIABLE_NAME} = ({struct_name})0;" )?; for (i, member) in members.iter().enumerate() { let field_name = &self.names[&NameKey::StructMember(constructor.ty, i as u32)]; match module.types[member.ty].inner { crate::TypeInner::Matrix { columns, rows: crate::VectorSize::Bi, .. } if member.binding.is_none() => { for j in 0..columns as u8 { writeln!( self.out, "{INDENT}{RETURN_VARIABLE_NAME}.{field_name}_{j} = {ARGUMENT_VARIABLE_NAME}{i}[{j}];" )?; } } ref other => { // We cast arrays of native HLSL `floatCx2`s to arrays of `matCx2`s // (where the inner matrix is represented by a struct with C `float2` members). // See the module-level block comment in mod.rs for details. if let Some(super::writer::MatrixType { columns, rows: crate::VectorSize::Bi, width: 4, }) = super::writer::get_inner_matrix_data(module, member.ty) { write!( self.out, "{}{}.{} = (__mat{}x2", INDENT, RETURN_VARIABLE_NAME, field_name, columns as u8 )?; if let crate::TypeInner::Array { base, size, .. } = *other { self.write_array_size(module, base, size)?; } writeln!(self.out, "){ARGUMENT_VARIABLE_NAME}{i};",)?; } else { writeln!( self.out, "{INDENT}{RETURN_VARIABLE_NAME}.{field_name} = {ARGUMENT_VARIABLE_NAME}{i};", )?; } } } } } crate::TypeInner::Array { base, size: crate::ArraySize::Constant(size), .. } => { write!(self.out, "{INDENT}")?; self.write_type(module, base)?; write!(self.out, " {RETURN_VARIABLE_NAME}")?; self.write_array_size(module, base, crate::ArraySize::Constant(size))?; write!(self.out, " = {{ ")?; for i in 0..size.get() { if i != 0 { write!(self.out, ", ")?; } write!(self.out, "{ARGUMENT_VARIABLE_NAME}{i}")?; } writeln!(self.out, " }};",)?; } _ => unreachable!(), } // Write return value writeln!(self.out, "{INDENT}return {RETURN_VARIABLE_NAME};")?; // End of function body writeln!(self.out, "}}")?; // Write extra new line writeln!(self.out)?; Ok(()) } /// Writes the conversion from a single length storage texture load to a vec4 with the loaded /// scalar in its `x` component, 1 in its `a` component and 0 everywhere else. fn write_loaded_scalar_to_storage_loaded_value( &mut self, scalar_type: crate::Scalar, ) -> BackendResult { const ARGUMENT_VARIABLE_NAME: &str = "arg"; const RETURN_VARIABLE_NAME: &str = "ret"; let zero; let one; match scalar_type.kind { ScalarKind::Sint => { assert_eq!( scalar_type.width, 4, "Scalar {scalar_type:?} is not a result from any storage format" ); zero = "0"; one = "1"; } ScalarKind::Uint => match scalar_type.width { 4 => { zero = "0u"; one = "1u"; } 8 => { zero = "0uL"; one = "1uL" } _ => unreachable!("Scalar {scalar_type:?} is not a result from any storage format"), }, ScalarKind::Float => { assert_eq!( scalar_type.width, 4, "Scalar {scalar_type:?} is not a result from any storage format" ); zero = "0.0"; one = "1.0"; } _ => unreachable!("Scalar {scalar_type:?} is not a result from any storage format"), } let ty = scalar_type.to_hlsl_str()?; writeln!( self.out, "{ty}4 {IMAGE_STORAGE_LOAD_SCALAR_WRAPPER}{ty}({ty} {ARGUMENT_VARIABLE_NAME}) {{\ {ty}4 {RETURN_VARIABLE_NAME} = {ty}4({ARGUMENT_VARIABLE_NAME}, {zero}, {zero}, {one});\ return {RETURN_VARIABLE_NAME};\ }}" )?; Ok(()) } pub(super) fn write_wrapped_struct_matrix_get_function_name( &mut self, access: WrappedStructMatrixAccess, ) -> BackendResult { let name = &self.names[&NameKey::Type(access.ty)]; let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; write!(self.out, "GetMat{field_name}On{name}")?; Ok(()) } /// Writes a function used to get a matCx2 from within a structure. pub(super) fn write_wrapped_struct_matrix_get_function( &mut self, module: &crate::Module, access: WrappedStructMatrixAccess, ) -> BackendResult { use crate::back::INDENT; const STRUCT_ARGUMENT_VARIABLE_NAME: &str = "obj"; // Write function return type and name let member = match module.types[access.ty].inner { crate::TypeInner::Struct { ref members, .. } => &members[access.index as usize], _ => unreachable!(), }; let ret_ty = &module.types[member.ty].inner; self.write_value_type(module, ret_ty)?; write!(self.out, " ")?; self.write_wrapped_struct_matrix_get_function_name(access)?; // Write function parameters write!(self.out, "(")?; let struct_name = &self.names[&NameKey::Type(access.ty)]; write!(self.out, "{struct_name} {STRUCT_ARGUMENT_VARIABLE_NAME}")?; // Write function body writeln!(self.out, ") {{")?; // Write return value write!(self.out, "{INDENT}return ")?; self.write_value_type(module, ret_ty)?; write!(self.out, "(")?; let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; match module.types[member.ty].inner { crate::TypeInner::Matrix { columns, .. } => { for i in 0..columns as u8 { if i != 0 { write!(self.out, ", ")?; } write!(self.out, "{STRUCT_ARGUMENT_VARIABLE_NAME}.{field_name}_{i}")?; } } _ => unreachable!(), } writeln!(self.out, ");")?; // End of function body writeln!(self.out, "}}")?; // Write extra new line writeln!(self.out)?; Ok(()) } pub(super) fn write_wrapped_struct_matrix_set_function_name( &mut self, access: WrappedStructMatrixAccess, ) -> BackendResult { let name = &self.names[&NameKey::Type(access.ty)]; let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; write!(self.out, "SetMat{field_name}On{name}")?; Ok(()) } /// Writes a function used to set a matCx2 from within a structure. pub(super) fn write_wrapped_struct_matrix_set_function( &mut self, module: &crate::Module, access: WrappedStructMatrixAccess, ) -> BackendResult { use crate::back::INDENT; const STRUCT_ARGUMENT_VARIABLE_NAME: &str = "obj"; const MATRIX_ARGUMENT_VARIABLE_NAME: &str = "mat"; // Write function return type and name write!(self.out, "void ")?; self.write_wrapped_struct_matrix_set_function_name(access)?; // Write function parameters write!(self.out, "(")?; let struct_name = &self.names[&NameKey::Type(access.ty)]; write!(self.out, "{struct_name} {STRUCT_ARGUMENT_VARIABLE_NAME}, ")?; let member = match module.types[access.ty].inner { crate::TypeInner::Struct { ref members, .. } => &members[access.index as usize], _ => unreachable!(), }; self.write_type(module, member.ty)?; write!(self.out, " {MATRIX_ARGUMENT_VARIABLE_NAME}")?; // Write function body writeln!(self.out, ") {{")?; let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; match module.types[member.ty].inner { crate::TypeInner::Matrix { columns, .. } => { for i in 0..columns as u8 { writeln!( self.out, "{INDENT}{STRUCT_ARGUMENT_VARIABLE_NAME}.{field_name}_{i} = {MATRIX_ARGUMENT_VARIABLE_NAME}[{i}];" )?; } } _ => unreachable!(), } // End of function body writeln!(self.out, "}}")?; // Write extra new line writeln!(self.out)?; Ok(()) } pub(super) fn write_wrapped_struct_matrix_set_vec_function_name( &mut self, access: WrappedStructMatrixAccess, ) -> BackendResult { let name = &self.names[&NameKey::Type(access.ty)]; let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; write!(self.out, "SetMatVec{field_name}On{name}")?; Ok(()) } /// Writes a function used to set a vec2 on a matCx2 from within a structure. pub(super) fn write_wrapped_struct_matrix_set_vec_function( &mut self, module: &crate::Module, access: WrappedStructMatrixAccess, ) -> BackendResult { use crate::back::INDENT; const STRUCT_ARGUMENT_VARIABLE_NAME: &str = "obj"; const VECTOR_ARGUMENT_VARIABLE_NAME: &str = "vec"; const MATRIX_INDEX_ARGUMENT_VARIABLE_NAME: &str = "mat_idx"; // Write function return type and name write!(self.out, "void ")?; self.write_wrapped_struct_matrix_set_vec_function_name(access)?; // Write function parameters write!(self.out, "(")?; let struct_name = &self.names[&NameKey::Type(access.ty)]; write!(self.out, "{struct_name} {STRUCT_ARGUMENT_VARIABLE_NAME}, ")?; let member = match module.types[access.ty].inner { crate::TypeInner::Struct { ref members, .. } => &members[access.index as usize], _ => unreachable!(), }; let vec_ty = match module.types[member.ty].inner { crate::TypeInner::Matrix { rows, scalar, .. } => { crate::TypeInner::Vector { size: rows, scalar } } _ => unreachable!(), }; self.write_value_type(module, &vec_ty)?; write!( self.out, " {VECTOR_ARGUMENT_VARIABLE_NAME}, uint {MATRIX_INDEX_ARGUMENT_VARIABLE_NAME}" )?; // Write function body writeln!(self.out, ") {{")?; writeln!( self.out, "{INDENT}switch({MATRIX_INDEX_ARGUMENT_VARIABLE_NAME}) {{" )?; let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; match module.types[member.ty].inner { crate::TypeInner::Matrix { columns, .. } => { for i in 0..columns as u8 { writeln!( self.out, "{INDENT}case {i}: {{ {STRUCT_ARGUMENT_VARIABLE_NAME}.{field_name}_{i} = {VECTOR_ARGUMENT_VARIABLE_NAME}; break; }}" )?; } } _ => unreachable!(), } writeln!(self.out, "{INDENT}}}")?; // End of function body writeln!(self.out, "}}")?; // Write extra new line writeln!(self.out)?; Ok(()) } pub(super) fn write_wrapped_struct_matrix_set_scalar_function_name( &mut self, access: WrappedStructMatrixAccess, ) -> BackendResult { let name = &self.names[&NameKey::Type(access.ty)]; let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; write!(self.out, "SetMatScalar{field_name}On{name}")?; Ok(()) } /// Writes a function used to set a float on a matCx2 from within a structure. pub(super) fn write_wrapped_struct_matrix_set_scalar_function( &mut self, module: &crate::Module, access: WrappedStructMatrixAccess, ) -> BackendResult { use crate::back::INDENT; const STRUCT_ARGUMENT_VARIABLE_NAME: &str = "obj"; const SCALAR_ARGUMENT_VARIABLE_NAME: &str = "scalar"; const MATRIX_INDEX_ARGUMENT_VARIABLE_NAME: &str = "mat_idx"; const VECTOR_INDEX_ARGUMENT_VARIABLE_NAME: &str = "vec_idx"; // Write function return type and name write!(self.out, "void ")?; self.write_wrapped_struct_matrix_set_scalar_function_name(access)?; // Write function parameters write!(self.out, "(")?; let struct_name = &self.names[&NameKey::Type(access.ty)]; write!(self.out, "{struct_name} {STRUCT_ARGUMENT_VARIABLE_NAME}, ")?; let member = match module.types[access.ty].inner { crate::TypeInner::Struct { ref members, .. } => &members[access.index as usize], _ => unreachable!(), }; let scalar_ty = match module.types[member.ty].inner { crate::TypeInner::Matrix { scalar, .. } => crate::TypeInner::Scalar(scalar), _ => unreachable!(), }; self.write_value_type(module, &scalar_ty)?; write!( self.out, " {SCALAR_ARGUMENT_VARIABLE_NAME}, uint {MATRIX_INDEX_ARGUMENT_VARIABLE_NAME}, uint {VECTOR_INDEX_ARGUMENT_VARIABLE_NAME}" )?; // Write function body writeln!(self.out, ") {{")?; writeln!( self.out, "{INDENT}switch({MATRIX_INDEX_ARGUMENT_VARIABLE_NAME}) {{" )?; let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; match module.types[member.ty].inner { crate::TypeInner::Matrix { columns, .. } => { for i in 0..columns as u8 { writeln!( self.out, "{INDENT}case {i}: {{ {STRUCT_ARGUMENT_VARIABLE_NAME}.{field_name}_{i}[{VECTOR_INDEX_ARGUMENT_VARIABLE_NAME}] = {SCALAR_ARGUMENT_VARIABLE_NAME}; break; }}" )?; } } _ => unreachable!(), } writeln!(self.out, "{INDENT}}}")?; // End of function body writeln!(self.out, "}}")?; // Write extra new line writeln!(self.out)?; Ok(()) } /// Write functions to create special types. pub(super) fn write_special_functions(&mut self, module: &crate::Module) -> BackendResult { for (type_key, struct_ty) in module.special_types.predeclared_types.iter() { match type_key { &crate::PredeclaredType::ModfResult { size, scalar } | &crate::PredeclaredType::FrexpResult { size, scalar } => { let arg_type_name_owner; let arg_type_name = if let Some(size) = size { arg_type_name_owner = format!( "{}{}", if scalar.width == 8 { "double" } else { "float" }, size as u8 ); &arg_type_name_owner } else if scalar.width == 8 { "double" } else { "float" }; let (defined_func_name, called_func_name, second_field_name, sign_multiplier) = if matches!(type_key, &crate::PredeclaredType::ModfResult { .. }) { (super::writer::MODF_FUNCTION, "modf", "whole", "") } else { ( super::writer::FREXP_FUNCTION, "frexp", "exp_", "sign(arg) * ", ) }; let struct_name = &self.names[&NameKey::Type(*struct_ty)]; writeln!( self.out, "{struct_name} {defined_func_name}({arg_type_name} arg) {{ {arg_type_name} other; {struct_name} result; result.fract = {sign_multiplier}{called_func_name}(arg, other); result.{second_field_name} = other; return result; }}" )?; writeln!(self.out)?; } &crate::PredeclaredType::AtomicCompareExchangeWeakResult { .. } => {} } } if module.special_types.ray_desc.is_some() { self.write_ray_desc_from_ray_desc_constructor_function(module)?; } Ok(()) } /// Helper function that writes wrapped functions for expressions in a function pub(super) fn write_wrapped_expression_functions( &mut self, module: &crate::Module, expressions: &crate::Arena, context: Option<&FunctionCtx>, ) -> BackendResult { for (handle, _) in expressions.iter() { match expressions[handle] { crate::Expression::Compose { ty, .. } => { match module.types[ty].inner { crate::TypeInner::Struct { .. } | crate::TypeInner::Array { .. } => { let constructor = WrappedConstructor { ty }; if self.wrapped.insert(WrappedType::Constructor(constructor)) { self.write_wrapped_constructor_function(module, constructor)?; } } _ => {} }; } crate::Expression::ImageLoad { image, .. } => { // This can only happen in a function as this is not a valid const expression match *context.as_ref().unwrap().resolve_type(image, &module.types) { crate::TypeInner::Image { class: crate::ImageClass::Storage { format, .. }, .. } => { if format.single_component() { let scalar: crate::Scalar = format.into(); if self.wrapped.insert(WrappedType::ImageLoadScalar(scalar)) { self.write_loaded_scalar_to_storage_loaded_value(scalar)?; } } } _ => {} } } crate::Expression::RayQueryGetIntersection { committed, .. } => { if committed { if !self.written_committed_intersection { self.write_committed_intersection_function(module)?; self.written_committed_intersection = true; } } else if !self.written_candidate_intersection { self.write_candidate_intersection_function(module)?; self.written_candidate_intersection = true; } } _ => {} } } Ok(()) } // TODO: we could merge this with iteration in write_wrapped_expression_functions... // /// Helper function that writes zero value wrapped functions pub(super) fn write_wrapped_zero_value_functions( &mut self, module: &crate::Module, expressions: &crate::Arena, ) -> BackendResult { for (handle, _) in expressions.iter() { if let crate::Expression::ZeroValue(ty) = expressions[handle] { let zero_value = WrappedZeroValue { ty }; if self.wrapped.insert(WrappedType::ZeroValue(zero_value)) { self.write_wrapped_zero_value_function(module, zero_value)?; } } } Ok(()) } pub(super) fn write_wrapped_math_functions( &mut self, module: &crate::Module, func_ctx: &FunctionCtx, ) -> BackendResult { for (_, expression) in func_ctx.expressions.iter() { if let crate::Expression::Math { fun, arg, arg1: _arg1, arg2: _arg2, arg3: _arg3, } = *expression { let arg_ty = func_ctx.resolve_type(arg, &module.types); match fun { crate::MathFunction::ExtractBits => { // The behavior of our extractBits polyfill is undefined if offset + count > bit_width. We need // to first sanitize the offset and count first. If we don't do this, we will get out-of-spec // values if the extracted range is not within the bit width. // // This encodes the exact formula specified by the wgsl spec: // https://gpuweb.github.io/gpuweb/wgsl/#extractBits-unsigned-builtin // // w = sizeof(x) * 8 // o = min(offset, w) // c = min(count, w - o) // // bitfieldExtract(x, o, c) let scalar = arg_ty.scalar().unwrap(); let components = arg_ty.components(); let wrapped = WrappedMath { fun, scalar, components, }; if !self.wrapped.insert(WrappedType::Math(wrapped)) { continue; } // Write return type self.write_value_type(module, arg_ty)?; let scalar_width: u8 = scalar.width * 8; // Write function name and parameters writeln!(self.out, " {EXTRACT_BITS_FUNCTION}(")?; write!(self.out, " ")?; self.write_value_type(module, arg_ty)?; writeln!(self.out, " e,")?; writeln!(self.out, " uint offset,")?; writeln!(self.out, " uint count")?; writeln!(self.out, ") {{")?; // Write function body writeln!(self.out, " uint w = {scalar_width};")?; writeln!(self.out, " uint o = min(offset, w);")?; writeln!(self.out, " uint c = min(count, w - o);")?; writeln!( self.out, " return (c == 0 ? 0 : (e << (w - c - o)) >> (w - c));" )?; // End of function body writeln!(self.out, "}}")?; } crate::MathFunction::InsertBits => { // The behavior of our insertBits polyfill has the same constraints as the extractBits polyfill. let scalar = arg_ty.scalar().unwrap(); let components = arg_ty.components(); let wrapped = WrappedMath { fun, scalar, components, }; if !self.wrapped.insert(WrappedType::Math(wrapped)) { continue; } // Write return type self.write_value_type(module, arg_ty)?; let scalar_width: u8 = scalar.width * 8; let scalar_max: u64 = match scalar.width { 1 => 0xFF, 2 => 0xFFFF, 4 => 0xFFFFFFFF, 8 => 0xFFFFFFFFFFFFFFFF, _ => unreachable!(), }; // Write function name and parameters writeln!(self.out, " {INSERT_BITS_FUNCTION}(")?; write!(self.out, " ")?; self.write_value_type(module, arg_ty)?; writeln!(self.out, " e,")?; write!(self.out, " ")?; self.write_value_type(module, arg_ty)?; writeln!(self.out, " newbits,")?; writeln!(self.out, " uint offset,")?; writeln!(self.out, " uint count")?; writeln!(self.out, ") {{")?; // Write function body writeln!(self.out, " uint w = {scalar_width}u;")?; writeln!(self.out, " uint o = min(offset, w);")?; writeln!(self.out, " uint c = min(count, w - o);")?; // The `u` suffix on the literals is _extremely_ important. Otherwise it will use // i32 shifting instead of the intended u32 shifting. writeln!( self.out, " uint mask = (({scalar_max}u >> ({scalar_width}u - c)) << o);" )?; writeln!( self.out, " return (c == 0 ? e : ((e & ~mask) | ((newbits << o) & mask)));" )?; // End of function body writeln!(self.out, "}}")?; } // Taking the absolute value of the minimum value of a two's // complement signed integer type causes overflow, which is // undefined behaviour in HLSL. To avoid this, when the value is // negative we bitcast the value to unsigned and negate it, then // bitcast back to signed. // This adheres to the WGSL spec in that the absolute of the type's // minimum value should equal to the minimum value. // // TODO(#7109): asint()/asuint() only support 32-bit integers, so we // must find another solution for different bit-widths. crate::MathFunction::Abs if matches!(arg_ty.scalar(), Some(crate::Scalar::I32)) => { let scalar = arg_ty.scalar().unwrap(); let components = arg_ty.components(); let wrapped = WrappedMath { fun, scalar, components, }; if !self.wrapped.insert(WrappedType::Math(wrapped)) { continue; } self.write_value_type(module, arg_ty)?; write!(self.out, " {ABS_FUNCTION}(")?; self.write_value_type(module, arg_ty)?; writeln!(self.out, " val) {{")?; let level = crate::back::Level(1); writeln!( self.out, "{level}return val >= 0 ? val : asint(-asuint(val));" )?; writeln!(self.out, "}}")?; writeln!(self.out)?; } _ => {} } } } Ok(()) } pub(super) fn write_wrapped_unary_ops( &mut self, module: &crate::Module, func_ctx: &FunctionCtx, ) -> BackendResult { for (_, expression) in func_ctx.expressions.iter() { if let crate::Expression::Unary { op, expr } = *expression { let expr_ty = func_ctx.resolve_type(expr, &module.types); let Some((vector_size, scalar)) = expr_ty.vector_size_and_scalar() else { continue; }; let wrapped = WrappedUnaryOp { op, ty: (vector_size, scalar), }; // Negating the minimum value of a two's complement signed integer type // causes overflow, which is undefined behaviour in HLSL. To avoid this // we bitcast the value to unsigned and negate it, then bitcast back to // signed. This adheres to the WGSL spec in that the negative of the // type's minimum value should equal to the minimum value. // // TODO(#7109): asint()/asuint() only support 32-bit integers, so we must // find another solution for different bit-widths. match (op, scalar) { (crate::UnaryOperator::Negate, crate::Scalar::I32) => { if !self.wrapped.insert(WrappedType::UnaryOp(wrapped)) { continue; } self.write_value_type(module, expr_ty)?; write!(self.out, " {NEG_FUNCTION}(")?; self.write_value_type(module, expr_ty)?; writeln!(self.out, " val) {{")?; let level = crate::back::Level(1); writeln!(self.out, "{level}return asint(-asuint(val));",)?; writeln!(self.out, "}}")?; writeln!(self.out)?; } _ => {} } } } Ok(()) } pub(super) fn write_wrapped_binary_ops( &mut self, module: &crate::Module, func_ctx: &FunctionCtx, ) -> BackendResult { for (expr_handle, expression) in func_ctx.expressions.iter() { if let crate::Expression::Binary { op, left, right } = *expression { let expr_ty = func_ctx.resolve_type(expr_handle, &module.types); let left_ty = func_ctx.resolve_type(left, &module.types); let right_ty = func_ctx.resolve_type(right, &module.types); match (op, expr_ty.scalar()) { // Signed integer division of the type's minimum representable value // divided by -1, or signed or unsigned division by zero, is // undefined behaviour in HLSL. We override the divisor to 1 in these // cases. // This adheres to the WGSL spec in that: // * TYPE_MIN / -1 == TYPE_MIN // * x / 0 == x ( crate::BinaryOperator::Divide, Some( scalar @ crate::Scalar { kind: ScalarKind::Sint | ScalarKind::Uint, .. }, ), ) => { let Some(left_wrapped_ty) = left_ty.vector_size_and_scalar() else { continue; }; let Some(right_wrapped_ty) = right_ty.vector_size_and_scalar() else { continue; }; let wrapped = WrappedBinaryOp { op, left_ty: left_wrapped_ty, right_ty: right_wrapped_ty, }; if !self.wrapped.insert(WrappedType::BinaryOp(wrapped)) { continue; } self.write_value_type(module, expr_ty)?; write!(self.out, " {DIV_FUNCTION}(")?; self.write_value_type(module, left_ty)?; write!(self.out, " lhs, ")?; self.write_value_type(module, right_ty)?; writeln!(self.out, " rhs) {{")?; let level = crate::back::Level(1); match scalar.kind { ScalarKind::Sint => { let min_val = match scalar.width { 4 => crate::Literal::I32(i32::MIN), 8 => crate::Literal::I64(i64::MIN), _ => { return Err(super::Error::UnsupportedScalar(scalar)); } }; write!(self.out, "{level}return lhs / (((lhs == ")?; self.write_literal(min_val)?; writeln!(self.out, " & rhs == -1) | (rhs == 0)) ? 1 : rhs);")? } ScalarKind::Uint => { writeln!(self.out, "{level}return lhs / (rhs == 0u ? 1u : rhs);")? } _ => unreachable!(), } writeln!(self.out, "}}")?; writeln!(self.out)?; } // The modulus operator is only defined for integers in HLSL when // either both sides are positive or both sides are negative. To // avoid this undefined behaviour we use the following equation: // // dividend - (dividend / divisor) * divisor // // overriding the divisor to 1 if either it is 0, or it is -1 // and the dividend is the minimum representable value. // // This adheres to the WGSL spec in that: // * min_value % -1 == 0 // * x % 0 == 0 ( crate::BinaryOperator::Modulo, Some( scalar @ crate::Scalar { kind: ScalarKind::Sint | ScalarKind::Uint | ScalarKind::Float, .. }, ), ) => { let Some(left_wrapped_ty) = left_ty.vector_size_and_scalar() else { continue; }; let Some(right_wrapped_ty) = right_ty.vector_size_and_scalar() else { continue; }; let wrapped = WrappedBinaryOp { op, left_ty: left_wrapped_ty, right_ty: right_wrapped_ty, }; if !self.wrapped.insert(WrappedType::BinaryOp(wrapped)) { continue; } self.write_value_type(module, expr_ty)?; write!(self.out, " {MOD_FUNCTION}(")?; self.write_value_type(module, left_ty)?; write!(self.out, " lhs, ")?; self.write_value_type(module, right_ty)?; writeln!(self.out, " rhs) {{")?; let level = crate::back::Level(1); match scalar.kind { ScalarKind::Sint => { let min_val = match scalar.width { 4 => crate::Literal::I32(i32::MIN), 8 => crate::Literal::I64(i64::MIN), _ => { return Err(super::Error::UnsupportedScalar(scalar)); } }; write!(self.out, "{level}")?; self.write_value_type(module, right_ty)?; write!(self.out, " divisor = ((lhs == ")?; self.write_literal(min_val)?; writeln!(self.out, " & rhs == -1) | (rhs == 0)) ? 1 : rhs;")?; writeln!( self.out, "{level}return lhs - (lhs / divisor) * divisor;" )? } ScalarKind::Uint => { writeln!(self.out, "{level}return lhs % (rhs == 0u ? 1u : rhs);")? } // HLSL's fmod has the same definition as WGSL's % operator but due // to its implementation in DXC it is not as accurate as the WGSL spec // requires it to be. See: // - https://shader-playground.timjones.io/0c8572816dbb6fc4435cc5d016a978a7 // - https://github.com/llvm/llvm-project/blob/50f9b8acafdca48e87e6b8e393c1f116a2d193ee/clang/lib/Headers/hlsl/hlsl_intrinsic_helpers.h#L78-L81 ScalarKind::Float => { writeln!(self.out, "{level}return lhs - rhs * trunc(lhs / rhs);")? } _ => unreachable!(), } writeln!(self.out, "}}")?; writeln!(self.out)?; } _ => {} } } } Ok(()) } fn write_wrapped_cast_functions( &mut self, module: &crate::Module, func_ctx: &FunctionCtx, ) -> BackendResult { for (_, expression) in func_ctx.expressions.iter() { if let crate::Expression::As { expr, kind, convert: Some(width), } = *expression { // Avoid undefined behaviour when casting from a float to integer // when the value is out of range for the target type. Additionally // ensure we clamp to the correct value as per the WGSL spec. // // https://www.w3.org/TR/WGSL/#floating-point-conversion: // * If X is exactly representable in the target type T, then the // result is that value. // * Otherwise, the result is the value in T closest to // truncate(X) and also exactly representable in the original // floating point type. let src_ty = func_ctx.resolve_type(expr, &module.types); let Some((vector_size, src_scalar)) = src_ty.vector_size_and_scalar() else { continue; }; let dst_scalar = crate::Scalar { kind, width }; if src_scalar.kind != ScalarKind::Float || (dst_scalar.kind != ScalarKind::Sint && dst_scalar.kind != ScalarKind::Uint) { continue; } let wrapped = WrappedCast { src_scalar, vector_size, dst_scalar, }; if !self.wrapped.insert(WrappedType::Cast(wrapped)) { continue; } let (src_ty, dst_ty) = match vector_size { None => ( crate::TypeInner::Scalar(src_scalar), crate::TypeInner::Scalar(dst_scalar), ), Some(vector_size) => ( crate::TypeInner::Vector { scalar: src_scalar, size: vector_size, }, crate::TypeInner::Vector { scalar: dst_scalar, size: vector_size, }, ), }; let (min, max) = crate::proc::min_max_float_representable_by(src_scalar, dst_scalar); let cast_str = format!( "{}{}", dst_scalar.to_hlsl_str()?, vector_size .map(crate::common::vector_size_str) .unwrap_or(""), ); let fun_name = match dst_scalar { crate::Scalar::I32 => F2I32_FUNCTION, crate::Scalar::U32 => F2U32_FUNCTION, crate::Scalar::I64 => F2I64_FUNCTION, crate::Scalar::U64 => F2U64_FUNCTION, _ => unreachable!(), }; self.write_value_type(module, &dst_ty)?; write!(self.out, " {fun_name}(")?; self.write_value_type(module, &src_ty)?; writeln!(self.out, " value) {{")?; let level = crate::back::Level(1); write!(self.out, "{level}return {cast_str}(clamp(value, ")?; self.write_literal(min)?; write!(self.out, ", ")?; self.write_literal(max)?; writeln!(self.out, "));",)?; writeln!(self.out, "}}")?; writeln!(self.out)?; } } Ok(()) } /// Helper function that writes various wrapped functions pub(super) fn write_wrapped_functions( &mut self, module: &crate::Module, func_ctx: &FunctionCtx, ) -> BackendResult { self.write_wrapped_math_functions(module, func_ctx)?; self.write_wrapped_unary_ops(module, func_ctx)?; self.write_wrapped_binary_ops(module, func_ctx)?; self.write_wrapped_expression_functions(module, func_ctx.expressions, Some(func_ctx))?; self.write_wrapped_zero_value_functions(module, func_ctx.expressions)?; self.write_wrapped_cast_functions(module, func_ctx)?; for (handle, _) in func_ctx.expressions.iter() { match func_ctx.expressions[handle] { crate::Expression::ArrayLength(expr) => { let global_expr = match func_ctx.expressions[expr] { crate::Expression::GlobalVariable(_) => expr, crate::Expression::AccessIndex { base, index: _ } => base, ref other => unreachable!("Array length of {:?}", other), }; let global_var = match func_ctx.expressions[global_expr] { crate::Expression::GlobalVariable(var_handle) => { &module.global_variables[var_handle] } ref other => { return Err(super::Error::Unimplemented(format!( "Array length of base {other:?}" ))) } }; let storage_access = match global_var.space { crate::AddressSpace::Storage { access } => access, _ => crate::StorageAccess::default(), }; let wal = WrappedArrayLength { writable: storage_access.contains(crate::StorageAccess::STORE), }; if self.wrapped.insert(WrappedType::ArrayLength(wal)) { self.write_wrapped_array_length_function(wal)?; } } crate::Expression::ImageLoad { image, .. } => { let class = match *func_ctx.resolve_type(image, &module.types) { crate::TypeInner::Image { class, .. } => class, _ => unreachable!(), }; let wrapped = WrappedImageLoad { class }; if self.wrapped.insert(WrappedType::ImageLoad(wrapped)) { self.write_wrapped_image_load_function(module, wrapped)?; } } crate::Expression::ImageSample { image, clamp_to_edge, .. } => { let class = match *func_ctx.resolve_type(image, &module.types) { crate::TypeInner::Image { class, .. } => class, _ => unreachable!(), }; let wrapped = WrappedImageSample { class, clamp_to_edge, }; if self.wrapped.insert(WrappedType::ImageSample(wrapped)) { self.write_wrapped_image_sample_function(module, wrapped)?; } } crate::Expression::ImageQuery { image, query } => { let wiq = match *func_ctx.resolve_type(image, &module.types) { crate::TypeInner::Image { dim, arrayed, class, } => WrappedImageQuery { dim, arrayed, class, query: query.into(), }, _ => unreachable!("we only query images"), }; if self.wrapped.insert(WrappedType::ImageQuery(wiq)) { self.write_wrapped_image_query_function(module, wiq, handle, func_ctx)?; } } // Write `WrappedConstructor` for structs that are loaded from `AddressSpace::Storage` // since they will later be used by the fn `write_storage_load` crate::Expression::Load { pointer } => { let pointer_space = func_ctx .resolve_type(pointer, &module.types) .pointer_space(); if let Some(crate::AddressSpace::Storage { .. }) = pointer_space { if let Some(ty) = func_ctx.info[handle].ty.handle() { write_wrapped_constructor(self, ty, module)?; } } fn write_wrapped_constructor( writer: &mut super::Writer<'_, W>, ty: Handle, module: &crate::Module, ) -> BackendResult { match module.types[ty].inner { crate::TypeInner::Struct { ref members, .. } => { for member in members { write_wrapped_constructor(writer, member.ty, module)?; } let constructor = WrappedConstructor { ty }; if writer.wrapped.insert(WrappedType::Constructor(constructor)) { writer .write_wrapped_constructor_function(module, constructor)?; } } crate::TypeInner::Array { base, .. } => { write_wrapped_constructor(writer, base, module)?; let constructor = WrappedConstructor { ty }; if writer.wrapped.insert(WrappedType::Constructor(constructor)) { writer .write_wrapped_constructor_function(module, constructor)?; } } _ => {} }; Ok(()) } } // We treat matrices of the form `matCx2` as a sequence of C `vec2`s // (see top level module docs for details). // // The functions injected here are required to get the matrix accesses working. crate::Expression::AccessIndex { base, index } => { let base_ty_res = &func_ctx.info[base].ty; let mut resolved = base_ty_res.inner_with(&module.types); let base_ty_handle = match *resolved { crate::TypeInner::Pointer { base, .. } => { resolved = &module.types[base].inner; Some(base) } _ => base_ty_res.handle(), }; if let crate::TypeInner::Struct { ref members, .. } = *resolved { let member = &members[index as usize]; match module.types[member.ty].inner { crate::TypeInner::Matrix { rows: crate::VectorSize::Bi, .. } if member.binding.is_none() => { let ty = base_ty_handle.unwrap(); let access = WrappedStructMatrixAccess { ty, index }; if self.wrapped.insert(WrappedType::StructMatrixAccess(access)) { self.write_wrapped_struct_matrix_get_function(module, access)?; self.write_wrapped_struct_matrix_set_function(module, access)?; self.write_wrapped_struct_matrix_set_vec_function( module, access, )?; self.write_wrapped_struct_matrix_set_scalar_function( module, access, )?; } } _ => {} } } } _ => {} }; } Ok(()) } /// Writes out the sampler heap declarations if they haven't been written yet. pub(super) fn write_sampler_heaps(&mut self) -> BackendResult { if self.wrapped.sampler_heaps { return Ok(()); } writeln!( self.out, "SamplerState {}[2048]: register(s{}, space{});", super::writer::SAMPLER_HEAP_VAR, self.options.sampler_heap_target.standard_samplers.register, self.options.sampler_heap_target.standard_samplers.space )?; writeln!( self.out, "SamplerComparisonState {}[2048]: register(s{}, space{});", super::writer::COMPARISON_SAMPLER_HEAP_VAR, self.options .sampler_heap_target .comparison_samplers .register, self.options.sampler_heap_target.comparison_samplers.space )?; self.wrapped.sampler_heaps = true; Ok(()) } /// Writes out the sampler index buffer declaration if it hasn't been written yet. pub(super) fn write_wrapped_sampler_buffer( &mut self, key: super::SamplerIndexBufferKey, ) -> BackendResult { // The astute will notice that we do a double hash lookup, but we do this to avoid // holding a mutable reference to `self` while trying to call `write_sampler_heaps`. // // We only pay this double lookup cost when we actually need to write out the sampler // buffer, which should be not be common. if self.wrapped.sampler_index_buffers.contains_key(&key) { return Ok(()); }; self.write_sampler_heaps()?; // Because the group number can be arbitrary, we use the namer to generate a unique name // instead of adding it to the reserved name list. let sampler_array_name = self .namer .call(&format!("nagaGroup{}SamplerIndexArray", key.group)); let bind_target = match self.options.sampler_buffer_binding_map.get(&key) { Some(&bind_target) => bind_target, None if self.options.fake_missing_bindings => super::BindTarget { space: u8::MAX, register: key.group, binding_array_size: None, dynamic_storage_buffer_offsets_index: None, restrict_indexing: false, }, None => { unreachable!("Sampler buffer of group {key:?} not bound to a register"); } }; writeln!( self.out, "StructuredBuffer {sampler_array_name} : register(t{}, space{});", bind_target.register, bind_target.space )?; self.wrapped .sampler_index_buffers .insert(key, sampler_array_name); Ok(()) } pub(super) fn write_texture_coordinates( &mut self, kind: &str, coordinate: Handle, array_index: Option>, mip_level: Option>, module: &crate::Module, func_ctx: &FunctionCtx, ) -> BackendResult { // HLSL expects the array index to be merged with the coordinate let extra = array_index.is_some() as usize + (mip_level.is_some()) as usize; if extra == 0 { self.write_expr(module, coordinate, func_ctx)?; } else { let num_coords = match *func_ctx.resolve_type(coordinate, &module.types) { crate::TypeInner::Scalar { .. } => 1, crate::TypeInner::Vector { size, .. } => size as usize, _ => unreachable!(), }; write!(self.out, "{}{}(", kind, num_coords + extra)?; self.write_expr(module, coordinate, func_ctx)?; if let Some(expr) = array_index { write!(self.out, ", ")?; self.write_expr(module, expr, func_ctx)?; } if let Some(expr) = mip_level { // Explicit cast if needed let cast_to_int = matches!( *func_ctx.resolve_type(expr, &module.types), crate::TypeInner::Scalar(crate::Scalar { kind: ScalarKind::Uint, .. }) ); write!(self.out, ", ")?; if cast_to_int { write!(self.out, "int(")?; } self.write_expr(module, expr, func_ctx)?; if cast_to_int { write!(self.out, ")")?; } } write!(self.out, ")")?; } Ok(()) } pub(super) fn write_mat_cx2_typedef_and_functions( &mut self, WrappedMatCx2 { columns }: WrappedMatCx2, ) -> BackendResult { use crate::back::INDENT; // typedef write!(self.out, "typedef struct {{ ")?; for i in 0..columns as u8 { write!(self.out, "float2 _{i}; ")?; } writeln!(self.out, "}} __mat{}x2;", columns as u8)?; // __get_col_of_mat writeln!( self.out, "float2 __get_col_of_mat{}x2(__mat{}x2 mat, uint idx) {{", columns as u8, columns as u8 )?; writeln!(self.out, "{INDENT}switch(idx) {{")?; for i in 0..columns as u8 { writeln!(self.out, "{INDENT}case {i}: {{ return mat._{i}; }}")?; } writeln!(self.out, "{INDENT}default: {{ return (float2)0; }}")?; writeln!(self.out, "{INDENT}}}")?; writeln!(self.out, "}}")?; // __set_col_of_mat writeln!( self.out, "void __set_col_of_mat{}x2(__mat{}x2 mat, uint idx, float2 value) {{", columns as u8, columns as u8 )?; writeln!(self.out, "{INDENT}switch(idx) {{")?; for i in 0..columns as u8 { writeln!(self.out, "{INDENT}case {i}: {{ mat._{i} = value; break; }}")?; } writeln!(self.out, "{INDENT}}}")?; writeln!(self.out, "}}")?; // __set_el_of_mat writeln!( self.out, "void __set_el_of_mat{}x2(__mat{}x2 mat, uint idx, uint vec_idx, float value) {{", columns as u8, columns as u8 )?; writeln!(self.out, "{INDENT}switch(idx) {{")?; for i in 0..columns as u8 { writeln!( self.out, "{INDENT}case {i}: {{ mat._{i}[vec_idx] = value; break; }}" )?; } writeln!(self.out, "{INDENT}}}")?; writeln!(self.out, "}}")?; writeln!(self.out)?; Ok(()) } pub(super) fn write_all_mat_cx2_typedefs_and_functions( &mut self, module: &crate::Module, ) -> BackendResult { for (handle, _) in module.global_variables.iter() { let global = &module.global_variables[handle]; if global.space == crate::AddressSpace::Uniform { if let Some(super::writer::MatrixType { columns, rows: crate::VectorSize::Bi, width: 4, }) = super::writer::get_inner_matrix_data(module, global.ty) { let entry = WrappedMatCx2 { columns }; if self.wrapped.insert(WrappedType::MatCx2(entry)) { self.write_mat_cx2_typedef_and_functions(entry)?; } } } } for (_, ty) in module.types.iter() { if let crate::TypeInner::Struct { ref members, .. } = ty.inner { for member in members.iter() { if let crate::TypeInner::Array { .. } = module.types[member.ty].inner { if let Some(super::writer::MatrixType { columns, rows: crate::VectorSize::Bi, width: 4, }) = super::writer::get_inner_matrix_data(module, member.ty) { let entry = WrappedMatCx2 { columns }; if self.wrapped.insert(WrappedType::MatCx2(entry)) { self.write_mat_cx2_typedef_and_functions(entry)?; } } } } } } Ok(()) } pub(super) fn write_wrapped_zero_value_function_name( &mut self, module: &crate::Module, zero_value: WrappedZeroValue, ) -> BackendResult { let name = crate::TypeInner::hlsl_type_id(zero_value.ty, module.to_ctx(), &self.names)?; write!(self.out, "ZeroValue{name}")?; Ok(()) } /// Helper function that write wrapped function for `Expression::ZeroValue` /// /// This is necessary since we might have a member access after the zero value expression, e.g. /// `.y` (in practice this can come up when consuming SPIRV that's been produced by glslc). /// /// So we can't just write `(float4)0` since `(float4)0.y` won't parse correctly. /// /// Parenthesizing the expression like `((float4)0).y` would work... except DXC can't handle /// cases like: /// /// ```text /// tests\out\hlsl\access.hlsl:183:41: error: cannot compile this l-value expression yet /// t_1.am = (__mat4x2[2])((float4x2[2])0); /// ^ /// ``` fn write_wrapped_zero_value_function( &mut self, module: &crate::Module, zero_value: WrappedZeroValue, ) -> BackendResult { use crate::back::INDENT; // Write function return type and name if let crate::TypeInner::Array { base, size, .. } = module.types[zero_value.ty].inner { write!(self.out, "typedef ")?; self.write_type(module, zero_value.ty)?; write!(self.out, " ret_")?; self.write_wrapped_zero_value_function_name(module, zero_value)?; self.write_array_size(module, base, size)?; writeln!(self.out, ";")?; write!(self.out, "ret_")?; self.write_wrapped_zero_value_function_name(module, zero_value)?; } else { self.write_type(module, zero_value.ty)?; } write!(self.out, " ")?; self.write_wrapped_zero_value_function_name(module, zero_value)?; // Write function parameters (none) and start function body writeln!(self.out, "() {{")?; // Write `ZeroValue` function. write!(self.out, "{INDENT}return ")?; self.write_default_init(module, zero_value.ty)?; writeln!(self.out, ";")?; // End of function body writeln!(self.out, "}}")?; // Write extra new line writeln!(self.out)?; Ok(()) } } impl crate::StorageFormat { /// Returns `true` if there is just one component, otherwise `false` pub(super) const fn single_component(&self) -> bool { match *self { crate::StorageFormat::R16Float | crate::StorageFormat::R32Float | crate::StorageFormat::R8Unorm | crate::StorageFormat::R16Unorm | crate::StorageFormat::R8Snorm | crate::StorageFormat::R16Snorm | crate::StorageFormat::R8Uint | crate::StorageFormat::R16Uint | crate::StorageFormat::R32Uint | crate::StorageFormat::R8Sint | crate::StorageFormat::R16Sint | crate::StorageFormat::R32Sint | crate::StorageFormat::R64Uint => true, _ => false, } } } ================================================ FILE: naga/src/back/hlsl/keywords.rs ================================================ use crate::proc::{CaseInsensitiveKeywordSet, KeywordSet}; use crate::racy_lock::RacyLock; // When compiling with FXC without strict mode, these keywords are actually case insensitive. // If you compile with strict mode and specify a different casing like "Pass" instead in an identifier, FXC will give this error: // "error X3086: alternate cases for 'pass' are deprecated in strict mode" // This behavior is not documented anywhere, but as far as I can tell this is the full list. pub const RESERVED_CASE_INSENSITIVE: &[&str] = &[ "asm", "decl", "pass", "technique", "Texture1D", "Texture2D", "Texture3D", "TextureCube", ]; pub const RESERVED: &[&str] = &[ // FXC keywords, from https://github.com/MicrosoftDocs/win32/blob/c885cb0c63b0e9be80c6a0e6512473ac6f4e771e/desktop-src/direct3dhlsl/dx-graphics-hlsl-appendix-keywords.md?plain=1#L99-L118 "AppendStructuredBuffer", "asm", "asm_fragment", "BlendState", "bool", "break", "Buffer", "ByteAddressBuffer", "case", "cbuffer", "centroid", "class", "column_major", "compile", "compile_fragment", "CompileShader", "const", "continue", "ComputeShader", "ConsumeStructuredBuffer", "default", "DepthStencilState", "DepthStencilView", "discard", "do", "double", "DomainShader", "dword", "else", "export", "extern", "false", "float", "for", "fxgroup", "GeometryShader", "groupshared", "half", "Hullshader", "if", "in", "inline", "inout", "InputPatch", "int", "interface", "line", "lineadj", "linear", "LineStream", "matrix", "min16float", "min10float", "min16int", "min12int", "min16uint", "namespace", "nointerpolation", "noperspective", "NULL", "out", "OutputPatch", "packoffset", "pass", "pixelfragment", "PixelShader", "point", "PointStream", "precise", "RasterizerState", "RenderTargetView", "return", "register", "row_major", "RWBuffer", "RWByteAddressBuffer", "RWStructuredBuffer", "RWTexture1D", "RWTexture1DArray", "RWTexture2D", "RWTexture2DArray", "RWTexture3D", "sample", "sampler", "SamplerState", "SamplerComparisonState", "shared", "snorm", "stateblock", "stateblock_state", "static", "string", "struct", "switch", "StructuredBuffer", "tbuffer", "technique", "technique10", "technique11", "texture", "Texture1D", "Texture1DArray", "Texture2D", "Texture2DArray", "Texture2DMS", "Texture2DMSArray", "Texture3D", "TextureCube", "TextureCubeArray", "true", "typedef", "triangle", "triangleadj", "TriangleStream", "uint", "uniform", "unorm", "unsigned", "vector", "vertexfragment", "VertexShader", "void", "volatile", "while", // FXC reserved keywords, from https://github.com/MicrosoftDocs/win32/blob/c885cb0c63b0e9be80c6a0e6512473ac6f4e771e/desktop-src/direct3dhlsl/dx-graphics-hlsl-appendix-reserved-words.md?plain=1#L19-L38 "auto", "case", "catch", "char", "class", "const_cast", "default", "delete", "dynamic_cast", "enum", "explicit", "friend", "goto", "long", "mutable", "new", "operator", "private", "protected", "public", "reinterpret_cast", "short", "signed", "sizeof", "static_cast", "template", "this", "throw", "try", "typename", "union", "unsigned", "using", "virtual", // FXC intrinsics, from https://github.com/MicrosoftDocs/win32/blob/1682b99e203708f6f5eda972d966e30f3c1588de/desktop-src/direct3dhlsl/dx-graphics-hlsl-intrinsic-functions.md?plain=1#L26-L165 "abort", "abs", "acos", "all", "AllMemoryBarrier", "AllMemoryBarrierWithGroupSync", "any", "asdouble", "asfloat", "asin", "asint", "asuint", "atan", "atan2", "ceil", "CheckAccessFullyMapped", "clamp", "clip", "cos", "cosh", "countbits", "cross", "D3DCOLORtoUBYTE4", "ddx", "ddx_coarse", "ddx_fine", "ddy", "ddy_coarse", "ddy_fine", "degrees", "determinant", "DeviceMemoryBarrier", "DeviceMemoryBarrierWithGroupSync", "distance", "dot", "dst", "errorf", "EvaluateAttributeCentroid", "EvaluateAttributeAtSample", "EvaluateAttributeSnapped", "exp", "exp2", "f16tof32", "f32tof16", "faceforward", "firstbithigh", "firstbitlow", "floor", "fma", "fmod", "frac", "frexp", "fwidth", "GetRenderTargetSampleCount", "GetRenderTargetSamplePosition", "GroupMemoryBarrier", "GroupMemoryBarrierWithGroupSync", "InterlockedAdd", "InterlockedAnd", "InterlockedCompareExchange", "InterlockedCompareStore", "InterlockedExchange", "InterlockedMax", "InterlockedMin", "InterlockedOr", "InterlockedXor", "isfinite", "isinf", "isnan", "ldexp", "length", "lerp", "lit", "log", "log10", "log2", "mad", "max", "min", "modf", "msad4", "mul", "noise", "normalize", "pow", "printf", "Process2DQuadTessFactorsAvg", "Process2DQuadTessFactorsMax", "Process2DQuadTessFactorsMin", "ProcessIsolineTessFactors", "ProcessQuadTessFactorsAvg", "ProcessQuadTessFactorsMax", "ProcessQuadTessFactorsMin", "ProcessTriTessFactorsAvg", "ProcessTriTessFactorsMax", "ProcessTriTessFactorsMin", "radians", "rcp", "reflect", "refract", "reversebits", "round", "rsqrt", "saturate", "sign", "sin", "sincos", "sinh", "smoothstep", "sqrt", "step", "tan", "tanh", "tex1D", "tex1Dbias", "tex1Dgrad", "tex1Dlod", "tex1Dproj", "tex2D", "tex2Dbias", "tex2Dgrad", "tex2Dlod", "tex2Dproj", "tex3D", "tex3Dbias", "tex3Dgrad", "tex3Dlod", "tex3Dproj", "texCUBE", "texCUBEbias", "texCUBEgrad", "texCUBElod", "texCUBEproj", "transpose", "trunc", // DXC (reserved) keywords, from https://github.com/microsoft/DirectXShaderCompiler/blob/d5d478470d3020a438d3cb810b8d3fe0992e6709/tools/clang/include/clang/Basic/TokenKinds.def#L222-L648 // with the KEYALL, KEYCXX, BOOLSUPPORT, WCHARSUPPORT, KEYHLSL options enabled (see https://github.com/microsoft/DirectXShaderCompiler/blob/d5d478470d3020a438d3cb810b8d3fe0992e6709/tools/clang/lib/Frontend/CompilerInvocation.cpp#L1199) "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "return", "short", "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Complex", "_Generic", "_Imaginary", "_Noreturn", "_Static_assert", "_Thread_local", "__func__", "__objc_yes", "__objc_no", "asm", "bool", "catch", "class", "const_cast", "delete", "dynamic_cast", "explicit", "export", "false", "friend", "mutable", "namespace", "new", "operator", "private", "protected", "public", "reinterpret_cast", "static_cast", "template", "this", "throw", "true", "try", "typename", "typeid", "using", "virtual", "wchar_t", "_Decimal32", "_Decimal64", "_Decimal128", "__null", "__alignof", "__attribute", "__builtin_choose_expr", "__builtin_offsetof", "__builtin_va_arg", "__extension__", "__imag", "__int128", "__label__", "__real", "__thread", "__FUNCTION__", "__PRETTY_FUNCTION__", "__is_nothrow_assignable", "__is_constructible", "__is_nothrow_constructible", "__has_nothrow_assign", "__has_nothrow_move_assign", "__has_nothrow_copy", "__has_nothrow_constructor", "__has_trivial_assign", "__has_trivial_move_assign", "__has_trivial_copy", "__has_trivial_constructor", "__has_trivial_move_constructor", "__has_trivial_destructor", "__has_virtual_destructor", "__is_abstract", "__is_base_of", "__is_class", "__is_convertible_to", "__is_empty", "__is_enum", "__is_final", "__is_literal", "__is_literal_type", "__is_pod", "__is_polymorphic", "__is_trivial", "__is_union", "__is_trivially_constructible", "__is_trivially_copyable", "__is_trivially_assignable", "__underlying_type", "__is_lvalue_expr", "__is_rvalue_expr", "__is_arithmetic", "__is_floating_point", "__is_integral", "__is_complete_type", "__is_void", "__is_array", "__is_function", "__is_reference", "__is_lvalue_reference", "__is_rvalue_reference", "__is_fundamental", "__is_object", "__is_scalar", "__is_compound", "__is_pointer", "__is_member_object_pointer", "__is_member_function_pointer", "__is_member_pointer", "__is_const", "__is_volatile", "__is_standard_layout", "__is_signed", "__is_unsigned", "__is_same", "__is_convertible", "__array_rank", "__array_extent", "__private_extern__", "__module_private__", "__declspec", "__cdecl", "__stdcall", "__fastcall", "__thiscall", "__vectorcall", "cbuffer", "tbuffer", "packoffset", "linear", "centroid", "nointerpolation", "noperspective", "sample", "column_major", "row_major", "in", "out", "inout", "uniform", "precise", "center", "shared", "groupshared", "discard", "snorm", "unorm", "point", "line", "lineadj", "triangle", "triangleadj", "globallycoherent", "interface", "sampler_state", "technique", "indices", "vertices", "primitives", "payload", "Technique", "technique10", "technique11", "__builtin_omp_required_simd_align", "__pascal", "__fp16", "__alignof__", "__asm", "__asm__", "__attribute__", "__complex", "__complex__", "__const", "__const__", "__decltype", "__imag__", "__inline", "__inline__", "__nullptr", "__real__", "__restrict", "__restrict__", "__signed", "__signed__", "__typeof", "__typeof__", "__volatile", "__volatile__", "_Nonnull", "_Nullable", "_Null_unspecified", "__builtin_convertvector", "__char16_t", "__char32_t", // DXC intrinsics, from https://github.com/microsoft/DirectXShaderCompiler/blob/18c9e114f9c314f93e68fbc72ce207d4ed2e65ae/utils/hct/gen_intrin_main.txt#L86-L376 "D3DCOLORtoUBYTE4", "GetRenderTargetSampleCount", "GetRenderTargetSamplePosition", "abort", "abs", "acos", "all", "AllMemoryBarrier", "AllMemoryBarrierWithGroupSync", "any", "asdouble", "asfloat", "asfloat16", "asint16", "asin", "asint", "asuint", "asuint16", "atan", "atan2", "ceil", "clamp", "clip", "cos", "cosh", "countbits", "cross", "ddx", "ddx_coarse", "ddx_fine", "ddy", "ddy_coarse", "ddy_fine", "degrees", "determinant", "DeviceMemoryBarrier", "DeviceMemoryBarrierWithGroupSync", "distance", "dot", "dst", "EvaluateAttributeAtSample", "EvaluateAttributeCentroid", "EvaluateAttributeSnapped", "GetAttributeAtVertex", "exp", "exp2", "f16tof32", "f32tof16", "faceforward", "firstbithigh", "firstbitlow", "floor", "fma", "fmod", "frac", "frexp", "fwidth", "GroupMemoryBarrier", "GroupMemoryBarrierWithGroupSync", "InterlockedAdd", "InterlockedMin", "InterlockedMax", "InterlockedAnd", "InterlockedOr", "InterlockedXor", "InterlockedCompareStore", "InterlockedExchange", "InterlockedCompareExchange", "InterlockedCompareStoreFloatBitwise", "InterlockedCompareExchangeFloatBitwise", "isfinite", "isinf", "isnan", "ldexp", "length", "lerp", "lit", "log", "log10", "log2", "mad", "max", "min", "modf", "msad4", "mul", "normalize", "pow", "printf", "Process2DQuadTessFactorsAvg", "Process2DQuadTessFactorsMax", "Process2DQuadTessFactorsMin", "ProcessIsolineTessFactors", "ProcessQuadTessFactorsAvg", "ProcessQuadTessFactorsMax", "ProcessQuadTessFactorsMin", "ProcessTriTessFactorsAvg", "ProcessTriTessFactorsMax", "ProcessTriTessFactorsMin", "radians", "rcp", "reflect", "refract", "reversebits", "round", "rsqrt", "saturate", "sign", "sin", "sincos", "sinh", "smoothstep", "source_mark", "sqrt", "step", "tan", "tanh", "tex1D", "tex1Dbias", "tex1Dgrad", "tex1Dlod", "tex1Dproj", "tex2D", "tex2Dbias", "tex2Dgrad", "tex2Dlod", "tex2Dproj", "tex3D", "tex3Dbias", "tex3Dgrad", "tex3Dlod", "tex3Dproj", "texCUBE", "texCUBEbias", "texCUBEgrad", "texCUBElod", "texCUBEproj", "transpose", "trunc", "CheckAccessFullyMapped", "AddUint64", "NonUniformResourceIndex", "WaveIsFirstLane", "WaveGetLaneIndex", "WaveGetLaneCount", "WaveActiveAnyTrue", "WaveActiveAllTrue", "WaveActiveAllEqual", "WaveActiveBallot", "WaveReadLaneAt", "WaveReadLaneFirst", "WaveActiveCountBits", "WaveActiveSum", "WaveActiveProduct", "WaveActiveBitAnd", "WaveActiveBitOr", "WaveActiveBitXor", "WaveActiveMin", "WaveActiveMax", "WavePrefixCountBits", "WavePrefixSum", "WavePrefixProduct", "WaveMatch", "WaveMultiPrefixBitAnd", "WaveMultiPrefixBitOr", "WaveMultiPrefixBitXor", "WaveMultiPrefixCountBits", "WaveMultiPrefixProduct", "WaveMultiPrefixSum", "QuadReadLaneAt", "QuadReadAcrossX", "QuadReadAcrossY", "QuadReadAcrossDiagonal", "QuadAny", "QuadAll", "TraceRay", "ReportHit", "CallShader", "IgnoreHit", "AcceptHitAndEndSearch", "DispatchRaysIndex", "DispatchRaysDimensions", "WorldRayOrigin", "WorldRayDirection", "ObjectRayOrigin", "ObjectRayDirection", "RayTMin", "RayTCurrent", "PrimitiveIndex", "InstanceID", "InstanceIndex", "GeometryIndex", "HitKind", "RayFlags", "ObjectToWorld", "WorldToObject", "ObjectToWorld3x4", "WorldToObject3x4", "ObjectToWorld4x3", "WorldToObject4x3", "dot4add_u8packed", "dot4add_i8packed", "dot2add", "unpack_s8s16", "unpack_u8u16", "unpack_s8s32", "unpack_u8u32", "pack_s8", "pack_u8", "pack_clamp_s8", "pack_clamp_u8", "SetMeshOutputCounts", "DispatchMesh", "IsHelperLane", "AllocateRayQuery", "CreateResourceFromHeap", "and", "or", "select", // DXC resource and other types, from https://github.com/microsoft/DirectXShaderCompiler/blob/18c9e114f9c314f93e68fbc72ce207d4ed2e65ae/tools/clang/lib/AST/HlslTypes.cpp#L441-#L572 "InputPatch", "OutputPatch", "PointStream", "LineStream", "TriangleStream", "Texture1D", "RWTexture1D", "Texture2D", "RWTexture2D", "Texture2DMS", "RWTexture2DMS", "Texture3D", "RWTexture3D", "TextureCube", "RWTextureCube", "Texture1DArray", "RWTexture1DArray", "Texture2DArray", "RWTexture2DArray", "Texture2DMSArray", "RWTexture2DMSArray", "TextureCubeArray", "RWTextureCubeArray", "FeedbackTexture2D", "FeedbackTexture2DArray", "RasterizerOrderedTexture1D", "RasterizerOrderedTexture2D", "RasterizerOrderedTexture3D", "RasterizerOrderedTexture1DArray", "RasterizerOrderedTexture2DArray", "RasterizerOrderedBuffer", "RasterizerOrderedByteAddressBuffer", "RasterizerOrderedStructuredBuffer", "ByteAddressBuffer", "RWByteAddressBuffer", "StructuredBuffer", "RWStructuredBuffer", "AppendStructuredBuffer", "ConsumeStructuredBuffer", "Buffer", "RWBuffer", "SamplerState", "SamplerComparisonState", "ConstantBuffer", "TextureBuffer", "RaytracingAccelerationStructure", // DXC templated types, from https://github.com/microsoft/DirectXShaderCompiler/blob/18c9e114f9c314f93e68fbc72ce207d4ed2e65ae/tools/clang/lib/AST/ASTContextHLSL.cpp // look for `BuiltinTypeDeclBuilder` "matrix", "vector", "TextureBuffer", "ConstantBuffer", "RayQuery", "RayDesc", // Naga utilities super::writer::MODF_FUNCTION, super::writer::FREXP_FUNCTION, super::writer::EXTRACT_BITS_FUNCTION, super::writer::INSERT_BITS_FUNCTION, super::writer::SAMPLER_HEAP_VAR, super::writer::COMPARISON_SAMPLER_HEAP_VAR, super::writer::SAMPLE_EXTERNAL_TEXTURE_FUNCTION, super::writer::ABS_FUNCTION, super::writer::DIV_FUNCTION, super::writer::MOD_FUNCTION, super::writer::NEG_FUNCTION, super::writer::F2I32_FUNCTION, super::writer::F2U32_FUNCTION, super::writer::F2I64_FUNCTION, super::writer::F2U64_FUNCTION, super::writer::IMAGE_LOAD_EXTERNAL_FUNCTION, super::writer::IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION, ]; // DXC scalar types, from https://github.com/microsoft/DirectXShaderCompiler/blob/18c9e114f9c314f93e68fbc72ce207d4ed2e65ae/tools/clang/lib/AST/ASTContextHLSL.cpp#L48-L254 // + vector and matrix shorthands pub const TYPES: &[&str] = &{ const L: usize = 23 * (1 + 4 + 4 * 4); let mut res = [""; L]; let mut c = 0; /// For each scalar type, it will additionally generate vector and matrix shorthands macro_rules! generate { ([$($roots:literal),*], $x:tt) => { $( generate!(@inner push $roots); generate!(@inner $roots, $x); )* }; (@inner $root:literal, [$($x:literal),*]) => { generate!(@inner vector $root, $($x)*); generate!(@inner matrix $root, $($x)*); }; (@inner vector $root:literal, $($x:literal)*) => { $( generate!(@inner push concat!($root, $x)); )* }; (@inner matrix $root:literal, $($x:literal)*) => { // Duplicate the list generate!(@inner matrix $root, $($x)*; $($x)*); }; // The head/tail recursion: pick the first element of the first list and recursively do it for the tail. (@inner matrix $root:literal, $head:literal $($tail:literal)*; $($x:literal)*) => { $( generate!(@inner push concat!($root, $head, "x", $x)); )* generate!(@inner matrix $root, $($tail)*; $($x)*); }; // The end of iteration: we exhausted the list (@inner matrix $root:literal, ; $($x:literal)*) => {}; (@inner push $v:expr) => { res[c] = $v; c += 1; }; } generate!( [ "bool", "int", "uint", "dword", "half", "float", "double", "min10float", "min16float", "min12int", "min16int", "min16uint", "int16_t", "int32_t", "int64_t", "uint16_t", "uint32_t", "uint64_t", "float16_t", "float32_t", "float64_t", "int8_t4_packed", "uint8_t4_packed" ], ["1", "2", "3", "4"] ); debug_assert!(c == L); res }; /// The above set of reserved keywords, turned into a cached HashSet. This saves /// significant time during [`Namer::reset`](crate::proc::Namer::reset). /// /// See for benchmarks. pub static RESERVED_SET: RacyLock = RacyLock::new(|| KeywordSet::from_iter(RESERVED.iter().chain(TYPES))); pub static RESERVED_CASE_INSENSITIVE_SET: RacyLock = RacyLock::new(|| CaseInsensitiveKeywordSet::from_iter(RESERVED_CASE_INSENSITIVE)); pub const RESERVED_PREFIXES: &[&str] = &[ "__dynamic_buffer_offsets", super::help::IMAGE_STORAGE_LOAD_SCALAR_WRAPPER, super::writer::RAY_QUERY_TRACKER_VARIABLE_PREFIX, super::writer::INTERNAL_PREFIX, ]; ================================================ FILE: naga/src/back/hlsl/mod.rs ================================================ /*! Backend for [HLSL][hlsl] (High-Level Shading Language). # Supported shader model versions: - 5.0 - 5.1 - 6.0 # Layout of values in `uniform` buffers WGSL's ["Internal Layout of Values"][ilov] rules specify how each WGSL type should be stored in `uniform` and `storage` buffers. The HLSL we generate must access values in that form, even when it is not what HLSL would use normally. Matching the WGSL memory layout is a concern only for `uniform` variables. WGSL `storage` buffers are translated as HLSL `ByteAddressBuffers`, for which we generate `Load` and `Store` method calls with explicit byte offsets. WGSL pipeline inputs must be scalars or vectors; they cannot be matrices, which is where the interesting problems arise. However, when an affected type appears in a struct definition, the transformations described here are applied without consideration of where the struct is used. Access to storage buffers is implemented in `storage.rs`. Access to uniform buffers is implemented where applicable in `writer.rs`. ## Row- and column-major ordering for matrices WGSL specifies that matrices in uniform buffers are stored in column-major order. This matches HLSL's default, so one might expect things to be straightforward. Unfortunately, WGSL and HLSL disagree on what indexing a matrix means: in WGSL, `m[i]` retrieves the `i`'th *column* of `m`, whereas in HLSL it retrieves the `i`'th *row*. We want to avoid translating `m[i]` into some complicated reassembly of a vector from individually fetched components, so this is a problem. However, with a bit of trickery, it is possible to use HLSL's `m[i]` as the translation of WGSL's `m[i]`: - We declare all matrices in uniform buffers in HLSL with the `row_major` qualifier, and transpose the row and column counts: a WGSL `mat3x4`, say, becomes an HLSL `row_major float3x4`. (Note that WGSL and HLSL type names put the row and column in reverse order.) Since the HLSL type is the transpose of how WebGPU directs the user to store the data, HLSL will load all matrices transposed. - Since matrices are transposed, an HLSL indexing expression retrieves the "columns" of the intended WGSL value, as desired. - For vector-matrix multiplication, since `mul(transpose(m), v)` is equivalent to `mul(v, m)` (note the reversal of the arguments), and `mul(v, transpose(m))` is equivalent to `mul(m, v)`, we can translate WGSL `m * v` and `v * m` to HLSL by simply reversing the arguments to `mul`. ## Padding in two-row matrices An HLSL `row_major floatKx2` matrix has padding between its rows that the WGSL `matKx2` matrix it represents does not. HLSL stores all matrix rows [aligned on 16-byte boundaries][16bb], whereas WGSL says that the columns of a `matKx2` need only be [aligned as required for `vec2`][ilov], which is [eight-byte alignment][8bb]. To compensate for this, any time a `matKx2` appears in a WGSL `uniform` value or as part of a struct/array, we actually emit `K` separate `float2` members, and assemble/disassemble the matrix from its columns (in WGSL; rows in HLSL) upon load and store. For example, the following WGSL struct type: ```ignore struct Baz { m: mat3x2, } ``` is rendered as the HLSL struct type: ```ignore struct Baz { float2 m_0; float2 m_1; float2 m_2; }; ``` The `wrapped_struct_matrix` functions in `help.rs` generate HLSL helper functions to access such members, converting between the stored form and the HLSL matrix types appropriately. For example, for reading the member `m` of the `Baz` struct above, we emit: ```ignore float3x2 GetMatmOnBaz(Baz obj) { return float3x2(obj.m_0, obj.m_1, obj.m_2); } ``` We also emit an analogous `Set` function, as well as functions for accessing individual columns by dynamic index. ## Sampler Handling Due to limitations in how sampler heaps work in D3D12, we need to access samplers through a layer of indirection. Instead of directly binding samplers, we bind the entire sampler heap as both a standard and a comparison sampler heap. We then use a sampler index buffer for each bind group. This buffer is accessed in the shader to get the actual sampler index within the heap. See the wgpu_hal dx12 backend documentation for more information. # External textures Support for [`crate::ImageClass::External`] textures is implemented by lowering each external texture global variable to 3 `Texture2D`s, and a `cbuffer` of type `NagaExternalTextureParams`. This provides up to 3 planes of texture data (for example single planar RGBA, or separate Y, Cb, and Cr planes), and the parameters buffer containing information describing how to handle these correctly. The bind target to use for each of these globals is specified via [`Options::external_texture_binding_map`]. External textures are supported by WGSL's `textureDimensions()`, `textureLoad()`, and `textureSampleBaseClampToEdge()` built-in functions. These are implemented using helper functions. See the following functions for how these are generated: * `Writer::write_wrapped_image_query_function` * `Writer::write_wrapped_image_load_function` * `Writer::write_wrapped_image_sample_function` Ideally the set of global variables could be wrapped in a single struct that could conveniently be passed around. But, alas, HLSL does not allow structs to have `Texture2D` members. Fortunately, however, external textures can only be used as arguments to either built-in or user-defined functions. We therefore expand any external texture function argument to four consecutive arguments (3 textures and the params struct) when declaring user-defined functions, and ensure our built-in function implementations take the same arguments. Then, whenever we need to emit an external texture in `Writer::write_expr`, which fortunately can only ever be for a global variable or function argument, we simply emit the variable name of each of the three textures and the parameters struct in a comma-separated list. This won't win any awards for elegance, but it works for our purposes. [hlsl]: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl [ilov]: https://gpuweb.github.io/gpuweb/wgsl/#internal-value-layout [16bb]: https://github.com/microsoft/DirectXShaderCompiler/wiki/Buffer-Packing#constant-buffer-packing [8bb]: https://gpuweb.github.io/gpuweb/wgsl/#alignment-and-size */ mod conv; mod help; mod keywords; mod ray; mod storage; mod writer; use alloc::{string::String, vec::Vec}; use core::fmt::Error as FmtError; use thiserror::Error; use crate::{back, ir, proc}; /// Direct3D 12 binding information for a global variable. /// /// This type provides the HLSL-specific information Naga needs to declare and /// access an HLSL global variable that cannot be derived from the `Module` /// itself. /// /// An HLSL global variable declaration includes details that the Direct3D API /// will use to refer to it. For example: /// /// RWByteAddressBuffer s_sasm : register(u0, space2); /// /// This defines a global `s_sasm` that a Direct3D root signature would refer to /// as register `0` in register space `2` in a `UAV` descriptor range. Naga can /// infer the register's descriptor range type from the variable's address class /// (writable [`Storage`] variables are implemented by Direct3D Unordered Access /// Views, the `u` register type), but the register number and register space /// must be supplied by the user. /// /// The [`back::hlsl::Options`] structure provides `BindTarget`s for various /// situations in which Naga may need to generate an HLSL global variable, like /// [`binding_map`] for Naga global variables, or [`immediates_target`] for /// a module's sole [`Immediate`] variable. See those fields' documentation /// for details. /// /// [`Storage`]: crate::ir::AddressSpace::Storage /// [`back::hlsl::Options`]: Options /// [`binding_map`]: Options::binding_map /// [`immediates_target`]: Options::immediates_target /// [`Immediate`]: crate::ir::AddressSpace::Immediate #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct BindTarget { pub space: u8, /// For regular bindings this is the register number. /// /// For sampler bindings, this is the index to use into the bind group's sampler index buffer. pub register: u32, /// If the binding is an unsized binding array, this overrides the size. pub binding_array_size: Option, /// This is the index in the buffer at [`Options::dynamic_storage_buffer_offsets_targets`]. pub dynamic_storage_buffer_offsets_index: Option, /// This is a hint that we need to restrict indexing of vectors, matrices and arrays. /// /// If [`Options::restrict_indexing`] is also `true`, we will restrict indexing. #[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))] pub restrict_indexing: bool, } #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] /// BindTarget for dynamic storage buffer offsets pub struct OffsetsBindTarget { pub space: u8, pub register: u32, pub size: u32, } #[cfg(feature = "deserialize")] #[derive(serde::Deserialize)] struct BindingMapSerialization { resource_binding: crate::ResourceBinding, bind_target: BindTarget, } #[cfg(feature = "deserialize")] fn deserialize_binding_map<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, { use serde::Deserialize; let vec = Vec::::deserialize(deserializer)?; let mut map = BindingMap::default(); for item in vec { map.insert(item.resource_binding, item.bind_target); } Ok(map) } // Using `BTreeMap` instead of `HashMap` so that we can hash itself. pub type BindingMap = alloc::collections::BTreeMap; /// A HLSL shader model version. #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub enum ShaderModel { V5_0, V5_1, V6_0, V6_1, V6_2, V6_3, V6_4, V6_5, V6_6, V6_7, V6_8, V6_9, } impl ShaderModel { pub const fn to_str(self) -> &'static str { match self { Self::V5_0 => "5_0", Self::V5_1 => "5_1", Self::V6_0 => "6_0", Self::V6_1 => "6_1", Self::V6_2 => "6_2", Self::V6_3 => "6_3", Self::V6_4 => "6_4", Self::V6_5 => "6_5", Self::V6_6 => "6_6", Self::V6_7 => "6_7", Self::V6_8 => "6_8", Self::V6_9 => "6_9", } } } impl crate::ShaderStage { pub const fn to_hlsl_str(self) -> &'static str { match self { Self::Vertex => "vs", Self::Fragment => "ps", Self::Compute => "cs", Self::Task => "as", Self::Mesh => "ms", Self::RayGeneration | Self::AnyHit | Self::ClosestHit | Self::Miss => "lib", } } } impl crate::ImageDimension { const fn to_hlsl_str(self) -> &'static str { match self { Self::D1 => "1D", Self::D2 => "2D", Self::D3 => "3D", Self::Cube => "Cube", } } } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct SamplerIndexBufferKey { pub group: u32, } #[derive(Clone, Debug, Hash, PartialEq, Eq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[cfg_attr(feature = "deserialize", serde(default))] pub struct SamplerHeapBindTargets { pub standard_samplers: BindTarget, pub comparison_samplers: BindTarget, } impl Default for SamplerHeapBindTargets { fn default() -> Self { Self { standard_samplers: BindTarget { space: 0, register: 0, binding_array_size: None, dynamic_storage_buffer_offsets_index: None, restrict_indexing: false, }, comparison_samplers: BindTarget { space: 1, register: 0, binding_array_size: None, dynamic_storage_buffer_offsets_index: None, restrict_indexing: false, }, } } } #[cfg(feature = "deserialize")] #[derive(serde::Deserialize)] struct SamplerIndexBufferBindingSerialization { group: u32, bind_target: BindTarget, } #[cfg(feature = "deserialize")] fn deserialize_sampler_index_buffer_bindings<'de, D>( deserializer: D, ) -> Result where D: serde::Deserializer<'de>, { use serde::Deserialize; let vec = Vec::::deserialize(deserializer)?; let mut map = SamplerIndexBufferBindingMap::default(); for item in vec { map.insert( SamplerIndexBufferKey { group: item.group }, item.bind_target, ); } Ok(map) } // We use a BTreeMap here so that we can hash it. pub type SamplerIndexBufferBindingMap = alloc::collections::BTreeMap; #[cfg(feature = "deserialize")] #[derive(serde::Deserialize)] struct DynamicStorageBufferOffsetTargetSerialization { index: u32, bind_target: OffsetsBindTarget, } #[cfg(feature = "deserialize")] fn deserialize_storage_buffer_offsets<'de, D>( deserializer: D, ) -> Result where D: serde::Deserializer<'de>, { use serde::Deserialize; let vec = Vec::::deserialize(deserializer)?; let mut map = DynamicStorageBufferOffsetsTargets::default(); for item in vec { map.insert(item.index, item.bind_target); } Ok(map) } pub type DynamicStorageBufferOffsetsTargets = alloc::collections::BTreeMap; /// HLSL binding information for a Naga [`External`] image global variable. /// /// See the module documentation's section on [External textures][mod] for details. /// /// [`External`]: crate::ir::ImageClass::External /// [mod]: #external-textures #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct ExternalTextureBindTarget { /// HLSL binding information for the individual plane textures. /// /// Each of these should refer to an HLSL `Texture2D` holding one /// plane of data for the external texture. The exact meaning of each plane /// varies at runtime depending on where the external texture's data /// originated. pub planes: [BindTarget; 3], /// HLSL binding information for a buffer holding the sampling parameters. /// /// This should refer to a cbuffer of type `NagaExternalTextureParams`, that /// the code Naga generates for `textureSampleBaseClampToEdge` consults to /// decide how to combine the data in [`planes`] to get the result required /// by the spec. /// /// [`planes`]: Self::planes pub params: BindTarget, } #[cfg(feature = "deserialize")] #[derive(serde::Deserialize)] struct ExternalTextureBindingMapSerialization { resource_binding: crate::ResourceBinding, bind_target: ExternalTextureBindTarget, } #[cfg(feature = "deserialize")] fn deserialize_external_texture_binding_map<'de, D>( deserializer: D, ) -> Result where D: serde::Deserializer<'de>, { use serde::Deserialize; let vec = Vec::::deserialize(deserializer)?; let mut map = ExternalTextureBindingMap::default(); for item in vec { map.insert(item.resource_binding, item.bind_target); } Ok(map) } pub type ExternalTextureBindingMap = alloc::collections::BTreeMap; /// Shorthand result used internally by the backend type BackendResult = Result<(), Error>; #[derive(Clone, Debug, PartialEq, thiserror::Error)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub enum EntryPointError { #[error("mapping of {0:?} is missing")] MissingBinding(crate::ResourceBinding), } /// Configuration used in the [`Writer`]. #[derive(Clone, Debug, Hash, PartialEq, Eq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[cfg_attr(feature = "deserialize", serde(default))] pub struct Options { /// The hlsl shader model to be used pub shader_model: ShaderModel, /// HLSL binding information for each Naga global variable. /// /// This maps Naga [`GlobalVariable`]'s [`ResourceBinding`]s to a /// [`BindTarget`] specifying its register number and space, along with /// other details necessary to generate a full HLSL declaration for it, /// or to access its value. /// /// This must provide a [`BindTarget`] for every [`GlobalVariable`] in the /// [`Module`] that has a [`binding`]. /// /// [`GlobalVariable`]: crate::ir::GlobalVariable /// [`ResourceBinding`]: crate::ir::ResourceBinding /// [`Module`]: crate::ir::Module /// [`binding`]: crate::ir::GlobalVariable::binding #[cfg_attr( feature = "deserialize", serde(deserialize_with = "deserialize_binding_map") )] pub binding_map: BindingMap, /// Don't panic on missing bindings, instead generate any HLSL. pub fake_missing_bindings: bool, /// Add special constants to `SV_VertexIndex` and `SV_InstanceIndex`, /// to make them work like in Vulkan/Metal, with help of the host. pub special_constants_binding: Option, /// HLSL binding information for the [`Immediate`] global, if present. /// /// If a module contains a global in the [`Immediate`] address space, the /// `dx12` backend stores its value directly in the root signature as a /// series of [`D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS`], whose binding /// information is given here. /// /// [`Immediate`]: crate::ir::AddressSpace::Immediate /// [`D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS`]: https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ne-d3d12-d3d12_root_parameter_type pub immediates_target: Option, /// HLSL binding information for the sampler heap and comparison sampler heap. pub sampler_heap_target: SamplerHeapBindTargets, /// Mapping of each bind group's sampler index buffer to a bind target. #[cfg_attr( feature = "deserialize", serde(deserialize_with = "deserialize_sampler_index_buffer_bindings") )] pub sampler_buffer_binding_map: SamplerIndexBufferBindingMap, /// Bind target for dynamic storage buffer offsets #[cfg_attr( feature = "deserialize", serde(deserialize_with = "deserialize_storage_buffer_offsets") )] pub dynamic_storage_buffer_offsets_targets: DynamicStorageBufferOffsetsTargets, #[cfg_attr( feature = "deserialize", serde(deserialize_with = "deserialize_external_texture_binding_map") )] /// HLSL binding information for [`External`] image global variables. /// /// See [`ExternalTextureBindTarget`] for details. /// /// [`External`]: crate::ir::ImageClass::External pub external_texture_binding_map: ExternalTextureBindingMap, /// Should workgroup variables be zero initialized (by polyfilling)? pub zero_initialize_workgroup_memory: bool, /// Should we restrict indexing of vectors, matrices and arrays? pub restrict_indexing: bool, /// If set, loops will have code injected into them, forcing the compiler /// to think the number of iterations is bounded. pub force_loop_bounding: bool, /// if set, ray queries will get a variable to track their state to prevent /// misuse. pub ray_query_initialization_tracking: bool, } impl Default for Options { fn default() -> Self { Options { shader_model: ShaderModel::V5_1, binding_map: BindingMap::default(), fake_missing_bindings: true, special_constants_binding: None, sampler_heap_target: SamplerHeapBindTargets::default(), sampler_buffer_binding_map: alloc::collections::BTreeMap::default(), immediates_target: None, dynamic_storage_buffer_offsets_targets: alloc::collections::BTreeMap::new(), external_texture_binding_map: ExternalTextureBindingMap::default(), zero_initialize_workgroup_memory: true, restrict_indexing: true, force_loop_bounding: true, ray_query_initialization_tracking: true, } } } impl Options { fn resolve_resource_binding( &self, res_binding: &crate::ResourceBinding, ) -> Result { match self.binding_map.get(res_binding) { Some(target) => Ok(*target), None if self.fake_missing_bindings => Ok(BindTarget { space: res_binding.group as u8, register: res_binding.binding, binding_array_size: None, dynamic_storage_buffer_offsets_index: None, restrict_indexing: false, }), None => Err(EntryPointError::MissingBinding(*res_binding)), } } fn resolve_external_texture_resource_binding( &self, res_binding: &crate::ResourceBinding, ) -> Result { match self.external_texture_binding_map.get(res_binding) { Some(target) => Ok(*target), None if self.fake_missing_bindings => { let fake = BindTarget { space: res_binding.group as u8, register: res_binding.binding, binding_array_size: None, dynamic_storage_buffer_offsets_index: None, restrict_indexing: false, }; Ok(ExternalTextureBindTarget { planes: [fake, fake, fake], params: fake, }) } None => Err(EntryPointError::MissingBinding(*res_binding)), } } } /// Reflection info for entry point names. #[derive(Default)] pub struct ReflectionInfo { /// Mapping of the entry point names. /// /// Each item in the array corresponds to an entry point index. The real entry point name may be different if one of the /// reserved words are used. /// /// Note: Some entry points may fail translation because of missing bindings. pub entry_point_names: Vec>, } /// A subset of options that are meant to be changed per pipeline. #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[cfg_attr(feature = "deserialize", serde(default))] pub struct PipelineOptions { /// The entry point to write. /// /// Entry points are identified by a shader stage specification, /// and a name. /// /// If `None`, all entry points will be written. If `Some` and the entry /// point is not found, an error will be thrown while writing. pub entry_point: Option<(ir::ShaderStage, String)>, } #[derive(Error, Debug)] pub enum Error { #[error(transparent)] IoError(#[from] FmtError), #[error("A scalar with an unsupported width was requested: {0:?}")] UnsupportedScalar(crate::Scalar), #[error("{0}")] Unimplemented(String), // TODO: Error used only during development #[error("{0}")] Custom(String), #[error("overrides should not be present at this stage")] Override, #[error(transparent)] ResolveArraySizeError(#[from] proc::ResolveArraySizeError), #[error("entry point with stage {0:?} and name '{1}' not found")] EntryPointNotFound(ir::ShaderStage, String), #[error("requires shader model {1:?} for reason: {0}")] ShaderModelTooLow(String, ShaderModel), } #[derive(PartialEq, Eq, Hash)] enum WrappedType { ZeroValue(help::WrappedZeroValue), ArrayLength(help::WrappedArrayLength), ImageSample(help::WrappedImageSample), ImageQuery(help::WrappedImageQuery), ImageLoad(help::WrappedImageLoad), ImageLoadScalar(crate::Scalar), Constructor(help::WrappedConstructor), StructMatrixAccess(help::WrappedStructMatrixAccess), MatCx2(help::WrappedMatCx2), Math(help::WrappedMath), UnaryOp(help::WrappedUnaryOp), BinaryOp(help::WrappedBinaryOp), Cast(help::WrappedCast), } #[derive(Default)] struct Wrapped { types: crate::FastHashSet, /// If true, the sampler heaps have been written out. sampler_heaps: bool, // Mapping from SamplerIndexBufferKey to the name the namer returned. sampler_index_buffers: crate::FastHashMap, } impl Wrapped { fn insert(&mut self, r#type: WrappedType) -> bool { self.types.insert(r#type) } fn clear(&mut self) { self.types.clear(); } } /// A fragment entry point to be considered when generating HLSL for the output interface of vertex /// entry points. /// /// This is provided as an optional parameter to [`Writer::write`]. /// /// If this is provided, vertex outputs will be removed if they are not inputs of this fragment /// entry point. This is necessary for generating correct HLSL when some of the vertex shader /// outputs are not consumed by the fragment shader. pub struct FragmentEntryPoint<'a> { module: &'a crate::Module, func: &'a crate::Function, } impl<'a> FragmentEntryPoint<'a> { /// Returns `None` if the entry point with the provided name can't be found or isn't a fragment /// entry point. pub fn new(module: &'a crate::Module, ep_name: &'a str) -> Option { module .entry_points .iter() .find(|ep| ep.name == ep_name) .filter(|ep| ep.stage == crate::ShaderStage::Fragment) .map(|ep| Self { module, func: &ep.function, }) } } pub struct Writer<'a, W> { out: W, names: crate::FastHashMap, namer: proc::Namer, /// HLSL backend options options: &'a Options, /// Per-stage backend options pipeline_options: &'a PipelineOptions, /// Information about entry point arguments and result types. entry_point_io: crate::FastHashMap, /// Set of expressions that have associated temporary variables named_expressions: crate::NamedExpressions, wrapped: Wrapped, written_committed_intersection: bool, written_candidate_intersection: bool, continue_ctx: back::continue_forward::ContinueCtx, /// A reference to some part of a global variable, lowered to a series of /// byte offset calculations. /// /// See the [`storage`] module for background on why we need this. /// /// Each [`SubAccess`] in the vector is a lowering of some [`Access`] or /// [`AccessIndex`] expression to the level of byte strides and offsets. See /// [`SubAccess`] for details. /// /// This field is a member of [`Writer`] solely to allow re-use of /// the `Vec`'s dynamic allocation. The value is no longer needed /// once HLSL for the access has been generated. /// /// [`Storage`]: crate::AddressSpace::Storage /// [`SubAccess`]: storage::SubAccess /// [`Access`]: crate::Expression::Access /// [`AccessIndex`]: crate::Expression::AccessIndex temp_access_chain: Vec, need_bake_expressions: back::NeedBakeExpressions, } pub fn supported_capabilities() -> crate::valid::Capabilities { use crate::valid::Capabilities as Caps; Caps::IMMEDIATES | Caps::FLOAT64 // Unsupported by wgpu but supported by naga | Caps::PRIMITIVE_INDEX | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY // No BUFFER_BINDING_ARRAY | Caps::STORAGE_TEXTURE_BINDING_ARRAY // No STORAGE_BUFFER_BINDING_ARRAY | Caps::ACCELERATION_STRUCTURE_BINDING_ARRAY // No CLIP_DISTANCES // No CULL_DISTANCE | Caps::STORAGE_TEXTURE_16BIT_NORM_FORMATS | Caps::MULTIVIEW // No EARLY_DEPTH_TEST | Caps::MULTISAMPLED_SHADING | Caps::RAY_QUERY | Caps::DUAL_SOURCE_BLENDING | Caps::CUBE_ARRAY_TEXTURES | Caps::SHADER_INT64 | Caps::SUBGROUP // No SUBGROUP_BARRIER // No SUBGROUP_VERTEX_STAGE | Caps::SHADER_INT64_ATOMIC_MIN_MAX | Caps::SHADER_INT64_ATOMIC_ALL_OPS // No SHADER_FLOAT32_ATOMIC | Caps::TEXTURE_ATOMIC | Caps::TEXTURE_INT64_ATOMIC // No RAY_HIT_VERTEX_POSITION | Caps::SHADER_FLOAT16 | Caps::TEXTURE_EXTERNAL | Caps::SHADER_FLOAT16_IN_FLOAT32 | Caps::SHADER_BARYCENTRICS // No MESH_SHADER // No MESH_SHADER_POINT_TOPOLOGY | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING // No BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING | Caps::STORAGE_TEXTURE_BINDING_ARRAY_NON_UNIFORM_INDEXING | Caps::STORAGE_BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING // No COOPERATIVE_MATRIX // No PER_VERTEX // No RAY_TRACING_PIPELINE // No DRAW_INDEX // No MEMORY_DECORATION_VOLATILE | Caps::MEMORY_DECORATION_COHERENT } ================================================ FILE: naga/src/back/hlsl/ray.rs ================================================ use alloc::{ format, string::{String, ToString}, vec, vec::Vec, }; use core::fmt::Write; use crate::{ back::{hlsl::BackendResult, Baked, Level}, Handle, }; use crate::{RayQueryIntersection, TypeInner}; impl super::Writer<'_, W> { // https://sakibsaikia.github.io/graphics/2022/01/04/Nan-Checks-In-HLSL.html suggests that isnan may not work, unsure if this has changed. fn write_not_finite(&mut self, expr: &str) -> BackendResult { self.write_contains_flags(&format!("asuint({expr})"), 0x7f800000) } fn write_nan(&mut self, expr: &str) -> BackendResult { write!(self.out, "(")?; self.write_not_finite(expr)?; write!(self.out, " && ((asuint({expr}) & 0x7fffff) != 0))")?; Ok(()) } fn write_contains_flags(&mut self, expr: &str, flags: u32) -> BackendResult { write!(self.out, "(({expr} & {flags}) == {flags})")?; Ok(()) } // constructs hlsl RayDesc from wgsl RayDesc pub(super) fn write_ray_desc_from_ray_desc_constructor_function( &mut self, module: &crate::Module, ) -> BackendResult { write!(self.out, "RayDesc RayDescFromRayDesc_(")?; self.write_type(module, module.special_types.ray_desc.unwrap())?; writeln!(self.out, " arg0) {{")?; writeln!(self.out, " RayDesc ret = (RayDesc)0;")?; writeln!(self.out, " ret.Origin = arg0.origin;")?; writeln!(self.out, " ret.TMin = arg0.tmin;")?; writeln!(self.out, " ret.Direction = arg0.dir;")?; writeln!(self.out, " ret.TMax = arg0.tmax;")?; writeln!(self.out, " return ret;")?; writeln!(self.out, "}}")?; writeln!(self.out)?; Ok(()) } pub(super) fn write_committed_intersection_function( &mut self, module: &crate::Module, ) -> BackendResult { self.write_type(module, module.special_types.ray_intersection.unwrap())?; write!(self.out, " GetCommittedIntersection(")?; self.write_value_type( module, &TypeInner::RayQuery { vertex_return: false, }, )?; write!(self.out, " rq, ")?; self.write_value_type(module, &TypeInner::Scalar(crate::Scalar::U32))?; writeln!(self.out, " rq_tracker) {{")?; write!(self.out, " ")?; self.write_type(module, module.special_types.ray_intersection.unwrap())?; write!(self.out, " ret = (")?; self.write_type(module, module.special_types.ray_intersection.unwrap())?; writeln!(self.out, ")0;")?; let mut extra_level = Level(0); if self.options.ray_query_initialization_tracking { // *Technically*, `CommittedStatus` is valid as long as the ray query is initialized, but the metal backend // doesn't support this function unless it has finished traversal, so to encourage portable behaviour we // disallow it here too. write!(self.out, " if (")?; self.write_contains_flags( "rq_tracker", crate::back::RayQueryPoint::FINISHED_TRAVERSAL.bits(), )?; writeln!(self.out, ") {{")?; extra_level = extra_level.next(); } writeln!( self.out, " {extra_level}ret.kind = rq.CommittedStatus();" )?; writeln!( self.out, " {extra_level}if( rq.CommittedStatus() == COMMITTED_NOTHING) {{}} else {{" )?; writeln!(self.out, " {extra_level}ret.t = rq.CommittedRayT();")?; writeln!( self.out, " {extra_level}ret.instance_custom_data = rq.CommittedInstanceID();" )?; writeln!( self.out, " {extra_level}ret.instance_index = rq.CommittedInstanceIndex();" )?; writeln!( self.out, " {extra_level}ret.sbt_record_offset = rq.CommittedInstanceContributionToHitGroupIndex();" )?; writeln!( self.out, " {extra_level}ret.geometry_index = rq.CommittedGeometryIndex();" )?; writeln!( self.out, " {extra_level}ret.primitive_index = rq.CommittedPrimitiveIndex();" )?; writeln!( self.out, " {extra_level}if( rq.CommittedStatus() == COMMITTED_TRIANGLE_HIT ) {{" )?; writeln!( self.out, " {extra_level}ret.barycentrics = rq.CommittedTriangleBarycentrics();" )?; writeln!( self.out, " {extra_level}ret.front_face = rq.CommittedTriangleFrontFace();" )?; writeln!(self.out, " {extra_level}}}")?; writeln!( self.out, " {extra_level}ret.object_to_world = rq.CommittedObjectToWorld4x3();" )?; writeln!( self.out, " {extra_level}ret.world_to_object = rq.CommittedWorldToObject4x3();" )?; writeln!(self.out, " {extra_level}}}")?; if self.options.ray_query_initialization_tracking { writeln!(self.out, " }}")?; } writeln!(self.out, " return ret;")?; writeln!(self.out, "}}")?; writeln!(self.out)?; Ok(()) } pub(super) fn write_candidate_intersection_function( &mut self, module: &crate::Module, ) -> BackendResult { self.write_type(module, module.special_types.ray_intersection.unwrap())?; write!(self.out, " GetCandidateIntersection(")?; self.write_value_type( module, &TypeInner::RayQuery { vertex_return: false, }, )?; write!(self.out, " rq, ")?; self.write_value_type(module, &TypeInner::Scalar(crate::Scalar::U32))?; writeln!(self.out, " rq_tracker) {{")?; write!(self.out, " ")?; self.write_type(module, module.special_types.ray_intersection.unwrap())?; write!(self.out, " ret = (")?; self.write_type(module, module.special_types.ray_intersection.unwrap())?; writeln!(self.out, ")0;")?; let mut extra_level = Level(0); if self.options.ray_query_initialization_tracking { write!(self.out, " if (")?; self.write_contains_flags("rq_tracker", crate::back::RayQueryPoint::PROCEED.bits())?; write!(self.out, " && !")?; self.write_contains_flags( "rq_tracker", crate::back::RayQueryPoint::FINISHED_TRAVERSAL.bits(), )?; writeln!(self.out, ") {{")?; extra_level = extra_level.next(); } writeln!( self.out, " {extra_level}CANDIDATE_TYPE kind = rq.CandidateType();" )?; writeln!( self.out, " {extra_level}if (kind == CANDIDATE_NON_OPAQUE_TRIANGLE) {{" )?; writeln!( self.out, " {extra_level}ret.kind = {};", RayQueryIntersection::Triangle as u32 )?; writeln!( self.out, " {extra_level}ret.t = rq.CandidateTriangleRayT();" )?; writeln!( self.out, " {extra_level}ret.barycentrics = rq.CandidateTriangleBarycentrics();" )?; writeln!( self.out, " {extra_level}ret.front_face = rq.CandidateTriangleFrontFace();" )?; writeln!(self.out, " {extra_level}}} else {{")?; writeln!( self.out, " {extra_level}ret.kind = {};", RayQueryIntersection::Aabb as u32 )?; writeln!(self.out, " {extra_level}}}")?; writeln!( self.out, " {extra_level}ret.instance_custom_data = rq.CandidateInstanceID();" )?; writeln!( self.out, " {extra_level}ret.instance_index = rq.CandidateInstanceIndex();" )?; writeln!( self.out, " {extra_level}ret.sbt_record_offset = rq.CandidateInstanceContributionToHitGroupIndex();" )?; writeln!( self.out, " {extra_level}ret.geometry_index = rq.CandidateGeometryIndex();" )?; writeln!( self.out, " {extra_level}ret.primitive_index = rq.CandidatePrimitiveIndex();" )?; writeln!( self.out, " {extra_level}ret.object_to_world = rq.CandidateObjectToWorld4x3();" )?; writeln!( self.out, " {extra_level}ret.world_to_object = rq.CandidateWorldToObject4x3();" )?; if self.options.ray_query_initialization_tracking { writeln!(self.out, " }}")?; } writeln!(self.out, " return ret;")?; writeln!(self.out, "}}")?; writeln!(self.out)?; Ok(()) } #[expect(clippy::too_many_arguments)] pub(super) fn write_initialize_function( &mut self, module: &crate::Module, mut level: Level, query: Handle, acceleration_structure: Handle, descriptor: Handle, rq_tracker: &str, func_ctx: &crate::back::FunctionCtx<'_>, ) -> BackendResult { let base_level = level; // This prevents variables flowing down a level and causing compile errors. writeln!(self.out, "{level}{{")?; level = level.next(); write!(self.out, "{level}")?; self.write_type( module, module .special_types .ray_desc .expect("should have been generated"), )?; write!(self.out, " naga_desc = ")?; self.write_expr(module, descriptor, func_ctx)?; writeln!(self.out, ";")?; if self.options.ray_query_initialization_tracking { // Validate ray extents https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html#ray-extents // just for convenience writeln!(self.out, "{level}float naga_tmin = naga_desc.tmin;")?; writeln!(self.out, "{level}float naga_tmax = naga_desc.tmax;")?; writeln!(self.out, "{level}float3 naga_origin = naga_desc.origin;")?; writeln!(self.out, "{level}float3 naga_dir = naga_desc.dir;")?; writeln!(self.out, "{level}uint naga_flags = naga_desc.flags;")?; write!( self.out, "{level}bool naga_tmin_valid = (naga_tmin >= 0.0) && (naga_tmin <= naga_tmax) && !" )?; self.write_nan("naga_tmin")?; writeln!(self.out, ";")?; write!(self.out, "{level}bool naga_tmax_valid = !")?; self.write_nan("naga_tmax")?; writeln!(self.out, ";")?; // Unlike Vulkan it seems that for DX12, it seems only NaN components of the origin and direction are invalid write!(self.out, "{level}bool naga_origin_valid = !any(")?; self.write_nan("naga_origin")?; writeln!(self.out, ");")?; write!(self.out, "{level}bool naga_dir_valid = !any(")?; self.write_nan("naga_dir")?; writeln!(self.out, ");")?; write!(self.out, "{level}bool naga_contains_opaque = ")?; self.write_contains_flags("naga_flags", crate::RayFlag::FORCE_OPAQUE.bits())?; writeln!(self.out, ";")?; write!(self.out, "{level}bool naga_contains_no_opaque = ")?; self.write_contains_flags("naga_flags", crate::RayFlag::FORCE_NO_OPAQUE.bits())?; writeln!(self.out, ";")?; write!(self.out, "{level}bool naga_contains_cull_opaque = ")?; self.write_contains_flags("naga_flags", crate::RayFlag::CULL_OPAQUE.bits())?; writeln!(self.out, ";")?; write!(self.out, "{level}bool naga_contains_cull_no_opaque = ")?; self.write_contains_flags("naga_flags", crate::RayFlag::CULL_NO_OPAQUE.bits())?; writeln!(self.out, ";")?; write!(self.out, "{level}bool naga_contains_cull_front = ")?; self.write_contains_flags("naga_flags", crate::RayFlag::CULL_FRONT_FACING.bits())?; writeln!(self.out, ";")?; write!(self.out, "{level}bool naga_contains_cull_back = ")?; self.write_contains_flags("naga_flags", crate::RayFlag::CULL_BACK_FACING.bits())?; writeln!(self.out, ";")?; write!(self.out, "{level}bool naga_contains_skip_triangles = ")?; self.write_contains_flags("naga_flags", crate::RayFlag::SKIP_TRIANGLES.bits())?; writeln!(self.out, ";")?; write!(self.out, "{level}bool naga_contains_skip_aabbs = ")?; self.write_contains_flags("naga_flags", crate::RayFlag::SKIP_AABBS.bits())?; writeln!(self.out, ";")?; // A textified version of the same in the spirv writer fn less_than_two_true(mut bools: Vec<&str>) -> Result { assert!(bools.len() > 1, "Must have multiple booleans!"); let mut final_expr = String::new(); while let Some(last_bool) = bools.pop() { for &bool in &bools { if !final_expr.is_empty() { final_expr.push_str("||"); } write!(final_expr, " ({last_bool} && {bool}) ")?; } } Ok(final_expr) } writeln!( self.out, "{level}bool naga_contains_skip_triangles_aabbs = {};", less_than_two_true(vec![ "naga_contains_skip_triangles", "naga_contains_skip_aabbs" ])? )?; writeln!( self.out, "{level}bool naga_contains_skip_triangles_cull = {};", less_than_two_true(vec![ "naga_contains_skip_triangles", "naga_contains_cull_back", "naga_contains_cull_front" ])? )?; writeln!( self.out, "{level}bool naga_contains_multiple_opaque = {};", less_than_two_true(vec![ "naga_contains_opaque", "naga_contains_no_opaque", "naga_contains_cull_opaque", "naga_contains_cull_no_opaque" ])? )?; writeln!( self.out, "{level}if (naga_tmin_valid && naga_tmax_valid && naga_origin_valid && naga_dir_valid && !(naga_contains_skip_triangles_aabbs || naga_contains_skip_triangles_cull || naga_contains_multiple_opaque)) {{" )?; level = level.next(); writeln!( self.out, "{level}{rq_tracker} = {rq_tracker} | {};", crate::back::RayQueryPoint::INITIALIZED.bits() )?; } write!(self.out, "{level}")?; self.write_expr(module, query, func_ctx)?; write!(self.out, ".TraceRayInline(")?; self.write_expr(module, acceleration_structure, func_ctx)?; writeln!( self.out, ", naga_desc.flags, naga_desc.cull_mask, RayDescFromRayDesc_(naga_desc));" )?; if self.options.ray_query_initialization_tracking { writeln!(self.out, "{base_level} }}")?; } writeln!(self.out, "{base_level}}}")?; Ok(()) } pub(super) fn write_proceed( &mut self, module: &crate::Module, mut level: Level, query: Handle, result: Handle, rq_tracker: &str, func_ctx: &crate::back::FunctionCtx<'_>, ) -> BackendResult { let base_level = level; write!(self.out, "{level}")?; let name = Baked(result).to_string(); writeln!(self.out, "bool {name} = false;")?; // This prevents variables flowing down a level and causing compile errors. if self.options.ray_query_initialization_tracking { writeln!(self.out, "{level}{{")?; level = level.next(); write!(self.out, "{level}bool naga_has_initialized = ")?; self.write_contains_flags(rq_tracker, crate::back::RayQueryPoint::INITIALIZED.bits())?; writeln!(self.out, ";")?; write!(self.out, "{level}bool naga_has_finished = ")?; self.write_contains_flags( rq_tracker, crate::back::RayQueryPoint::FINISHED_TRAVERSAL.bits(), )?; writeln!(self.out, ";")?; writeln!( self.out, "{level}if (naga_has_initialized && !naga_has_finished) {{" )?; level = level.next(); } write!(self.out, "{level}{name} = ")?; self.write_expr(module, query, func_ctx)?; writeln!(self.out, ".Proceed();")?; if self.options.ray_query_initialization_tracking { writeln!( self.out, "{level}{rq_tracker} = {rq_tracker} | {};", crate::back::RayQueryPoint::PROCEED.bits() )?; writeln!( self.out, "{level}if (!{name}) {{ {rq_tracker} = {rq_tracker} | {}; }}", crate::back::RayQueryPoint::FINISHED_TRAVERSAL.bits() )?; writeln!(self.out, "{base_level}}}}}")?; } self.named_expressions.insert(result, name); Ok(()) } pub(super) fn write_generate_intersection( &mut self, module: &crate::Module, mut level: Level, query: Handle, hit_t: Handle, rq_tracker: &str, func_ctx: &crate::back::FunctionCtx<'_>, ) -> BackendResult { let base_level = level; if self.options.ray_query_initialization_tracking { write!(self.out, "{level}if (")?; self.write_contains_flags(rq_tracker, crate::back::RayQueryPoint::PROCEED.bits())?; write!(self.out, " && !")?; self.write_contains_flags( rq_tracker, crate::back::RayQueryPoint::FINISHED_TRAVERSAL.bits(), )?; writeln!(self.out, ") {{")?; level = level.next(); write!(self.out, "{level}CANDIDATE_TYPE naga_kind = ")?; self.write_expr(module, query, func_ctx)?; writeln!(self.out, ".CandidateType();")?; write!(self.out, "{level}float naga_tmin = ")?; self.write_expr(module, query, func_ctx)?; writeln!(self.out, ".RayTMin();")?; write!(self.out, "{level}float naga_tcurrentmax = ")?; self.write_expr(module, query, func_ctx)?; // This gets initialized to tmax and is updated after each intersection is committed so is valid to call. // Note: there is a bug in DXC's spirv backend that makes this technically UB in spirv, but HLSL backend // is intended for DXIL, so it should be fine (hopefully). writeln!(self.out, ".CommittedRayT();")?; write!( self.out, "{level}if ((naga_kind == CANDIDATE_PROCEDURAL_PRIMITIVE) && (naga_tmin <=" )?; self.write_expr(module, hit_t, func_ctx)?; write!(self.out, ") && (")?; self.write_expr(module, hit_t, func_ctx)?; writeln!(self.out, " <= naga_tcurrentmax)) {{")?; level = level.next(); } write!(self.out, "{level}")?; self.write_expr(module, query, func_ctx)?; write!(self.out, ".CommitProceduralPrimitiveHit(")?; self.write_expr(module, hit_t, func_ctx)?; writeln!(self.out, ");")?; if self.options.ray_query_initialization_tracking { writeln!(self.out, "{base_level}}}}}")?; } Ok(()) } pub(super) fn write_confirm_intersection( &mut self, module: &crate::Module, mut level: Level, query: Handle, rq_tracker: &str, func_ctx: &crate::back::FunctionCtx<'_>, ) -> BackendResult { let base_level = level; if self.options.ray_query_initialization_tracking { write!(self.out, "{level}if (")?; self.write_contains_flags(rq_tracker, crate::back::RayQueryPoint::PROCEED.bits())?; write!(self.out, " && !")?; self.write_contains_flags( rq_tracker, crate::back::RayQueryPoint::FINISHED_TRAVERSAL.bits(), )?; writeln!(self.out, ") {{")?; level = level.next(); write!(self.out, "{level}CANDIDATE_TYPE naga_kind = ")?; self.write_expr(module, query, func_ctx)?; writeln!(self.out, ".CandidateType();")?; writeln!( self.out, "{level}if (naga_kind == CANDIDATE_NON_OPAQUE_TRIANGLE) {{" )?; level = level.next(); } write!(self.out, "{level}")?; self.write_expr(module, query, func_ctx)?; writeln!(self.out, ".CommitNonOpaqueTriangleHit();")?; if self.options.ray_query_initialization_tracking { writeln!(self.out, "{base_level}}}}}")?; } Ok(()) } pub(super) fn write_terminate( &mut self, module: &crate::Module, mut level: Level, query: Handle, rq_tracker: &str, func_ctx: &crate::back::FunctionCtx<'_>, ) -> BackendResult { let base_level = level; if self.options.ray_query_initialization_tracking { write!(self.out, "{level}if (")?; // RayQuery::Abort() can be called any time after RayQuery::TraceRayInline() has been called. // from https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html#rayquery-abort self.write_contains_flags(rq_tracker, crate::back::RayQueryPoint::INITIALIZED.bits())?; writeln!(self.out, ") {{")?; level = level.next(); } write!(self.out, "{level}")?; self.write_expr(module, query, func_ctx)?; writeln!(self.out, ".Abort();")?; if self.options.ray_query_initialization_tracking { writeln!(self.out, "{base_level}}}")?; } Ok(()) } } ================================================ FILE: naga/src/back/hlsl/storage.rs ================================================ /*! Generating accesses to [`ByteAddressBuffer`] contents. Naga IR globals in the [`Storage`] address space are rendered as [`ByteAddressBuffer`]s or [`RWByteAddressBuffer`]s in HLSL. These buffers don't have HLSL types (structs, arrays, etc.); instead, they are just raw blocks of bytes, with methods to load and store values of specific types at particular byte offsets. This means that Naga must translate chains of [`Access`] and [`AccessIndex`] expressions into HLSL expressions that compute byte offsets into the buffer. To generate code for a [`Storage`] access: - Call [`Writer::fill_access_chain`] on the expression referring to the value. This populates [`Writer::temp_access_chain`] with the appropriate byte offset calculations, as a vector of [`SubAccess`] values. - Call [`Writer::write_storage_address`] to emit an HLSL expression for a given slice of [`SubAccess`] values. Naga IR expressions can operate on composite values of any type, but [`ByteAddressBuffer`] and [`RWByteAddressBuffer`] have only a fixed set of `Load` and `Store` methods, to access one through four consecutive 32-bit values. To synthesize a Naga access, you can initialize [`temp_access_chain`] to refer to the composite, and then temporarily push and pop additional steps on [`Writer::temp_access_chain`] to generate accesses to the individual elements/members. The [`temp_access_chain`] field is a member of [`Writer`] solely to allow re-use of the `Vec`'s dynamic allocation. Its value is no longer needed once HLSL for the access has been generated. Note about DXC and Load/Store functions: DXC's HLSL has a generic [`Load` and `Store`] function for [`ByteAddressBuffer`] and [`RWByteAddressBuffer`]. This is not available in FXC's HLSL, so we use it only for types that are only available in DXC. Notably 64 and 16 bit types. FXC's HLSL has functions Load, Load2, Load3, and Load4 and Store, Store2, Store3, Store4. This loads/stores a vector of length 1, 2, 3, or 4. We use that for 32bit types, bitcasting to the correct type if necessary. [`Storage`]: crate::AddressSpace::Storage [`ByteAddressBuffer`]: https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-byteaddressbuffer [`RWByteAddressBuffer`]: https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-rwbyteaddressbuffer [`Access`]: crate::Expression::Access [`AccessIndex`]: crate::Expression::AccessIndex [`Writer::fill_access_chain`]: super::Writer::fill_access_chain [`Writer::write_storage_address`]: super::Writer::write_storage_address [`Writer::temp_access_chain`]: super::Writer::temp_access_chain [`temp_access_chain`]: super::Writer::temp_access_chain [`Writer`]: super::Writer [`Load` and `Store`]: https://github.com/microsoft/DirectXShaderCompiler/wiki/ByteAddressBuffer-Load-Store-Additions */ use alloc::format; use core::{fmt, mem}; use super::{super::FunctionCtx, BackendResult, Error}; use crate::{ proc::{Alignment, NameKey, TypeResolution}, Handle, }; const STORE_TEMP_NAME: &str = "_value"; /// One step in accessing a [`Storage`] global's component or element. /// /// [`Writer::temp_access_chain`] holds a series of these structures, /// describing how to compute the byte offset of a particular element /// or member of some global variable in the [`Storage`] address /// space. /// /// [`Writer::temp_access_chain`]: super::Writer::temp_access_chain /// [`Storage`]: crate::AddressSpace::Storage #[derive(Debug)] pub(super) enum SubAccess { BufferOffset { group: u32, offset: u32, }, /// Add the given byte offset. This is used for struct members, or /// known components of a vector or matrix. In all those cases, /// the byte offset is a compile-time constant. Offset(u32), /// Scale `value` by `stride`, and add that to the current byte /// offset. This is used to compute the offset of an array element /// whose index is computed at runtime. Index { value: Handle, stride: u32, }, } pub(super) enum StoreValue { Expression(Handle), TempIndex { depth: usize, index: u32, ty: TypeResolution, }, TempAccess { depth: usize, base: Handle, member_index: u32, }, // Access to a single column of a Cx2 matrix within a struct TempColumnAccess { depth: usize, base: Handle, member_index: u32, column: u32, }, } impl super::Writer<'_, W> { pub(super) fn write_storage_address( &mut self, module: &crate::Module, chain: &[SubAccess], func_ctx: &FunctionCtx, ) -> BackendResult { if chain.is_empty() { write!(self.out, "0")?; } for (i, access) in chain.iter().enumerate() { if i != 0 { write!(self.out, "+")?; } match *access { SubAccess::BufferOffset { group, offset } => { write!(self.out, "__dynamic_buffer_offsets{group}._{offset}")?; } SubAccess::Offset(offset) => { write!(self.out, "{offset}")?; } SubAccess::Index { value, stride } => { self.write_expr(module, value, func_ctx)?; write!(self.out, "*{stride}")?; } } } Ok(()) } fn write_storage_load_sequence>( &mut self, module: &crate::Module, var_handle: Handle, sequence: I, func_ctx: &FunctionCtx, ) -> BackendResult { for (i, (ty_resolution, offset)) in sequence.enumerate() { // add the index temporarily self.temp_access_chain.push(SubAccess::Offset(offset)); if i != 0 { write!(self.out, ", ")?; }; self.write_storage_load(module, var_handle, ty_resolution, func_ctx)?; self.temp_access_chain.pop(); } Ok(()) } /// Emit code to access a [`Storage`] global's component. /// /// Emit HLSL to access the component of `var_handle`, a global /// variable in the [`Storage`] address space, whose type is /// `result_ty` and whose location within the global is given by /// [`self.temp_access_chain`]. See the [`storage`] module's /// documentation for background. /// /// [`Storage`]: crate::AddressSpace::Storage /// [`self.temp_access_chain`]: super::Writer::temp_access_chain pub(super) fn write_storage_load( &mut self, module: &crate::Module, var_handle: Handle, result_ty: TypeResolution, func_ctx: &FunctionCtx, ) -> BackendResult { match *result_ty.inner_with(&module.types) { crate::TypeInner::Scalar(scalar) => { // working around the borrow checker in `self.write_expr` let chain = mem::take(&mut self.temp_access_chain); let var_name = &self.names[&NameKey::GlobalVariable(var_handle)]; // See note about DXC and Load/Store in the module's documentation. if scalar.width == 4 { let cast = scalar.kind.to_hlsl_cast(); write!(self.out, "{cast}({var_name}.Load(")?; } else { let ty = scalar.to_hlsl_str()?; write!(self.out, "{var_name}.Load<{ty}>(")?; }; self.write_storage_address(module, &chain, func_ctx)?; write!(self.out, ")")?; if scalar.width == 4 { write!(self.out, ")")?; } self.temp_access_chain = chain; } crate::TypeInner::Vector { size, scalar } => { // working around the borrow checker in `self.write_expr` let chain = mem::take(&mut self.temp_access_chain); let var_name = &self.names[&NameKey::GlobalVariable(var_handle)]; let size = size as u8; // See note about DXC and Load/Store in the module's documentation. if scalar.width == 4 { let cast = scalar.kind.to_hlsl_cast(); write!(self.out, "{cast}({var_name}.Load{size}(")?; } else { let ty = scalar.to_hlsl_str()?; write!(self.out, "{var_name}.Load<{ty}{size}>(")?; }; self.write_storage_address(module, &chain, func_ctx)?; write!(self.out, ")")?; if scalar.width == 4 { write!(self.out, ")")?; } self.temp_access_chain = chain; } crate::TypeInner::Matrix { columns, rows, scalar, } => { write!( self.out, "{}{}x{}(", scalar.to_hlsl_str()?, columns as u8, rows as u8, )?; // Note: Matrices containing vec3s, due to padding, act like they contain vec4s. let row_stride = Alignment::from(rows) * scalar.width as u32; let iter = (0..columns as u32).map(|i| { let ty_inner = crate::TypeInner::Vector { size: rows, scalar }; (TypeResolution::Value(ty_inner), i * row_stride) }); self.write_storage_load_sequence(module, var_handle, iter, func_ctx)?; write!(self.out, ")")?; } crate::TypeInner::Array { base, size: crate::ArraySize::Constant(size), stride, } => { let constructor = super::help::WrappedConstructor { ty: result_ty.handle().unwrap(), }; self.write_wrapped_constructor_function_name(module, constructor)?; write!(self.out, "(")?; let iter = (0..size.get()).map(|i| (TypeResolution::Handle(base), stride * i)); self.write_storage_load_sequence(module, var_handle, iter, func_ctx)?; write!(self.out, ")")?; } crate::TypeInner::Struct { ref members, .. } => { let constructor = super::help::WrappedConstructor { ty: result_ty.handle().unwrap(), }; self.write_wrapped_constructor_function_name(module, constructor)?; write!(self.out, "(")?; let iter = members .iter() .map(|m| (TypeResolution::Handle(m.ty), m.offset)); self.write_storage_load_sequence(module, var_handle, iter, func_ctx)?; write!(self.out, ")")?; } _ => unreachable!(), } Ok(()) } fn write_store_value( &mut self, module: &crate::Module, value: &StoreValue, func_ctx: &FunctionCtx, ) -> BackendResult { match *value { StoreValue::Expression(expr) => self.write_expr(module, expr, func_ctx)?, StoreValue::TempIndex { depth, index, ty: _, } => write!(self.out, "{STORE_TEMP_NAME}{depth}[{index}]")?, StoreValue::TempAccess { depth, base, member_index, } => { let name = &self.names[&NameKey::StructMember(base, member_index)]; write!(self.out, "{STORE_TEMP_NAME}{depth}.{name}")? } StoreValue::TempColumnAccess { depth, base, member_index, column, } => { let name = &self.names[&NameKey::StructMember(base, member_index)]; write!(self.out, "{STORE_TEMP_NAME}{depth}.{name}_{column}")? } } Ok(()) } /// Helper function to write down the Store operation on a `ByteAddressBuffer`. pub(super) fn write_storage_store( &mut self, module: &crate::Module, var_handle: Handle, value: StoreValue, func_ctx: &FunctionCtx, level: crate::back::Level, within_struct: Option>, ) -> BackendResult { let temp_resolution; let ty_resolution = match value { StoreValue::Expression(expr) => &func_ctx.info[expr].ty, StoreValue::TempIndex { depth: _, index: _, ref ty, } => ty, StoreValue::TempAccess { depth: _, base, member_index, } => { let ty_handle = match module.types[base].inner { crate::TypeInner::Struct { ref members, .. } => { members[member_index as usize].ty } _ => unreachable!(), }; temp_resolution = TypeResolution::Handle(ty_handle); &temp_resolution } StoreValue::TempColumnAccess { .. } => { unreachable!("attempting write_storage_store for TempColumnAccess"); } }; match *ty_resolution.inner_with(&module.types) { crate::TypeInner::Scalar(scalar) => { // working around the borrow checker in `self.write_expr` let chain = mem::take(&mut self.temp_access_chain); let var_name = &self.names[&NameKey::GlobalVariable(var_handle)]; // See note about DXC and Load/Store in the module's documentation. if scalar.width == 4 { write!(self.out, "{level}{var_name}.Store(")?; self.write_storage_address(module, &chain, func_ctx)?; write!(self.out, ", asuint(")?; self.write_store_value(module, &value, func_ctx)?; writeln!(self.out, "));")?; } else { write!(self.out, "{level}{var_name}.Store(")?; self.write_storage_address(module, &chain, func_ctx)?; write!(self.out, ", ")?; self.write_store_value(module, &value, func_ctx)?; writeln!(self.out, ");")?; } self.temp_access_chain = chain; } crate::TypeInner::Vector { size, scalar } => { // working around the borrow checker in `self.write_expr` let chain = mem::take(&mut self.temp_access_chain); let var_name = &self.names[&NameKey::GlobalVariable(var_handle)]; // See note about DXC and Load/Store in the module's documentation. if scalar.width == 4 { write!(self.out, "{}{}.Store{}(", level, var_name, size as u8)?; self.write_storage_address(module, &chain, func_ctx)?; write!(self.out, ", asuint(")?; self.write_store_value(module, &value, func_ctx)?; writeln!(self.out, "));")?; } else { write!(self.out, "{level}{var_name}.Store(")?; self.write_storage_address(module, &chain, func_ctx)?; write!(self.out, ", ")?; self.write_store_value(module, &value, func_ctx)?; writeln!(self.out, ");")?; } self.temp_access_chain = chain; } crate::TypeInner::Matrix { columns, rows, scalar, } => { // Note: Matrices containing vec3s, due to padding, act like they contain vec4s. let row_stride = Alignment::from(rows) * scalar.width as u32; writeln!(self.out, "{level}{{")?; match within_struct { Some(containing_struct) if rows == crate::VectorSize::Bi => { // If we are within a struct, then the struct was already assigned to // a temporary, we don't need to make another. let mut chain = mem::take(&mut self.temp_access_chain); for i in 0..columns as u32 { chain.push(SubAccess::Offset(i * row_stride)); // working around the borrow checker in `self.write_expr` let var_name = &self.names[&NameKey::GlobalVariable(var_handle)]; let StoreValue::TempAccess { member_index, .. } = value else { unreachable!( "write_storage_store within_struct but not TempAccess" ); }; let column_value = StoreValue::TempColumnAccess { depth: level.0, // note not incrementing, b/c no temp base: containing_struct, member_index, column: i, }; // See note about DXC and Load/Store in the module's documentation. if scalar.width == 4 { write!( self.out, "{}{}.Store{}(", level.next(), var_name, rows as u8 )?; self.write_storage_address(module, &chain, func_ctx)?; write!(self.out, ", asuint(")?; self.write_store_value(module, &column_value, func_ctx)?; writeln!(self.out, "));")?; } else { write!(self.out, "{}{var_name}.Store(", level.next())?; self.write_storage_address(module, &chain, func_ctx)?; write!(self.out, ", ")?; self.write_store_value(module, &column_value, func_ctx)?; writeln!(self.out, ");")?; } chain.pop(); } self.temp_access_chain = chain; } _ => { // first, assign the value to a temporary let depth = level.0 + 1; write!( self.out, "{}{}{}x{} {}{} = ", level.next(), scalar.to_hlsl_str()?, columns as u8, rows as u8, STORE_TEMP_NAME, depth, )?; self.write_store_value(module, &value, func_ctx)?; writeln!(self.out, ";")?; // then iterate the stores for i in 0..columns as u32 { self.temp_access_chain .push(SubAccess::Offset(i * row_stride)); let ty_inner = crate::TypeInner::Vector { size: rows, scalar }; let sv = StoreValue::TempIndex { depth, index: i, ty: TypeResolution::Value(ty_inner), }; self.write_storage_store( module, var_handle, sv, func_ctx, level.next(), None, )?; self.temp_access_chain.pop(); } } } // done writeln!(self.out, "{level}}}")?; } crate::TypeInner::Array { base, size: crate::ArraySize::Constant(size), stride, } => { // first, assign the value to a temporary writeln!(self.out, "{level}{{")?; write!(self.out, "{}", level.next())?; self.write_type(module, base)?; let depth = level.next().0; write!(self.out, " {STORE_TEMP_NAME}{depth}")?; self.write_array_size(module, base, crate::ArraySize::Constant(size))?; write!(self.out, " = ")?; self.write_store_value(module, &value, func_ctx)?; writeln!(self.out, ";")?; // then iterate the stores for i in 0..size.get() { self.temp_access_chain.push(SubAccess::Offset(i * stride)); let sv = StoreValue::TempIndex { depth, index: i, ty: TypeResolution::Handle(base), }; self.write_storage_store(module, var_handle, sv, func_ctx, level.next(), None)?; self.temp_access_chain.pop(); } // done writeln!(self.out, "{level}}}")?; } crate::TypeInner::Struct { ref members, .. } => { // first, assign the value to a temporary writeln!(self.out, "{level}{{")?; let depth = level.next().0; let struct_ty = ty_resolution.handle().unwrap(); let struct_name = &self.names[&NameKey::Type(struct_ty)]; write!( self.out, "{}{} {}{} = ", level.next(), struct_name, STORE_TEMP_NAME, depth )?; self.write_store_value(module, &value, func_ctx)?; writeln!(self.out, ";")?; // then iterate the stores for (i, member) in members.iter().enumerate() { self.temp_access_chain .push(SubAccess::Offset(member.offset)); let sv = StoreValue::TempAccess { depth, base: struct_ty, member_index: i as u32, }; self.write_storage_store( module, var_handle, sv, func_ctx, level.next(), Some(struct_ty), )?; self.temp_access_chain.pop(); } // done writeln!(self.out, "{level}}}")?; } _ => unreachable!(), } Ok(()) } /// Set [`temp_access_chain`] to compute the byte offset of `cur_expr`. /// /// The `cur_expr` expression must be a reference to a global /// variable in the [`Storage`] address space, or a chain of /// [`Access`] and [`AccessIndex`] expressions referring to some /// component of such a global. /// /// [`temp_access_chain`]: super::Writer::temp_access_chain /// [`Storage`]: crate::AddressSpace::Storage /// [`Access`]: crate::Expression::Access /// [`AccessIndex`]: crate::Expression::AccessIndex pub(super) fn fill_access_chain( &mut self, module: &crate::Module, mut cur_expr: Handle, func_ctx: &FunctionCtx, ) -> Result, Error> { enum AccessIndex { Expression(Handle), Constant(u32), } enum Parent<'a> { Array { stride: u32 }, Struct(&'a [crate::StructMember]), } self.temp_access_chain.clear(); loop { let (next_expr, access_index) = match func_ctx.expressions[cur_expr] { crate::Expression::GlobalVariable(handle) => { if let Some(ref binding) = module.global_variables[handle].binding { // this was already resolved earlier when we started evaluating an entry point. let bt = self.options.resolve_resource_binding(binding).unwrap(); if let Some(dynamic_storage_buffer_offsets_index) = bt.dynamic_storage_buffer_offsets_index { self.temp_access_chain.push(SubAccess::BufferOffset { group: binding.group, offset: dynamic_storage_buffer_offsets_index, }); } } return Ok(handle); } crate::Expression::Access { base, index } => (base, AccessIndex::Expression(index)), crate::Expression::AccessIndex { base, index } => { (base, AccessIndex::Constant(index)) } ref other => { return Err(Error::Unimplemented(format!("Pointer access of {other:?}"))) } }; let parent = match *func_ctx.resolve_type(next_expr, &module.types) { crate::TypeInner::Pointer { base, .. } => match module.types[base].inner { crate::TypeInner::Struct { ref members, .. } => Parent::Struct(members), crate::TypeInner::Array { stride, .. } => Parent::Array { stride }, crate::TypeInner::Vector { scalar, .. } => Parent::Array { stride: scalar.width as u32, }, crate::TypeInner::Matrix { rows, scalar, .. } => Parent::Array { // The stride between matrices is the count of rows as this is how // long each column is. stride: Alignment::from(rows) * scalar.width as u32, }, _ => unreachable!(), }, crate::TypeInner::ValuePointer { scalar, .. } => Parent::Array { stride: scalar.width as u32, }, _ => unreachable!(), }; let sub = match (parent, access_index) { (Parent::Array { stride }, AccessIndex::Expression(value)) => { SubAccess::Index { value, stride } } (Parent::Array { stride }, AccessIndex::Constant(index)) => { SubAccess::Offset(stride * index) } (Parent::Struct(members), AccessIndex::Constant(index)) => { SubAccess::Offset(members[index as usize].offset) } (Parent::Struct(_), AccessIndex::Expression(_)) => unreachable!(), }; self.temp_access_chain.push(sub); cur_expr = next_expr; } } } ================================================ FILE: naga/src/back/hlsl/writer.rs ================================================ use alloc::{ format, string::{String, ToString}, vec::Vec, }; use core::{fmt, mem}; use super::{ help, help::{ WrappedArrayLength, WrappedConstructor, WrappedImageQuery, WrappedStructMatrixAccess, WrappedZeroValue, }, storage::StoreValue, BackendResult, Error, FragmentEntryPoint, Options, PipelineOptions, ShaderModel, }; use crate::{ back::{self, get_entry_points, Baked}, common, proc::{self, index, ExternalTextureNameKey, NameKey}, valid, Handle, Module, RayQueryFunction, Scalar, ScalarKind, ShaderStage, TypeInner, }; const LOCATION_SEMANTIC: &str = "LOC"; const SPECIAL_CBUF_TYPE: &str = "NagaConstants"; const SPECIAL_CBUF_VAR: &str = "_NagaConstants"; const SPECIAL_FIRST_VERTEX: &str = "first_vertex"; const SPECIAL_FIRST_INSTANCE: &str = "first_instance"; const SPECIAL_OTHER: &str = "other"; pub(crate) const MODF_FUNCTION: &str = "naga_modf"; pub(crate) const FREXP_FUNCTION: &str = "naga_frexp"; pub(crate) const EXTRACT_BITS_FUNCTION: &str = "naga_extractBits"; pub(crate) const INSERT_BITS_FUNCTION: &str = "naga_insertBits"; pub(crate) const SAMPLER_HEAP_VAR: &str = "nagaSamplerHeap"; pub(crate) const COMPARISON_SAMPLER_HEAP_VAR: &str = "nagaComparisonSamplerHeap"; pub(crate) const SAMPLE_EXTERNAL_TEXTURE_FUNCTION: &str = "nagaSampleExternalTexture"; pub(crate) const ABS_FUNCTION: &str = "naga_abs"; pub(crate) const DIV_FUNCTION: &str = "naga_div"; pub(crate) const MOD_FUNCTION: &str = "naga_mod"; pub(crate) const NEG_FUNCTION: &str = "naga_neg"; pub(crate) const F2I32_FUNCTION: &str = "naga_f2i32"; pub(crate) const F2U32_FUNCTION: &str = "naga_f2u32"; pub(crate) const F2I64_FUNCTION: &str = "naga_f2i64"; pub(crate) const F2U64_FUNCTION: &str = "naga_f2u64"; pub(crate) const IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION: &str = "nagaTextureSampleBaseClampToEdge"; pub(crate) const IMAGE_LOAD_EXTERNAL_FUNCTION: &str = "nagaTextureLoadExternal"; pub(crate) const RAY_QUERY_TRACKER_VARIABLE_PREFIX: &str = "naga_query_init_tracker_for_"; /// Prefix for variables in a naga statement pub(crate) const INTERNAL_PREFIX: &str = "naga_"; enum Index { Expression(Handle), Static(u32), } struct EpStructMember { name: String, ty: Handle, // technically, this should always be `Some` // (we `debug_assert!` this in `write_interface_struct`) binding: Option, index: u32, } /// Structure contains information required for generating /// wrapped structure of all entry points arguments struct EntryPointBinding { /// Name of the fake EP argument that contains the struct /// with all the flattened input data. arg_name: String, /// Generated structure name ty_name: String, /// Members of generated structure members: Vec, local_invocation_index_name: Option, } pub(super) struct EntryPointInterface { /// If `Some`, the input of an entry point is gathered in a special /// struct with members sorted by binding. /// The `EntryPointBinding::members` array is sorted by index, /// so that we can walk it in `write_ep_arguments_initialization`. input: Option, /// If `Some`, the output of an entry point is flattened. /// The `EntryPointBinding::members` array is sorted by binding, /// So that we can walk it in `Statement::Return` handler. output: Option, } #[derive(Clone, Eq, PartialEq, PartialOrd, Ord)] enum InterfaceKey { Location(u32), BuiltIn(crate::BuiltIn), Other, } impl InterfaceKey { const fn new(binding: Option<&crate::Binding>) -> Self { match binding { Some(&crate::Binding::Location { location, .. }) => Self::Location(location), Some(&crate::Binding::BuiltIn(built_in)) => Self::BuiltIn(built_in), None => Self::Other, } } } #[derive(Copy, Clone, PartialEq)] enum Io { Input, Output, } const fn is_subgroup_builtin_binding(binding: &Option) -> bool { let &Some(crate::Binding::BuiltIn(builtin)) = binding else { return false; }; matches!( builtin, crate::BuiltIn::SubgroupSize | crate::BuiltIn::SubgroupInvocationId | crate::BuiltIn::NumSubgroups | crate::BuiltIn::SubgroupId ) } /// Information for how to generate a `binding_array` access. struct BindingArraySamplerInfo { /// Variable name of the sampler heap sampler_heap_name: &'static str, /// Variable name of the sampler index buffer sampler_index_buffer_name: String, /// Variable name of the base index _into_ the sampler index buffer binding_array_base_index_name: String, } impl<'a, W: fmt::Write> super::Writer<'a, W> { pub fn new(out: W, options: &'a Options, pipeline_options: &'a PipelineOptions) -> Self { Self { out, names: crate::FastHashMap::default(), namer: proc::Namer::default(), options, pipeline_options, entry_point_io: crate::FastHashMap::default(), named_expressions: crate::NamedExpressions::default(), wrapped: super::Wrapped::default(), written_committed_intersection: false, written_candidate_intersection: false, continue_ctx: back::continue_forward::ContinueCtx::default(), temp_access_chain: Vec::new(), need_bake_expressions: Default::default(), } } fn reset(&mut self, module: &Module) { self.names.clear(); self.namer.reset( module, &super::keywords::RESERVED_SET, proc::KeywordSet::empty(), &super::keywords::RESERVED_CASE_INSENSITIVE_SET, super::keywords::RESERVED_PREFIXES, &mut self.names, ); self.entry_point_io.clear(); self.named_expressions.clear(); self.wrapped.clear(); self.written_committed_intersection = false; self.written_candidate_intersection = false; self.continue_ctx.clear(); self.need_bake_expressions.clear(); } /// Generates statements to be inserted immediately before and at the very /// start of the body of each loop, to defeat infinite loop reasoning. /// The 0th item of the returned tuple should be inserted immediately prior /// to the loop and the 1st item should be inserted at the very start of /// the loop body. /// /// See [`back::msl::Writer::gen_force_bounded_loop_statements`] for details. fn gen_force_bounded_loop_statements( &mut self, level: back::Level, ) -> Option<(String, String)> { if !self.options.force_loop_bounding { return None; } let loop_bound_name = self.namer.call("loop_bound"); let max = u32::MAX; // Count down from u32::MAX rather than up from 0 to avoid hang on // certain Intel drivers. See . let decl = format!("{level}uint2 {loop_bound_name} = uint2({max}u, {max}u);"); let level = level.next(); let break_and_inc = format!( "{level}if (all({loop_bound_name} == uint2(0u, 0u))) {{ break; }} {level}{loop_bound_name} -= uint2({loop_bound_name}.y == 0u, 1u);" ); Some((decl, break_and_inc)) } /// Helper method used to find which expressions of a given function require baking /// /// # Notes /// Clears `need_bake_expressions` set before adding to it fn update_expressions_to_bake( &mut self, module: &Module, func: &crate::Function, info: &valid::FunctionInfo, ) { use crate::Expression; self.need_bake_expressions.clear(); for (exp_handle, expr) in func.expressions.iter() { let expr_info = &info[exp_handle]; let min_ref_count = func.expressions[exp_handle].bake_ref_count(); if min_ref_count <= expr_info.ref_count { self.need_bake_expressions.insert(exp_handle); } if let Expression::Math { fun, arg, arg1, .. } = *expr { match fun { crate::MathFunction::Asinh | crate::MathFunction::Acosh | crate::MathFunction::Atanh | crate::MathFunction::Unpack2x16float | crate::MathFunction::Unpack2x16snorm | crate::MathFunction::Unpack2x16unorm | crate::MathFunction::Unpack4x8snorm | crate::MathFunction::Unpack4x8unorm | crate::MathFunction::Unpack4xI8 | crate::MathFunction::Unpack4xU8 | crate::MathFunction::Pack2x16float | crate::MathFunction::Pack2x16snorm | crate::MathFunction::Pack2x16unorm | crate::MathFunction::Pack4x8snorm | crate::MathFunction::Pack4x8unorm | crate::MathFunction::Pack4xI8 | crate::MathFunction::Pack4xU8 | crate::MathFunction::Pack4xI8Clamp | crate::MathFunction::Pack4xU8Clamp => { self.need_bake_expressions.insert(arg); } crate::MathFunction::CountLeadingZeros => { let inner = info[exp_handle].ty.inner_with(&module.types); if let Some(ScalarKind::Sint) = inner.scalar_kind() { self.need_bake_expressions.insert(arg); } } crate::MathFunction::Dot4U8Packed | crate::MathFunction::Dot4I8Packed => { self.need_bake_expressions.insert(arg); self.need_bake_expressions.insert(arg1.unwrap()); } _ => {} } } if let Expression::Derivative { axis, ctrl, expr } = *expr { use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; if axis == Axis::Width && (ctrl == Ctrl::Coarse || ctrl == Ctrl::Fine) { self.need_bake_expressions.insert(expr); } } if let Expression::GlobalVariable(_) = *expr { let inner = info[exp_handle].ty.inner_with(&module.types); if let TypeInner::Sampler { .. } = *inner { self.need_bake_expressions.insert(exp_handle); } } } for statement in func.body.iter() { match *statement { crate::Statement::SubgroupCollectiveOperation { op: _, collective_op: crate::CollectiveOperation::InclusiveScan, argument, result: _, } => { self.need_bake_expressions.insert(argument); } crate::Statement::Atomic { fun: crate::AtomicFunction::Exchange { compare: Some(cmp) }, .. } => { self.need_bake_expressions.insert(cmp); } _ => {} } } } pub fn write( &mut self, module: &Module, module_info: &valid::ModuleInfo, fragment_entry_point: Option<&FragmentEntryPoint<'_>>, ) -> Result { self.reset(module); // Write special constants, if needed if let Some(ref bt) = self.options.special_constants_binding { writeln!(self.out, "struct {SPECIAL_CBUF_TYPE} {{")?; writeln!(self.out, "{}int {};", back::INDENT, SPECIAL_FIRST_VERTEX)?; writeln!(self.out, "{}int {};", back::INDENT, SPECIAL_FIRST_INSTANCE)?; writeln!(self.out, "{}uint {};", back::INDENT, SPECIAL_OTHER)?; writeln!(self.out, "}};")?; write!( self.out, "ConstantBuffer<{}> {}: register(b{}", SPECIAL_CBUF_TYPE, SPECIAL_CBUF_VAR, bt.register )?; if bt.space != 0 { write!(self.out, ", space{}", bt.space)?; } writeln!(self.out, ");")?; // Extra newline for readability writeln!(self.out)?; } for (group, bt) in self.options.dynamic_storage_buffer_offsets_targets.iter() { writeln!(self.out, "struct __dynamic_buffer_offsetsTy{group} {{")?; for i in 0..bt.size { writeln!(self.out, "{}uint _{};", back::INDENT, i)?; } writeln!(self.out, "}};")?; writeln!( self.out, "ConstantBuffer<__dynamic_buffer_offsetsTy{}> __dynamic_buffer_offsets{}: register(b{}, space{});", group, group, bt.register, bt.space )?; // Extra newline for readability writeln!(self.out)?; } // Save all entry point output types let ep_results = module .entry_points .iter() .map(|ep| (ep.stage, ep.function.result.clone())) .collect::)>>(); self.write_all_mat_cx2_typedefs_and_functions(module)?; // Write all structs for (handle, ty) in module.types.iter() { if let TypeInner::Struct { ref members, span } = ty.inner { if module.types[members.last().unwrap().ty] .inner .is_dynamically_sized(&module.types) { // unsized arrays can only be in storage buffers, // for which we use `ByteAddressBuffer` anyway. continue; } let ep_result = ep_results.iter().find(|e| { if let Some(ref result) = e.1 { result.ty == handle } else { false } }); self.write_struct( module, handle, members, span, ep_result.map(|r| (r.0, Io::Output)), )?; writeln!(self.out)?; } } self.write_special_functions(module)?; self.write_wrapped_expression_functions(module, &module.global_expressions, None)?; self.write_wrapped_zero_value_functions(module, &module.global_expressions)?; // Write all named constants let mut constants = module .constants .iter() .filter(|&(_, c)| c.name.is_some()) .peekable(); while let Some((handle, _)) = constants.next() { self.write_global_constant(module, handle)?; // Add extra newline for readability on last iteration if constants.peek().is_none() { writeln!(self.out)?; } } // Write all globals for (global, _) in module.global_variables.iter() { self.write_global(module, global)?; } if !module.global_variables.is_empty() { // Add extra newline for readability writeln!(self.out)?; } let ep_range = get_entry_points(module, self.pipeline_options.entry_point.as_ref()) .map_err(|(stage, name)| Error::EntryPointNotFound(stage, name))?; // Write all entry points wrapped structs for index in ep_range.clone() { let ep = &module.entry_points[index]; let ep_name = self.names[&NameKey::EntryPoint(index as u16)].clone(); let ep_io = self.write_ep_interface( module, &ep.function, ep.stage, &ep_name, fragment_entry_point, )?; self.entry_point_io.insert(index, ep_io); } // Write all regular functions for (handle, function) in module.functions.iter() { let info = &module_info[handle]; // Check if all of the globals are accessible if !self.options.fake_missing_bindings { if let Some((var_handle, _)) = module .global_variables .iter() .find(|&(var_handle, var)| match var.binding { Some(ref binding) if !info[var_handle].is_empty() => { self.options.resolve_resource_binding(binding).is_err() && self .options .resolve_external_texture_resource_binding(binding) .is_err() } _ => false, }) { log::debug!( "Skipping function {:?} (name {:?}) because global {:?} is inaccessible", handle, function.name, var_handle ); continue; } } let ctx = back::FunctionCtx { ty: back::FunctionType::Function(handle), info, expressions: &function.expressions, named_expressions: &function.named_expressions, }; let name = self.names[&NameKey::Function(handle)].clone(); self.write_wrapped_functions(module, &ctx)?; self.write_function(module, name.as_str(), function, &ctx, info)?; writeln!(self.out)?; } let mut translated_ep_names = Vec::with_capacity(ep_range.len()); // Write all entry points for index in ep_range { let ep = &module.entry_points[index]; let info = module_info.get_entry_point(index); if !self.options.fake_missing_bindings { let mut ep_error = None; for (var_handle, var) in module.global_variables.iter() { match var.binding { Some(ref binding) if !info[var_handle].is_empty() => { if let Err(err) = self.options.resolve_resource_binding(binding) { if self .options .resolve_external_texture_resource_binding(binding) .is_err() { ep_error = Some(err); break; } } } _ => {} } } if let Some(err) = ep_error { translated_ep_names.push(Err(err)); continue; } } let ctx = back::FunctionCtx { ty: back::FunctionType::EntryPoint(index as u16), info, expressions: &ep.function.expressions, named_expressions: &ep.function.named_expressions, }; self.write_wrapped_functions(module, &ctx)?; if ep.stage.compute_like() { // HLSL is calling workgroup size "num threads" let num_threads = ep.workgroup_size; writeln!( self.out, "[numthreads({}, {}, {})]", num_threads[0], num_threads[1], num_threads[2] )?; } let name = self.names[&NameKey::EntryPoint(index as u16)].clone(); self.write_function(module, &name, &ep.function, &ctx, info)?; if index < module.entry_points.len() - 1 { writeln!(self.out)?; } translated_ep_names.push(Ok(name)); } Ok(super::ReflectionInfo { entry_point_names: translated_ep_names, }) } fn write_modifier(&mut self, binding: &crate::Binding) -> BackendResult { match *binding { crate::Binding::BuiltIn(crate::BuiltIn::Position { invariant: true }) => { write!(self.out, "precise ")?; } crate::Binding::BuiltIn(crate::BuiltIn::Barycentric { perspective: false }) => { write!(self.out, "noperspective ")?; } crate::Binding::Location { interpolation, sampling, .. } => { if let Some(interpolation) = interpolation { if let Some(string) = interpolation.to_hlsl_str() { write!(self.out, "{string} ")? } } if let Some(sampling) = sampling { if let Some(string) = sampling.to_hlsl_str() { write!(self.out, "{string} ")? } } } crate::Binding::BuiltIn(_) => {} } Ok(()) } //TODO: we could force fragment outputs to always go through `entry_point_io.output` path // if they are struct, so that the `stage` argument here could be omitted. fn write_semantic( &mut self, binding: &Option, stage: Option<(ShaderStage, Io)>, ) -> BackendResult { match *binding { Some(crate::Binding::BuiltIn(builtin)) if !is_subgroup_builtin_binding(binding) => { if builtin == crate::BuiltIn::ViewIndex && self.options.shader_model < ShaderModel::V6_1 { return Err(Error::ShaderModelTooLow( "used @builtin(view_index) or SV_ViewID".to_string(), ShaderModel::V6_1, )); } let builtin_str = builtin.to_hlsl_str()?; write!(self.out, " : {builtin_str}")?; } Some(crate::Binding::Location { blend_src: Some(1), .. }) => { write!(self.out, " : SV_Target1")?; } Some(crate::Binding::Location { location, .. }) => { if stage == Some((ShaderStage::Fragment, Io::Output)) { write!(self.out, " : SV_Target{location}")?; } else { write!(self.out, " : {LOCATION_SEMANTIC}{location}")?; } } _ => {} } Ok(()) } fn write_interface_struct( &mut self, module: &Module, shader_stage: (ShaderStage, Io), struct_name: String, mut members: Vec, ) -> Result { // Sort the members so that first come the user-defined varyings // in ascending locations, and then built-ins. This allows VS and FS // interfaces to match with regards to order. members.sort_by_key(|m| InterfaceKey::new(m.binding.as_ref())); write!(self.out, "struct {struct_name}")?; writeln!(self.out, " {{")?; let mut local_invocation_index_name = None; let mut subgroup_id_used = false; for m in members.iter() { // Sanity check that each IO member is a built-in or is assigned a // location. Also see note about nesting in `write_ep_input_struct`. debug_assert!(m.binding.is_some()); match m.binding { Some(crate::Binding::BuiltIn(crate::BuiltIn::SubgroupId)) => { subgroup_id_used = true; } Some(crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationIndex)) => { local_invocation_index_name = Some(m.name.clone()); } _ => (), } if is_subgroup_builtin_binding(&m.binding) { continue; } write!(self.out, "{}", back::INDENT)?; if let Some(ref binding) = m.binding { self.write_modifier(binding)?; } self.write_type(module, m.ty)?; write!(self.out, " {}", &m.name)?; self.write_semantic(&m.binding, Some(shader_stage))?; writeln!(self.out, ";")?; } if subgroup_id_used && local_invocation_index_name.is_none() { let name = self.namer.call("local_invocation_index"); writeln!(self.out, "{}uint {name} : SV_GroupIndex;", back::INDENT)?; local_invocation_index_name = Some(name); } writeln!(self.out, "}};")?; writeln!(self.out)?; // See ordering notes on EntryPointInterface fields match shader_stage.1 { Io::Input => { // bring back the original order members.sort_by_key(|m| m.index); } Io::Output => { // keep it sorted by binding } } Ok(EntryPointBinding { arg_name: self.namer.call(struct_name.to_lowercase().as_str()), ty_name: struct_name, members, local_invocation_index_name, }) } /// Flatten all entry point arguments into a single struct. /// This is needed since we need to re-order them: first placing user locations, /// then built-ins. fn write_ep_input_struct( &mut self, module: &Module, func: &crate::Function, stage: ShaderStage, entry_point_name: &str, ) -> Result { let struct_name = format!("{stage:?}Input_{entry_point_name}"); let mut fake_members = Vec::new(); for arg in func.arguments.iter() { // NOTE: We don't need to handle nesting structs. All members must // be either built-ins or assigned a location. I.E. `binding` is // `Some`. This is checked in `VaryingContext::validate`. See: // https://gpuweb.github.io/gpuweb/wgsl/#input-output-locations match module.types[arg.ty].inner { TypeInner::Struct { ref members, .. } => { for member in members.iter() { let name = self.namer.call_or(&member.name, "member"); let index = fake_members.len() as u32; fake_members.push(EpStructMember { name, ty: member.ty, binding: member.binding.clone(), index, }); } } _ => { let member_name = self.namer.call_or(&arg.name, "member"); let index = fake_members.len() as u32; fake_members.push(EpStructMember { name: member_name, ty: arg.ty, binding: arg.binding.clone(), index, }); } } } self.write_interface_struct(module, (stage, Io::Input), struct_name, fake_members) } /// Flatten all entry point results into a single struct. /// This is needed since we need to re-order them: first placing user locations, /// then built-ins. fn write_ep_output_struct( &mut self, module: &Module, result: &crate::FunctionResult, stage: ShaderStage, entry_point_name: &str, frag_ep: Option<&FragmentEntryPoint<'_>>, ) -> Result { let struct_name = format!("{stage:?}Output_{entry_point_name}"); let empty = []; let members = match module.types[result.ty].inner { TypeInner::Struct { ref members, .. } => members, ref other => { log::error!("Unexpected {other:?} output type without a binding"); &empty[..] } }; // Gather list of fragment input locations. We use this below to remove user-defined // varyings from VS outputs that aren't in the FS inputs. This makes the VS interface match // as long as the FS inputs are a subset of the VS outputs. This is only applied if the // writer is supplied with information about the fragment entry point. let fs_input_locs = if let (Some(frag_ep), ShaderStage::Vertex) = (frag_ep, stage) { let mut fs_input_locs = Vec::new(); for arg in frag_ep.func.arguments.iter() { let mut push_if_location = |binding: &Option| match *binding { Some(crate::Binding::Location { location, .. }) => fs_input_locs.push(location), Some(crate::Binding::BuiltIn(_)) | None => {} }; // NOTE: We don't need to handle struct nesting. See note in // `write_ep_input_struct`. match frag_ep.module.types[arg.ty].inner { TypeInner::Struct { ref members, .. } => { for member in members.iter() { push_if_location(&member.binding); } } _ => push_if_location(&arg.binding), } } fs_input_locs.sort(); Some(fs_input_locs) } else { None }; let mut fake_members = Vec::new(); for (index, member) in members.iter().enumerate() { if let Some(ref fs_input_locs) = fs_input_locs { match member.binding { Some(crate::Binding::Location { location, .. }) => { if fs_input_locs.binary_search(&location).is_err() { continue; } } Some(crate::Binding::BuiltIn(_)) | None => {} } } let member_name = self.namer.call_or(&member.name, "member"); fake_members.push(EpStructMember { name: member_name, ty: member.ty, binding: member.binding.clone(), index: index as u32, }); } self.write_interface_struct(module, (stage, Io::Output), struct_name, fake_members) } /// Writes special interface structures for an entry point. The special structures have /// all the fields flattened into them and sorted by binding. They are needed to emulate /// subgroup built-ins and to make the interfaces between VS outputs and FS inputs match. fn write_ep_interface( &mut self, module: &Module, func: &crate::Function, stage: ShaderStage, ep_name: &str, frag_ep: Option<&FragmentEntryPoint<'_>>, ) -> Result { Ok(EntryPointInterface { input: if !func.arguments.is_empty() && (stage == ShaderStage::Fragment || func .arguments .iter() .any(|arg| is_subgroup_builtin_binding(&arg.binding))) { Some(self.write_ep_input_struct(module, func, stage, ep_name)?) } else { None }, output: match func.result { Some(ref fr) if fr.binding.is_none() && stage == ShaderStage::Vertex => { Some(self.write_ep_output_struct(module, fr, stage, ep_name, frag_ep)?) } _ => None, }, }) } fn write_ep_argument_initialization( &mut self, ep: &crate::EntryPoint, ep_input: &EntryPointBinding, fake_member: &EpStructMember, ) -> BackendResult { match fake_member.binding { Some(crate::Binding::BuiltIn(crate::BuiltIn::SubgroupSize)) => { write!(self.out, "WaveGetLaneCount()")? } Some(crate::Binding::BuiltIn(crate::BuiltIn::SubgroupInvocationId)) => { write!(self.out, "WaveGetLaneIndex()")? } Some(crate::Binding::BuiltIn(crate::BuiltIn::NumSubgroups)) => write!( self.out, "({}u + WaveGetLaneCount() - 1u) / WaveGetLaneCount()", ep.workgroup_size[0] * ep.workgroup_size[1] * ep.workgroup_size[2] )?, Some(crate::Binding::BuiltIn(crate::BuiltIn::SubgroupId)) => { write!( self.out, "{}.{} / WaveGetLaneCount()", ep_input.arg_name, // When writing SubgroupId, we always guarantee that local_invocation_index_name is written ep_input.local_invocation_index_name.as_ref().unwrap() )?; } _ => { write!(self.out, "{}.{}", ep_input.arg_name, fake_member.name)?; } } Ok(()) } /// Write an entry point preface that initializes the arguments as specified in IR. fn write_ep_arguments_initialization( &mut self, module: &Module, func: &crate::Function, ep_index: u16, ) -> BackendResult { let ep = &module.entry_points[ep_index as usize]; let ep_input = match self .entry_point_io .get_mut(&(ep_index as usize)) .unwrap() .input .take() { Some(ep_input) => ep_input, None => return Ok(()), }; let mut fake_iter = ep_input.members.iter(); for (arg_index, arg) in func.arguments.iter().enumerate() { write!(self.out, "{}", back::INDENT)?; self.write_type(module, arg.ty)?; let arg_name = &self.names[&NameKey::EntryPointArgument(ep_index, arg_index as u32)]; write!(self.out, " {arg_name}")?; match module.types[arg.ty].inner { TypeInner::Array { base, size, .. } => { self.write_array_size(module, base, size)?; write!(self.out, " = ")?; self.write_ep_argument_initialization( ep, &ep_input, fake_iter.next().unwrap(), )?; writeln!(self.out, ";")?; } TypeInner::Struct { ref members, .. } => { write!(self.out, " = {{ ")?; for index in 0..members.len() { if index != 0 { write!(self.out, ", ")?; } self.write_ep_argument_initialization( ep, &ep_input, fake_iter.next().unwrap(), )?; } writeln!(self.out, " }};")?; } _ => { write!(self.out, " = ")?; self.write_ep_argument_initialization( ep, &ep_input, fake_iter.next().unwrap(), )?; writeln!(self.out, ";")?; } } } assert!(fake_iter.next().is_none()); Ok(()) } /// Helper method used to write global variables /// # Notes /// Always adds a newline fn write_global( &mut self, module: &Module, handle: Handle, ) -> BackendResult { let global = &module.global_variables[handle]; let inner = &module.types[global.ty].inner; let handle_ty = match *inner { TypeInner::BindingArray { ref base, .. } => &module.types[*base].inner, _ => inner, }; // External textures are handled entirely differently, so defer entirely to that method. // We do so prior to calling resolve_resource_binding() below, as we even need to resolve // their bindings separately. let is_external_texture = matches!( *handle_ty, TypeInner::Image { class: crate::ImageClass::External, .. } ); if is_external_texture { return self.write_global_external_texture(module, handle, global); } if let Some(ref binding) = global.binding { if let Err(err) = self.options.resolve_resource_binding(binding) { log::debug!( "Skipping global {:?} (name {:?}) for being inaccessible: {}", handle, global.name, err, ); return Ok(()); } } // Samplers are handled entirely differently, so defer entirely to that method. let is_sampler = matches!(*handle_ty, TypeInner::Sampler { .. }); if is_sampler { return self.write_global_sampler(module, handle, global); } // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-variable-register let register_ty = match global.space { crate::AddressSpace::Function => unreachable!("Function address space"), crate::AddressSpace::Private => { write!(self.out, "static ")?; self.write_type(module, global.ty)?; "" } crate::AddressSpace::WorkGroup => { write!(self.out, "groupshared ")?; self.write_type(module, global.ty)?; "" } crate::AddressSpace::TaskPayload => unimplemented!(), crate::AddressSpace::Uniform => { // constant buffer declarations are expected to be inlined, e.g. // `cbuffer foo: register(b0) { field1: type1; }` write!(self.out, "cbuffer")?; "b" } crate::AddressSpace::Storage { access } => { if global .memory_decorations .contains(crate::MemoryDecorations::COHERENT) { write!(self.out, "globallycoherent ")?; } let (prefix, register) = if access.contains(crate::StorageAccess::STORE) { ("RW", "u") } else { ("", "t") }; write!(self.out, "{prefix}ByteAddressBuffer")?; register } crate::AddressSpace::Handle => { let register = match *handle_ty { // all storage textures are UAV, unconditionally TypeInner::Image { class: crate::ImageClass::Storage { .. }, .. } => "u", _ => "t", }; self.write_type(module, global.ty)?; register } crate::AddressSpace::Immediate => { // The type of the immediates will be wrapped in `ConstantBuffer` write!(self.out, "ConstantBuffer<")?; "b" } crate::AddressSpace::RayPayload | crate::AddressSpace::IncomingRayPayload => { unimplemented!() } }; // If the global is a immediate data write the type now because it will be a // generic argument to `ConstantBuffer` if global.space == crate::AddressSpace::Immediate { self.write_global_type(module, global.ty)?; // need to write the array size if the type was emitted with `write_type` if let TypeInner::Array { base, size, .. } = module.types[global.ty].inner { self.write_array_size(module, base, size)?; } // Close the angled brackets for the generic argument write!(self.out, ">")?; } let name = &self.names[&NameKey::GlobalVariable(handle)]; write!(self.out, " {name}")?; // Immediates need to be assigned a binding explicitly by the consumer // since naga has no way to know the binding from the shader alone if global.space == crate::AddressSpace::Immediate { match module.types[global.ty].inner { TypeInner::Struct { .. } => {} _ => { return Err(Error::Unimplemented(format!( "push-constant '{name}' has non-struct type; tracked by: https://github.com/gfx-rs/wgpu/issues/5683" ))); } } let target = self .options .immediates_target .as_ref() .expect("No bind target was defined for the immediates block"); write!(self.out, ": register(b{}", target.register)?; if target.space != 0 { write!(self.out, ", space{}", target.space)?; } write!(self.out, ")")?; } if let Some(ref binding) = global.binding { // this was already resolved earlier when we started evaluating an entry point. let bt = self.options.resolve_resource_binding(binding).unwrap(); // need to write the binding array size if the type was emitted with `write_type` if let TypeInner::BindingArray { base, size, .. } = module.types[global.ty].inner { if let Some(overridden_size) = bt.binding_array_size { write!(self.out, "[{overridden_size}]")?; } else { self.write_array_size(module, base, size)?; } } write!(self.out, " : register({}{}", register_ty, bt.register)?; if bt.space != 0 { write!(self.out, ", space{}", bt.space)?; } write!(self.out, ")")?; } else { // need to write the array size if the type was emitted with `write_type` if let TypeInner::Array { base, size, .. } = module.types[global.ty].inner { self.write_array_size(module, base, size)?; } if global.space == crate::AddressSpace::Private { write!(self.out, " = ")?; if let Some(init) = global.init { self.write_const_expression(module, init, &module.global_expressions)?; } else { self.write_default_init(module, global.ty)?; } } } if global.space == crate::AddressSpace::Uniform { write!(self.out, " {{ ")?; self.write_global_type(module, global.ty)?; write!( self.out, " {}", &self.names[&NameKey::GlobalVariable(handle)] )?; // need to write the array size if the type was emitted with `write_type` if let TypeInner::Array { base, size, .. } = module.types[global.ty].inner { self.write_array_size(module, base, size)?; } writeln!(self.out, "; }}")?; } else { writeln!(self.out, ";")?; } Ok(()) } fn write_global_sampler( &mut self, module: &Module, handle: Handle, global: &crate::GlobalVariable, ) -> BackendResult { let binding = *global.binding.as_ref().unwrap(); let key = super::SamplerIndexBufferKey { group: binding.group, }; self.write_wrapped_sampler_buffer(key)?; // This was already validated, so we can confidently unwrap it. let bt = self.options.resolve_resource_binding(&binding).unwrap(); match module.types[global.ty].inner { TypeInner::Sampler { comparison } => { // If we are generating a static access, we create a variable for the sampler. // // This prevents the DXIL from containing multiple lookups for the sampler, which // the backend compiler will then have to eliminate. AMD does seem to be able to // eliminate these, but better safe than sorry. write!(self.out, "static const ")?; self.write_type(module, global.ty)?; let heap_var = if comparison { COMPARISON_SAMPLER_HEAP_VAR } else { SAMPLER_HEAP_VAR }; let index_buffer_name = &self.wrapped.sampler_index_buffers[&key]; let name = &self.names[&NameKey::GlobalVariable(handle)]; writeln!( self.out, " {name} = {heap_var}[{index_buffer_name}[{register}]];", register = bt.register )?; } TypeInner::BindingArray { .. } => { // If we are generating a binding array, we cannot directly access the sampler as the index // into the sampler index buffer is unknown at compile time. Instead we generate a constant // that represents the "base" index into the sampler index buffer. This constant is added // to the user provided index to get the final index into the sampler index buffer. let name = &self.names[&NameKey::GlobalVariable(handle)]; writeln!( self.out, "static const uint {name} = {register};", register = bt.register )?; } _ => unreachable!(), }; Ok(()) } /// Write the declarations for an external texture global variable. /// These are emitted as multiple global variables: Three `Texture2D`s /// (one for each plane) and a parameters cbuffer. fn write_global_external_texture( &mut self, module: &Module, handle: Handle, global: &crate::GlobalVariable, ) -> BackendResult { let res_binding = global .binding .as_ref() .expect("External texture global variables must have a resource binding"); let ext_tex_bindings = match self .options .resolve_external_texture_resource_binding(res_binding) { Ok(bindings) => bindings, Err(err) => { log::debug!( "Skipping global {:?} (name {:?}) for being inaccessible: {}", handle, global.name, err, ); return Ok(()); } }; let mut write_plane = |bt: &super::BindTarget, name| -> BackendResult { write!( self.out, "Texture2D {}: register(t{}", name, bt.register )?; if bt.space != 0 { write!(self.out, ", space{}", bt.space)?; } writeln!(self.out, ");")?; Ok(()) }; for (i, bt) in ext_tex_bindings.planes.iter().enumerate() { let plane_name = &self.names [&NameKey::ExternalTextureGlobalVariable(handle, ExternalTextureNameKey::Plane(i))]; write_plane(bt, plane_name)?; } let params_name = &self.names [&NameKey::ExternalTextureGlobalVariable(handle, ExternalTextureNameKey::Params)]; let params_ty_name = &self.names[&NameKey::Type(module.special_types.external_texture_params.unwrap())]; write!( self.out, "cbuffer {}: register(b{}", params_name, ext_tex_bindings.params.register )?; if ext_tex_bindings.params.space != 0 { write!(self.out, ", space{}", ext_tex_bindings.params.space)?; } writeln!(self.out, ") {{ {params_ty_name} {params_name}; }};")?; Ok(()) } /// Helper method used to write global constants /// /// # Notes /// Ends in a newline fn write_global_constant( &mut self, module: &Module, handle: Handle, ) -> BackendResult { write!(self.out, "static const ")?; let constant = &module.constants[handle]; self.write_type(module, constant.ty)?; let name = &self.names[&NameKey::Constant(handle)]; write!(self.out, " {name}")?; // Write size for array type if let TypeInner::Array { base, size, .. } = module.types[constant.ty].inner { self.write_array_size(module, base, size)?; } write!(self.out, " = ")?; self.write_const_expression(module, constant.init, &module.global_expressions)?; writeln!(self.out, ";")?; Ok(()) } pub(super) fn write_array_size( &mut self, module: &Module, base: Handle, size: crate::ArraySize, ) -> BackendResult { write!(self.out, "[")?; match size.resolve(module.to_ctx())? { proc::IndexableLength::Known(size) => { write!(self.out, "{size}")?; } proc::IndexableLength::Dynamic => unreachable!(), } write!(self.out, "]")?; if let TypeInner::Array { base: next_base, size: next_size, .. } = module.types[base].inner { self.write_array_size(module, next_base, next_size)?; } Ok(()) } /// Helper method used to write structs /// /// # Notes /// Ends in a newline fn write_struct( &mut self, module: &Module, handle: Handle, members: &[crate::StructMember], span: u32, shader_stage: Option<(ShaderStage, Io)>, ) -> BackendResult { // Write struct name let struct_name = &self.names[&NameKey::Type(handle)]; writeln!(self.out, "struct {struct_name} {{")?; let mut last_offset = 0; for (index, member) in members.iter().enumerate() { if member.binding.is_none() && member.offset > last_offset { // using int as padding should work as long as the backend // doesn't support a type that's less than 4 bytes in size // (Error::UnsupportedScalar catches this) let padding = (member.offset - last_offset) / 4; for i in 0..padding { writeln!(self.out, "{}int _pad{}_{};", back::INDENT, index, i)?; } } let ty_inner = &module.types[member.ty].inner; last_offset = member.offset + ty_inner.size_hlsl(module.to_ctx())?; // The indentation is only for readability write!(self.out, "{}", back::INDENT)?; match module.types[member.ty].inner { TypeInner::Array { base, size, .. } => { // HLSL arrays are written as `type name[size]` self.write_global_type(module, member.ty)?; // Write `name` write!( self.out, " {}", &self.names[&NameKey::StructMember(handle, index as u32)] )?; // Write [size] self.write_array_size(module, base, size)?; } // We treat matrices of the form `matCx2` as a sequence of C `vec2`s. // See the module-level block comment in mod.rs for details. TypeInner::Matrix { rows, columns, scalar, } if member.binding.is_none() && rows == crate::VectorSize::Bi => { let vec_ty = TypeInner::Vector { size: rows, scalar }; let field_name_key = NameKey::StructMember(handle, index as u32); for i in 0..columns as u8 { if i != 0 { write!(self.out, "; ")?; } self.write_value_type(module, &vec_ty)?; write!(self.out, " {}_{}", &self.names[&field_name_key], i)?; } } _ => { // Write modifier before type if let Some(ref binding) = member.binding { self.write_modifier(binding)?; } // Even though Naga IR matrices are column-major, we must describe // matrices passed from the CPU as being in row-major order. // See the module-level block comment in mod.rs for details. if let TypeInner::Matrix { .. } = module.types[member.ty].inner { write!(self.out, "row_major ")?; } // Write the member type and name self.write_type(module, member.ty)?; write!( self.out, " {}", &self.names[&NameKey::StructMember(handle, index as u32)] )?; } } self.write_semantic(&member.binding, shader_stage)?; writeln!(self.out, ";")?; } // add padding at the end since sizes of types don't get rounded up to their alignment in HLSL if members.last().unwrap().binding.is_none() && span > last_offset { let padding = (span - last_offset) / 4; for i in 0..padding { writeln!(self.out, "{}int _end_pad_{};", back::INDENT, i)?; } } writeln!(self.out, "}};")?; Ok(()) } /// Helper method used to write global/structs non image/sampler types /// /// # Notes /// Adds no trailing or leading whitespace pub(super) fn write_global_type( &mut self, module: &Module, ty: Handle, ) -> BackendResult { let matrix_data = get_inner_matrix_data(module, ty); // We treat matrices of the form `matCx2` as a sequence of C `vec2`s. // See the module-level block comment in mod.rs for details. if let Some(MatrixType { columns, rows: crate::VectorSize::Bi, width: 4, }) = matrix_data { write!(self.out, "__mat{}x2", columns as u8)?; } else { // Even though Naga IR matrices are column-major, we must describe // matrices passed from the CPU as being in row-major order. // See the module-level block comment in mod.rs for details. if matrix_data.is_some() { write!(self.out, "row_major ")?; } self.write_type(module, ty)?; } Ok(()) } /// Helper method used to write non image/sampler types /// /// # Notes /// Adds no trailing or leading whitespace pub(super) fn write_type(&mut self, module: &Module, ty: Handle) -> BackendResult { let inner = &module.types[ty].inner; match *inner { TypeInner::Struct { .. } => write!(self.out, "{}", self.names[&NameKey::Type(ty)])?, // hlsl array has the size separated from the base type TypeInner::Array { base, .. } | TypeInner::BindingArray { base, .. } => { self.write_type(module, base)? } ref other => self.write_value_type(module, other)?, } Ok(()) } /// Helper method used to write value types /// /// # Notes /// Adds no trailing or leading whitespace pub(super) fn write_value_type(&mut self, module: &Module, inner: &TypeInner) -> BackendResult { match *inner { TypeInner::Scalar(scalar) | TypeInner::Atomic(scalar) => { write!(self.out, "{}", scalar.to_hlsl_str()?)?; } TypeInner::Vector { size, scalar } => { write!( self.out, "{}{}", scalar.to_hlsl_str()?, common::vector_size_str(size) )?; } TypeInner::Matrix { columns, rows, scalar, } => { // The IR supports only float matrix // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-matrix // Because of the implicit transpose all matrices have in HLSL, we need to transpose the size as well. write!( self.out, "{}{}x{}", scalar.to_hlsl_str()?, common::vector_size_str(columns), common::vector_size_str(rows), )?; } TypeInner::Image { dim, arrayed, class, } => { self.write_image_type(dim, arrayed, class)?; } TypeInner::Sampler { comparison } => { let sampler = if comparison { "SamplerComparisonState" } else { "SamplerState" }; write!(self.out, "{sampler}")?; } // HLSL arrays are written as `type name[size]` // Current code is written arrays only as `[size]` // Base `type` and `name` should be written outside TypeInner::Array { base, size, .. } | TypeInner::BindingArray { base, size } => { self.write_array_size(module, base, size)?; } TypeInner::AccelerationStructure { .. } => { write!(self.out, "RaytracingAccelerationStructure")?; } TypeInner::RayQuery { .. } => { // these are constant flags, there are dynamic flags also but constant flags are not supported by naga write!(self.out, "RayQuery")?; } _ => return Err(Error::Unimplemented(format!("write_value_type {inner:?}"))), } Ok(()) } /// Helper method used to write functions /// # Notes /// Ends in a newline fn write_function( &mut self, module: &Module, name: &str, func: &crate::Function, func_ctx: &back::FunctionCtx<'_>, info: &valid::FunctionInfo, ) -> BackendResult { // Function Declaration Syntax - https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-function-syntax self.update_expressions_to_bake(module, func, info); if let Some(ref result) = func.result { // Write typedef if return type is an array let array_return_type = match module.types[result.ty].inner { TypeInner::Array { base, size, .. } => { let array_return_type = self.namer.call(&format!("ret_{name}")); write!(self.out, "typedef ")?; self.write_type(module, result.ty)?; write!(self.out, " {array_return_type}")?; self.write_array_size(module, base, size)?; writeln!(self.out, ";")?; Some(array_return_type) } _ => None, }; // Write modifier if let Some( ref binding @ crate::Binding::BuiltIn(crate::BuiltIn::Position { invariant: true }), ) = result.binding { self.write_modifier(binding)?; } // Write return type match func_ctx.ty { back::FunctionType::Function(_) => { if let Some(array_return_type) = array_return_type { write!(self.out, "{array_return_type}")?; } else { self.write_type(module, result.ty)?; } } back::FunctionType::EntryPoint(index) => { if let Some(ref ep_output) = self.entry_point_io.get(&(index as usize)).unwrap().output { write!(self.out, "{}", ep_output.ty_name)?; } else { self.write_type(module, result.ty)?; } } } } else { write!(self.out, "void")?; } // Write function name write!(self.out, " {name}(")?; let need_workgroup_variables_initialization = self.need_workgroup_variables_initialization(func_ctx, module); let needs_local_invocation_id_name = need_workgroup_variables_initialization; let mut local_invocation_id_name = None; // Write function arguments for non entry point functions match func_ctx.ty { back::FunctionType::Function(handle) => { for (index, arg) in func.arguments.iter().enumerate() { if index != 0 { write!(self.out, ", ")?; } self.write_function_argument(module, handle, arg, index)?; } } back::FunctionType::EntryPoint(ep_index) => { if let Some(ref ep_input) = self.entry_point_io.get(&(ep_index as usize)).unwrap().input { write!(self.out, "{} {}", ep_input.ty_name, ep_input.arg_name)?; } else { let stage = module.entry_points[ep_index as usize].stage; for (index, arg) in func.arguments.iter().enumerate() { if index != 0 { write!(self.out, ", ")?; } self.write_type(module, arg.ty)?; let argument_name = &self.names[&NameKey::EntryPointArgument(ep_index, index as u32)]; if arg.binding == Some(crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationId)) { local_invocation_id_name = Some(argument_name.clone()); } write!(self.out, " {argument_name}")?; if let TypeInner::Array { base, size, .. } = module.types[arg.ty].inner { self.write_array_size(module, base, size)?; } self.write_semantic(&arg.binding, Some((stage, Io::Input)))?; } } if needs_local_invocation_id_name && local_invocation_id_name.is_none() { if self .entry_point_io .get(&(ep_index as usize)) .unwrap() .input .is_some() || !func.arguments.is_empty() { write!(self.out, ", ")?; } let var_name = self.namer.call("local_invocation_id"); write!(self.out, "uint3 {var_name} : SV_GroupThreadID")?; local_invocation_id_name = Some(var_name); } } } // Ends of arguments write!(self.out, ")")?; // Write semantic if it present if let back::FunctionType::EntryPoint(index) = func_ctx.ty { let stage = module.entry_points[index as usize].stage; if let Some(crate::FunctionResult { ref binding, .. }) = func.result { self.write_semantic(binding, Some((stage, Io::Output)))?; } } // Function body start writeln!(self.out)?; writeln!(self.out, "{{")?; if need_workgroup_variables_initialization { self.write_workgroup_variables_initialization( func_ctx, module, // need_workgroup_variables_initialization forces this to be written // if the user doesn't specify it (so this must be Some()) local_invocation_id_name.unwrap(), )?; } if let back::FunctionType::EntryPoint(index) = func_ctx.ty { self.write_ep_arguments_initialization(module, func, index)?; } // Write function local variables for (handle, local) in func.local_variables.iter() { // Write indentation (only for readability) write!(self.out, "{}", back::INDENT)?; // Write the local name // The leading space is important self.write_type(module, local.ty)?; write!(self.out, " {}", self.names[&func_ctx.name_key(handle)])?; // Write size for array type if let TypeInner::Array { base, size, .. } = module.types[local.ty].inner { self.write_array_size(module, base, size)?; } let is_ray_query = match module.types[local.ty].inner { // from https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html#tracerayinline-example-1 it seems that ray queries shouldn't be zeroed TypeInner::RayQuery { .. } => true, _ => { write!(self.out, " = ")?; // Write the local initializer if needed if let Some(init) = local.init { self.write_expr(module, init, func_ctx)?; } else { // Zero initialize local variables self.write_default_init(module, local.ty)?; } false } }; // Finish the local with `;` and add a newline (only for readability) writeln!(self.out, ";")?; // If it's a ray query, we also want a tracker variable if is_ray_query { write!(self.out, "{}", back::INDENT)?; self.write_value_type(module, &TypeInner::Scalar(Scalar::U32))?; writeln!( self.out, " {RAY_QUERY_TRACKER_VARIABLE_PREFIX}{} = 0;", self.names[&func_ctx.name_key(handle)] )?; } } if !func.local_variables.is_empty() { writeln!(self.out)?; } // Write the function body (statement list) for sta in func.body.iter() { // The indentation should always be 1 when writing the function body self.write_stmt(module, sta, func_ctx, back::Level(1))?; } writeln!(self.out, "}}")?; self.named_expressions.clear(); Ok(()) } fn write_function_argument( &mut self, module: &Module, handle: Handle, arg: &crate::FunctionArgument, index: usize, ) -> BackendResult { // External texture arguments must be expanded into separate // arguments for each plane and the params buffer. if let TypeInner::Image { class: crate::ImageClass::External, .. } = module.types[arg.ty].inner { return self.write_function_external_texture_argument(module, handle, index); } // Write argument type let arg_ty = match module.types[arg.ty].inner { // pointers in function arguments are expected and resolve to `inout` TypeInner::Pointer { base, .. } => { //TODO: can we narrow this down to just `in` when possible? write!(self.out, "inout ")?; base } _ => arg.ty, }; self.write_type(module, arg_ty)?; let argument_name = &self.names[&NameKey::FunctionArgument(handle, index as u32)]; // Write argument name. Space is important. write!(self.out, " {argument_name}")?; if let TypeInner::Array { base, size, .. } = module.types[arg_ty].inner { self.write_array_size(module, base, size)?; } Ok(()) } fn write_function_external_texture_argument( &mut self, module: &Module, handle: Handle, index: usize, ) -> BackendResult { let plane_names = [0, 1, 2].map(|i| { &self.names[&NameKey::ExternalTextureFunctionArgument( handle, index as u32, ExternalTextureNameKey::Plane(i), )] }); let params_name = &self.names[&NameKey::ExternalTextureFunctionArgument( handle, index as u32, ExternalTextureNameKey::Params, )]; let params_ty_name = &self.names[&NameKey::Type(module.special_types.external_texture_params.unwrap())]; write!( self.out, "Texture2D {}, Texture2D {}, Texture2D {}, {params_ty_name} {params_name}", plane_names[0], plane_names[1], plane_names[2], )?; Ok(()) } fn need_workgroup_variables_initialization( &mut self, func_ctx: &back::FunctionCtx, module: &Module, ) -> bool { self.options.zero_initialize_workgroup_memory && func_ctx.ty.is_compute_like_entry_point(module) && module.global_variables.iter().any(|(handle, var)| { !func_ctx.info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup }) } fn write_workgroup_variables_initialization( &mut self, func_ctx: &back::FunctionCtx, module: &Module, local_invocation_id_name: String, ) -> BackendResult { let level = back::Level(1); writeln!( self.out, "{level}if (all({local_invocation_id_name} == uint3(0u, 0u, 0u))) {{" )?; let vars = module.global_variables.iter().filter(|&(handle, var)| { !func_ctx.info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup }); for (handle, var) in vars { let name = &self.names[&NameKey::GlobalVariable(handle)]; write!(self.out, "{}{} = ", level.next(), name)?; self.write_default_init(module, var.ty)?; writeln!(self.out, ";")?; } writeln!(self.out, "{level}}}")?; self.write_control_barrier(crate::Barrier::WORK_GROUP, level) } /// Helper method used to write switches fn write_switch( &mut self, module: &Module, func_ctx: &back::FunctionCtx<'_>, level: back::Level, selector: Handle, cases: &[crate::SwitchCase], ) -> BackendResult { // Write all cases let indent_level_1 = level.next(); let indent_level_2 = indent_level_1.next(); // See docs of `back::continue_forward` module. if let Some(variable) = self.continue_ctx.enter_switch(&mut self.namer) { writeln!(self.out, "{level}bool {variable} = false;",)?; }; // Check if there is only one body, by seeing if all except the last case are fall through // with empty bodies. FXC doesn't handle these switches correctly, so // we generate a `do {} while(false);` loop instead. There must be a default case, so there // is no need to check if one of the cases would have matched. let one_body = cases .iter() .rev() .skip(1) .all(|case| case.fall_through && case.body.is_empty()); if one_body { // Start the do-while writeln!(self.out, "{level}do {{")?; // Note: Expressions have no side-effects so we don't need to emit selector expression. // Body if let Some(case) = cases.last() { for sta in case.body.iter() { self.write_stmt(module, sta, func_ctx, indent_level_1)?; } } // End do-while writeln!(self.out, "{level}}} while(false);")?; } else { // Start the switch write!(self.out, "{level}")?; write!(self.out, "switch(")?; self.write_expr(module, selector, func_ctx)?; writeln!(self.out, ") {{")?; for (i, case) in cases.iter().enumerate() { match case.value { crate::SwitchValue::I32(value) => { write!(self.out, "{indent_level_1}case {value}:")? } crate::SwitchValue::U32(value) => { write!(self.out, "{indent_level_1}case {value}u:")? } crate::SwitchValue::Default => write!(self.out, "{indent_level_1}default:")?, } // The new block is not only stylistic, it plays a role here: // We might end up having to write the same case body // multiple times due to FXC not supporting fallthrough. // Therefore, some `Expression`s written by `Statement::Emit` // will end up having the same name (`_expr`). // So we need to put each case in its own scope. let write_block_braces = !(case.fall_through && case.body.is_empty()); if write_block_braces { writeln!(self.out, " {{")?; } else { writeln!(self.out)?; } // Although FXC does support a series of case clauses before // a block[^yes], it does not support fallthrough from a // non-empty case block to the next[^no]. If this case has a // non-empty body with a fallthrough, emulate that by // duplicating the bodies of all the cases it would fall // into as extensions of this case's own body. This makes // the HLSL output potentially quadratic in the size of the // Naga IR. // // [^yes]: ```hlsl // case 1: // case 2: do_stuff() // ``` // [^no]: ```hlsl // case 1: do_this(); // case 2: do_that(); // ``` if case.fall_through && !case.body.is_empty() { let curr_len = i + 1; let end_case_idx = curr_len + cases .iter() .skip(curr_len) .position(|case| !case.fall_through) .unwrap(); let indent_level_3 = indent_level_2.next(); for case in &cases[i..=end_case_idx] { writeln!(self.out, "{indent_level_2}{{")?; let prev_len = self.named_expressions.len(); for sta in case.body.iter() { self.write_stmt(module, sta, func_ctx, indent_level_3)?; } // Clear all named expressions that were previously inserted by the statements in the block self.named_expressions.truncate(prev_len); writeln!(self.out, "{indent_level_2}}}")?; } let last_case = &cases[end_case_idx]; if last_case.body.last().is_none_or(|s| !s.is_terminator()) { writeln!(self.out, "{indent_level_2}break;")?; } } else { for sta in case.body.iter() { self.write_stmt(module, sta, func_ctx, indent_level_2)?; } if !case.fall_through && case.body.last().is_none_or(|s| !s.is_terminator()) { writeln!(self.out, "{indent_level_2}break;")?; } } if write_block_braces { writeln!(self.out, "{indent_level_1}}}")?; } } writeln!(self.out, "{level}}}")?; } // Handle any forwarded continue statements. use back::continue_forward::ExitControlFlow; let op = match self.continue_ctx.exit_switch() { ExitControlFlow::None => None, ExitControlFlow::Continue { variable } => Some(("continue", variable)), ExitControlFlow::Break { variable } => Some(("break", variable)), }; if let Some((control_flow, variable)) = op { writeln!(self.out, "{level}if ({variable}) {{")?; writeln!(self.out, "{indent_level_1}{control_flow};")?; writeln!(self.out, "{level}}}")?; } Ok(()) } fn write_index( &mut self, module: &Module, index: Index, func_ctx: &back::FunctionCtx<'_>, ) -> BackendResult { match index { Index::Static(index) => { write!(self.out, "{index}")?; } Index::Expression(index) => { self.write_expr(module, index, func_ctx)?; } } Ok(()) } /// Helper method used to write statements /// /// # Notes /// Always adds a newline fn write_stmt( &mut self, module: &Module, stmt: &crate::Statement, func_ctx: &back::FunctionCtx<'_>, level: back::Level, ) -> BackendResult { use crate::Statement; match *stmt { Statement::Emit(ref range) => { for handle in range.clone() { let ptr_class = func_ctx.resolve_type(handle, &module.types).pointer_space(); let expr_name = if ptr_class.is_some() { // HLSL can't save a pointer-valued expression in a variable, // but we shouldn't ever need to: they should never be named expressions, // and none of the expression types flagged by bake_ref_count can be pointer-valued. None } else if let Some(name) = func_ctx.named_expressions.get(&handle) { // Front end provides names for all variables at the start of writing. // But we write them to step by step. We need to recache them // Otherwise, we could accidentally write variable name instead of full expression. // Also, we use sanitized names! It defense backend from generating variable with name from reserved keywords. Some(self.namer.call(name)) } else if self.need_bake_expressions.contains(&handle) { Some(Baked(handle).to_string()) } else { None }; if let Some(name) = expr_name { write!(self.out, "{level}")?; self.write_named_expr(module, handle, name, handle, func_ctx)?; } } } // TODO: copy-paste from glsl-out Statement::Block(ref block) => { write!(self.out, "{level}")?; writeln!(self.out, "{{")?; for sta in block.iter() { // Increase the indentation to help with readability self.write_stmt(module, sta, func_ctx, level.next())? } writeln!(self.out, "{level}}}")? } // TODO: copy-paste from glsl-out Statement::If { condition, ref accept, ref reject, } => { write!(self.out, "{level}")?; write!(self.out, "if (")?; self.write_expr(module, condition, func_ctx)?; writeln!(self.out, ") {{")?; let l2 = level.next(); for sta in accept { // Increase indentation to help with readability self.write_stmt(module, sta, func_ctx, l2)?; } // If there are no statements in the reject block we skip writing it // This is only for readability if !reject.is_empty() { writeln!(self.out, "{level}}} else {{")?; for sta in reject { // Increase indentation to help with readability self.write_stmt(module, sta, func_ctx, l2)?; } } writeln!(self.out, "{level}}}")? } // TODO: copy-paste from glsl-out Statement::Kill => writeln!(self.out, "{level}discard;")?, Statement::Return { value: None } => { writeln!(self.out, "{level}return;")?; } Statement::Return { value: Some(expr) } => { let base_ty_res = &func_ctx.info[expr].ty; let mut resolved = base_ty_res.inner_with(&module.types); if let TypeInner::Pointer { base, space: _ } = *resolved { resolved = &module.types[base].inner; } if let TypeInner::Struct { .. } = *resolved { // We can safely unwrap here, since we now we working with struct let ty = base_ty_res.handle().unwrap(); let struct_name = &self.names[&NameKey::Type(ty)]; let variable_name = self.namer.call(&struct_name.to_lowercase()); write!(self.out, "{level}const {struct_name} {variable_name} = ",)?; self.write_expr(module, expr, func_ctx)?; writeln!(self.out, ";")?; // for entry point returns, we may need to reshuffle the outputs into a different struct let ep_output = match func_ctx.ty { back::FunctionType::Function(_) => None, back::FunctionType::EntryPoint(index) => self .entry_point_io .get(&(index as usize)) .unwrap() .output .as_ref(), }; let final_name = match ep_output { Some(ep_output) => { let final_name = self.namer.call(&variable_name); write!( self.out, "{}const {} {} = {{ ", level, ep_output.ty_name, final_name, )?; for (index, m) in ep_output.members.iter().enumerate() { if index != 0 { write!(self.out, ", ")?; } let member_name = &self.names[&NameKey::StructMember(ty, m.index)]; write!(self.out, "{variable_name}.{member_name}")?; } writeln!(self.out, " }};")?; final_name } None => variable_name, }; writeln!(self.out, "{level}return {final_name};")?; } else { write!(self.out, "{level}return ")?; self.write_expr(module, expr, func_ctx)?; writeln!(self.out, ";")? } } Statement::Store { pointer, value } => { let ty_inner = func_ctx.resolve_type(pointer, &module.types); if let Some(crate::AddressSpace::Storage { .. }) = ty_inner.pointer_space() { let var_handle = self.fill_access_chain(module, pointer, func_ctx)?; self.write_storage_store( module, var_handle, StoreValue::Expression(value), func_ctx, level, None, )?; } else { // We treat matrices of the form `matCx2` as a sequence of C `vec2`s. // See the module-level block comment in mod.rs for details. // // We handle matrix Stores here directly (including sub accesses for Vectors and Scalars). // Loads are handled by `Expression::AccessIndex` (since sub accesses work fine for Loads). enum MatrixAccess { Direct { base: Handle, index: u32, }, Struct { columns: crate::VectorSize, base: Handle, }, } let get_members = |expr: Handle| { let resolved = func_ctx.resolve_type(expr, &module.types); match *resolved { TypeInner::Pointer { base, .. } => match module.types[base].inner { TypeInner::Struct { ref members, .. } => Some(members), _ => None, }, _ => None, } }; write!(self.out, "{level}")?; let matrix_access_on_lhs = find_matrix_in_access_chain(module, pointer, func_ctx).and_then( |(matrix_expr, vector, scalar)| match ( func_ctx.resolve_type(matrix_expr, &module.types), &func_ctx.expressions[matrix_expr], ) { ( &TypeInner::Pointer { base: ty, .. }, &crate::Expression::AccessIndex { base, index }, ) if matches!( module.types[ty].inner, TypeInner::Matrix { rows: crate::VectorSize::Bi, .. } ) && get_members(base) .map(|members| members[index as usize].binding.is_none()) == Some(true) => { Some((MatrixAccess::Direct { base, index }, vector, scalar)) } _ => { if let Some(MatrixType { columns, rows: crate::VectorSize::Bi, width: 4, }) = get_inner_matrix_of_struct_array_member( module, matrix_expr, func_ctx, true, ) { Some(( MatrixAccess::Struct { columns, base: matrix_expr, }, vector, scalar, )) } else { None } } }, ); match matrix_access_on_lhs { Some((MatrixAccess::Direct { index, base }, vector, scalar)) => { let base_ty_res = &func_ctx.info[base].ty; let resolved = base_ty_res.inner_with(&module.types); let ty = match *resolved { TypeInner::Pointer { base, .. } => base, _ => base_ty_res.handle().unwrap(), }; if let Some(Index::Static(vec_index)) = vector { self.write_expr(module, base, func_ctx)?; write!( self.out, ".{}_{}", &self.names[&NameKey::StructMember(ty, index)], vec_index )?; if let Some(scalar_index) = scalar { write!(self.out, "[")?; self.write_index(module, scalar_index, func_ctx)?; write!(self.out, "]")?; } write!(self.out, " = ")?; self.write_expr(module, value, func_ctx)?; writeln!(self.out, ";")?; } else { let access = WrappedStructMatrixAccess { ty, index }; match (&vector, &scalar) { (&Some(_), &Some(_)) => { self.write_wrapped_struct_matrix_set_scalar_function_name( access, )?; } (&Some(_), &None) => { self.write_wrapped_struct_matrix_set_vec_function_name( access, )?; } (&None, _) => { self.write_wrapped_struct_matrix_set_function_name(access)?; } } write!(self.out, "(")?; self.write_expr(module, base, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, value, func_ctx)?; if let Some(Index::Expression(vec_index)) = vector { write!(self.out, ", ")?; self.write_expr(module, vec_index, func_ctx)?; if let Some(scalar_index) = scalar { write!(self.out, ", ")?; self.write_index(module, scalar_index, func_ctx)?; } } writeln!(self.out, ");")?; } } Some(( MatrixAccess::Struct { columns, base }, Some(Index::Expression(vec_index)), scalar, )) => { // We handle `Store`s to __matCx2 column vectors and scalar elements via // the previously injected functions __set_col_of_matCx2 / __set_el_of_matCx2. if scalar.is_some() { write!(self.out, "__set_el_of_mat{}x2", columns as u8)?; } else { write!(self.out, "__set_col_of_mat{}x2", columns as u8)?; } write!(self.out, "(")?; self.write_expr(module, base, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, vec_index, func_ctx)?; if let Some(scalar_index) = scalar { write!(self.out, ", ")?; self.write_index(module, scalar_index, func_ctx)?; } write!(self.out, ", ")?; self.write_expr(module, value, func_ctx)?; writeln!(self.out, ");")?; } Some((MatrixAccess::Struct { .. }, Some(Index::Static(_)), _)) | Some((MatrixAccess::Struct { .. }, None, _)) | None => { self.write_expr(module, pointer, func_ctx)?; write!(self.out, " = ")?; // We cast the RHS of this store in cases where the LHS // is a struct member with type: // - matCx2 or // - a (possibly nested) array of matCx2's if let Some(MatrixType { columns, rows: crate::VectorSize::Bi, width: 4, }) = get_inner_matrix_of_struct_array_member( module, pointer, func_ctx, false, ) { let mut resolved = func_ctx.resolve_type(pointer, &module.types); if let TypeInner::Pointer { base, .. } = *resolved { resolved = &module.types[base].inner; } write!(self.out, "(__mat{}x2", columns as u8)?; if let TypeInner::Array { base, size, .. } = *resolved { self.write_array_size(module, base, size)?; } write!(self.out, ")")?; } self.write_expr(module, value, func_ctx)?; writeln!(self.out, ";")? } } } } Statement::Loop { ref body, ref continuing, break_if, } => { let force_loop_bound_statements = self.gen_force_bounded_loop_statements(level); let gate_name = (!continuing.is_empty() || break_if.is_some()) .then(|| self.namer.call("loop_init")); if let Some((ref decl, _)) = force_loop_bound_statements { writeln!(self.out, "{decl}")?; } if let Some(ref gate_name) = gate_name { writeln!(self.out, "{level}bool {gate_name} = true;")?; } self.continue_ctx.enter_loop(); writeln!(self.out, "{level}while(true) {{")?; if let Some((_, ref break_and_inc)) = force_loop_bound_statements { writeln!(self.out, "{break_and_inc}")?; } let l2 = level.next(); if let Some(gate_name) = gate_name { writeln!(self.out, "{l2}if (!{gate_name}) {{")?; let l3 = l2.next(); for sta in continuing.iter() { self.write_stmt(module, sta, func_ctx, l3)?; } if let Some(condition) = break_if { write!(self.out, "{l3}if (")?; self.write_expr(module, condition, func_ctx)?; writeln!(self.out, ") {{")?; writeln!(self.out, "{}break;", l3.next())?; writeln!(self.out, "{l3}}}")?; } writeln!(self.out, "{l2}}}")?; writeln!(self.out, "{l2}{gate_name} = false;")?; } for sta in body.iter() { self.write_stmt(module, sta, func_ctx, l2)?; } writeln!(self.out, "{level}}}")?; self.continue_ctx.exit_loop(); } Statement::Break => writeln!(self.out, "{level}break;")?, Statement::Continue => { if let Some(variable) = self.continue_ctx.continue_encountered() { writeln!(self.out, "{level}{variable} = true;")?; writeln!(self.out, "{level}break;")? } else { writeln!(self.out, "{level}continue;")? } } Statement::ControlBarrier(barrier) => { self.write_control_barrier(barrier, level)?; } Statement::MemoryBarrier(barrier) => { self.write_memory_barrier(barrier, level)?; } Statement::ImageStore { image, coordinate, array_index, value, } => { write!(self.out, "{level}")?; self.write_expr(module, image, func_ctx)?; write!(self.out, "[")?; if let Some(index) = array_index { // Array index accepted only for texture_storage_2d_array, so we can safety use int3(coordinate, array_index) here write!(self.out, "int3(")?; self.write_expr(module, coordinate, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, index, func_ctx)?; write!(self.out, ")")?; } else { self.write_expr(module, coordinate, func_ctx)?; } write!(self.out, "]")?; write!(self.out, " = ")?; self.write_expr(module, value, func_ctx)?; writeln!(self.out, ";")?; } Statement::Call { function, ref arguments, result, } => { write!(self.out, "{level}")?; if let Some(expr) = result { write!(self.out, "const ")?; let name = Baked(expr).to_string(); let expr_ty = &func_ctx.info[expr].ty; let ty_inner = match *expr_ty { proc::TypeResolution::Handle(handle) => { self.write_type(module, handle)?; &module.types[handle].inner } proc::TypeResolution::Value(ref value) => { self.write_value_type(module, value)?; value } }; write!(self.out, " {name}")?; if let TypeInner::Array { base, size, .. } = *ty_inner { self.write_array_size(module, base, size)?; } write!(self.out, " = ")?; self.named_expressions.insert(expr, name); } let func_name = &self.names[&NameKey::Function(function)]; write!(self.out, "{func_name}(")?; for (index, argument) in arguments.iter().enumerate() { if index != 0 { write!(self.out, ", ")?; } self.write_expr(module, *argument, func_ctx)?; } writeln!(self.out, ");")? } Statement::Atomic { pointer, ref fun, value, result, } => { write!(self.out, "{level}")?; let res_var_info = if let Some(res_handle) = result { let name = Baked(res_handle).to_string(); match func_ctx.info[res_handle].ty { proc::TypeResolution::Handle(handle) => self.write_type(module, handle)?, proc::TypeResolution::Value(ref value) => { self.write_value_type(module, value)? } }; write!(self.out, " {name}; ")?; self.named_expressions.insert(res_handle, name.clone()); Some((res_handle, name)) } else { None }; let pointer_space = func_ctx .resolve_type(pointer, &module.types) .pointer_space() .unwrap(); let fun_str = fun.to_hlsl_suffix(); let compare_expr = match *fun { crate::AtomicFunction::Exchange { compare: Some(cmp) } => Some(cmp), _ => None, }; match pointer_space { crate::AddressSpace::WorkGroup => { write!(self.out, "Interlocked{fun_str}(")?; self.write_expr(module, pointer, func_ctx)?; self.emit_hlsl_atomic_tail( module, func_ctx, fun, compare_expr, value, &res_var_info, )?; } crate::AddressSpace::Storage { .. } => { let var_handle = self.fill_access_chain(module, pointer, func_ctx)?; let var_name = &self.names[&NameKey::GlobalVariable(var_handle)]; let width = match func_ctx.resolve_type(value, &module.types) { &TypeInner::Scalar(Scalar { width: 8, .. }) => "64", _ => "", }; write!(self.out, "{var_name}.Interlocked{fun_str}{width}(")?; let chain = mem::take(&mut self.temp_access_chain); self.write_storage_address(module, &chain, func_ctx)?; self.temp_access_chain = chain; self.emit_hlsl_atomic_tail( module, func_ctx, fun, compare_expr, value, &res_var_info, )?; } ref other => { return Err(Error::Custom(format!( "invalid address space {other:?} for atomic statement" ))) } } if let Some(cmp) = compare_expr { if let Some(&(_res_handle, ref res_name)) = res_var_info.as_ref() { write!( self.out, "{level}{res_name}.exchanged = ({res_name}.old_value == " )?; self.write_expr(module, cmp, func_ctx)?; writeln!(self.out, ");")?; } } } Statement::ImageAtomic { image, coordinate, array_index, fun, value, } => { write!(self.out, "{level}")?; let fun_str = fun.to_hlsl_suffix(); write!(self.out, "Interlocked{fun_str}(")?; self.write_expr(module, image, func_ctx)?; write!(self.out, "[")?; self.write_texture_coordinates( "int", coordinate, array_index, None, module, func_ctx, )?; write!(self.out, "],")?; self.write_expr(module, value, func_ctx)?; writeln!(self.out, ");")?; } Statement::WorkGroupUniformLoad { pointer, result } => { self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?; write!(self.out, "{level}")?; let name = Baked(result).to_string(); self.write_named_expr(module, pointer, name, result, func_ctx)?; self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?; } Statement::Switch { selector, ref cases, } => { self.write_switch(module, func_ctx, level, selector, cases)?; } Statement::RayQuery { query, ref fun } => { // There are three possibilities for a ptr to be: // 1. A variable // 2. A function argument // 3. part of a struct // // 2 and 3 are not possible, a ray query (in naga IR) // is not allowed to be passed into a function, and // all languages disallow it in a struct (you get fun results if // you try it :) ). // // Therefore, the ray query expression must be a variable. let crate::Expression::LocalVariable(query_var) = func_ctx.expressions[query] else { unreachable!() }; let tracker_expr_name = format!( "{RAY_QUERY_TRACKER_VARIABLE_PREFIX}{}", self.names[&func_ctx.name_key(query_var)] ); match *fun { RayQueryFunction::Initialize { acceleration_structure, descriptor, } => { self.write_initialize_function( module, level, query, acceleration_structure, descriptor, &tracker_expr_name, func_ctx, )?; } RayQueryFunction::Proceed { result } => { self.write_proceed( module, level, query, result, &tracker_expr_name, func_ctx, )?; } RayQueryFunction::GenerateIntersection { hit_t } => { self.write_generate_intersection( module, level, query, hit_t, &tracker_expr_name, func_ctx, )?; } RayQueryFunction::ConfirmIntersection => { self.write_confirm_intersection( module, level, query, &tracker_expr_name, func_ctx, )?; } RayQueryFunction::Terminate => { self.write_terminate(module, level, query, &tracker_expr_name, func_ctx)?; } } } Statement::SubgroupBallot { result, predicate } => { write!(self.out, "{level}")?; let name = Baked(result).to_string(); write!(self.out, "const uint4 {name} = ")?; self.named_expressions.insert(result, name); write!(self.out, "WaveActiveBallot(")?; match predicate { Some(predicate) => self.write_expr(module, predicate, func_ctx)?, None => write!(self.out, "true")?, } writeln!(self.out, ");")?; } Statement::SubgroupCollectiveOperation { op, collective_op, argument, result, } => { write!(self.out, "{level}")?; write!(self.out, "const ")?; let name = Baked(result).to_string(); match func_ctx.info[result].ty { proc::TypeResolution::Handle(handle) => self.write_type(module, handle)?, proc::TypeResolution::Value(ref value) => { self.write_value_type(module, value)? } }; write!(self.out, " {name} = ")?; self.named_expressions.insert(result, name); match (collective_op, op) { (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::All) => { write!(self.out, "WaveActiveAllTrue(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Any) => { write!(self.out, "WaveActiveAnyTrue(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Add) => { write!(self.out, "WaveActiveSum(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Mul) => { write!(self.out, "WaveActiveProduct(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Max) => { write!(self.out, "WaveActiveMax(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Min) => { write!(self.out, "WaveActiveMin(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::And) => { write!(self.out, "WaveActiveBitAnd(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Or) => { write!(self.out, "WaveActiveBitOr(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Xor) => { write!(self.out, "WaveActiveBitXor(")? } (crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Add) => { write!(self.out, "WavePrefixSum(")? } (crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Mul) => { write!(self.out, "WavePrefixProduct(")? } (crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Add) => { self.write_expr(module, argument, func_ctx)?; write!(self.out, " + WavePrefixSum(")?; } (crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Mul) => { self.write_expr(module, argument, func_ctx)?; write!(self.out, " * WavePrefixProduct(")?; } _ => unimplemented!(), } self.write_expr(module, argument, func_ctx)?; writeln!(self.out, ");")?; } Statement::SubgroupGather { mode, argument, result, } => { write!(self.out, "{level}")?; write!(self.out, "const ")?; let name = Baked(result).to_string(); match func_ctx.info[result].ty { proc::TypeResolution::Handle(handle) => self.write_type(module, handle)?, proc::TypeResolution::Value(ref value) => { self.write_value_type(module, value)? } }; write!(self.out, " {name} = ")?; self.named_expressions.insert(result, name); match mode { crate::GatherMode::BroadcastFirst => { write!(self.out, "WaveReadLaneFirst(")?; self.write_expr(module, argument, func_ctx)?; } crate::GatherMode::QuadBroadcast(index) => { write!(self.out, "QuadReadLaneAt(")?; self.write_expr(module, argument, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, index, func_ctx)?; } crate::GatherMode::QuadSwap(direction) => { match direction { crate::Direction::X => { write!(self.out, "QuadReadAcrossX(")?; } crate::Direction::Y => { write!(self.out, "QuadReadAcrossY(")?; } crate::Direction::Diagonal => { write!(self.out, "QuadReadAcrossDiagonal(")?; } } self.write_expr(module, argument, func_ctx)?; } _ => { write!(self.out, "WaveReadLaneAt(")?; self.write_expr(module, argument, func_ctx)?; write!(self.out, ", ")?; match mode { crate::GatherMode::BroadcastFirst => unreachable!(), crate::GatherMode::Broadcast(index) | crate::GatherMode::Shuffle(index) => { self.write_expr(module, index, func_ctx)?; } crate::GatherMode::ShuffleDown(index) => { write!(self.out, "WaveGetLaneIndex() + ")?; self.write_expr(module, index, func_ctx)?; } crate::GatherMode::ShuffleUp(index) => { write!(self.out, "WaveGetLaneIndex() - ")?; self.write_expr(module, index, func_ctx)?; } crate::GatherMode::ShuffleXor(index) => { write!(self.out, "WaveGetLaneIndex() ^ ")?; self.write_expr(module, index, func_ctx)?; } crate::GatherMode::QuadBroadcast(_) => unreachable!(), crate::GatherMode::QuadSwap(_) => unreachable!(), } } } writeln!(self.out, ");")?; } Statement::CooperativeStore { .. } => unimplemented!(), Statement::RayPipelineFunction(_) => unreachable!(), } Ok(()) } fn write_const_expression( &mut self, module: &Module, expr: Handle, arena: &crate::Arena, ) -> BackendResult { self.write_possibly_const_expression(module, expr, arena, |writer, expr| { writer.write_const_expression(module, expr, arena) }) } pub(super) fn write_literal(&mut self, literal: crate::Literal) -> BackendResult { match literal { crate::Literal::F64(value) => write!(self.out, "{value:?}L")?, crate::Literal::F32(value) => write!(self.out, "{value:?}")?, crate::Literal::F16(value) => write!(self.out, "{value:?}h")?, crate::Literal::U32(value) => write!(self.out, "{value}u")?, // `-2147483648` is parsed by some compilers as unary negation of // positive 2147483648, which is too large for an int, causing // issues for some compilers. Neither DXC nor FXC appear to have // this problem, but this is not specified and could change. We // therefore use `-2147483647 - 1` as a precaution. crate::Literal::I32(value) if value == i32::MIN => { write!(self.out, "int({} - 1)", value + 1)? } // HLSL has no suffix for explicit i32 literals, but not using any suffix // makes the type ambiguous which prevents overload resolution from // working. So we explicitly use the int() constructor syntax. crate::Literal::I32(value) => write!(self.out, "int({value})")?, crate::Literal::U64(value) => write!(self.out, "{value}uL")?, // I64 version of the minimum I32 value issue described above. crate::Literal::I64(value) if value == i64::MIN => { write!(self.out, "({}L - 1L)", value + 1)?; } crate::Literal::I64(value) => write!(self.out, "{value}L")?, crate::Literal::Bool(value) => write!(self.out, "{value}")?, crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { return Err(Error::Custom( "Abstract types should not appear in IR presented to backends".into(), )); } } Ok(()) } fn write_possibly_const_expression( &mut self, module: &Module, expr: Handle, expressions: &crate::Arena, write_expression: E, ) -> BackendResult where E: Fn(&mut Self, Handle) -> BackendResult, { use crate::Expression; match expressions[expr] { Expression::Literal(literal) => { self.write_literal(literal)?; } Expression::Constant(handle) => { let constant = &module.constants[handle]; if constant.name.is_some() { write!(self.out, "{}", self.names[&NameKey::Constant(handle)])?; } else { self.write_const_expression(module, constant.init, &module.global_expressions)?; } } Expression::ZeroValue(ty) => { self.write_wrapped_zero_value_function_name(module, WrappedZeroValue { ty })?; write!(self.out, "()")?; } Expression::Compose { ty, ref components } => { match module.types[ty].inner { TypeInner::Struct { .. } | TypeInner::Array { .. } => { self.write_wrapped_constructor_function_name( module, WrappedConstructor { ty }, )?; } _ => { self.write_type(module, ty)?; } }; write!(self.out, "(")?; for (index, component) in components.iter().enumerate() { if index != 0 { write!(self.out, ", ")?; } write_expression(self, *component)?; } write!(self.out, ")")?; } Expression::Splat { size, value } => { // hlsl is not supported one value constructor // if we write, for example, int4(0), dxc returns error: // error: too few elements in vector initialization (expected 4 elements, have 1) let number_of_components = match size { crate::VectorSize::Bi => "xx", crate::VectorSize::Tri => "xxx", crate::VectorSize::Quad => "xxxx", }; write!(self.out, "(")?; write_expression(self, value)?; write!(self.out, ").{number_of_components}")? } _ => { return Err(Error::Override); } } Ok(()) } /// Helper method to write expressions /// /// # Notes /// Doesn't add any newlines or leading/trailing spaces pub(super) fn write_expr( &mut self, module: &Module, expr: Handle, func_ctx: &back::FunctionCtx<'_>, ) -> BackendResult { use crate::Expression; // Handle the special semantics of vertex_index/instance_index let ff_input = if self.options.special_constants_binding.is_some() { func_ctx.is_fixed_function_input(expr, module) } else { None }; let closing_bracket = match ff_input { Some(crate::BuiltIn::VertexIndex) => { write!(self.out, "({SPECIAL_CBUF_VAR}.{SPECIAL_FIRST_VERTEX} + ")?; ")" } Some(crate::BuiltIn::InstanceIndex) => { write!(self.out, "({SPECIAL_CBUF_VAR}.{SPECIAL_FIRST_INSTANCE} + ",)?; ")" } Some(crate::BuiltIn::NumWorkGroups) => { // Note: despite their names (`FIRST_VERTEX` and `FIRST_INSTANCE`), // in compute shaders the special constants contain the number // of workgroups, which we are using here. write!( self.out, "uint3({SPECIAL_CBUF_VAR}.{SPECIAL_FIRST_VERTEX}, {SPECIAL_CBUF_VAR}.{SPECIAL_FIRST_INSTANCE}, {SPECIAL_CBUF_VAR}.{SPECIAL_OTHER})", )?; return Ok(()); } _ => "", }; if let Some(name) = self.named_expressions.get(&expr) { write!(self.out, "{name}{closing_bracket}")?; return Ok(()); } let expression = &func_ctx.expressions[expr]; match *expression { Expression::Literal(_) | Expression::Constant(_) | Expression::ZeroValue(_) | Expression::Compose { .. } | Expression::Splat { .. } => { self.write_possibly_const_expression( module, expr, func_ctx.expressions, |writer, expr| writer.write_expr(module, expr, func_ctx), )?; } Expression::Override(_) => return Err(Error::Override), // Avoid undefined behaviour for addition, subtraction, and // multiplication of signed integers by casting operands to // unsigned, performing the operation, then casting the result back // to signed. // TODO(#7109): This relies on the asint()/asuint() functions which only work // for 32-bit types, so we must find another solution for different bit widths. Expression::Binary { op: op @ crate::BinaryOperator::Add | op @ crate::BinaryOperator::Subtract | op @ crate::BinaryOperator::Multiply, left, right, } if matches!( func_ctx.resolve_type(expr, &module.types).scalar(), Some(Scalar::I32) ) => { write!(self.out, "asint(asuint(",)?; self.write_expr(module, left, func_ctx)?; write!(self.out, ") {} asuint(", back::binary_operation_str(op))?; self.write_expr(module, right, func_ctx)?; write!(self.out, "))")?; } // All of the multiplication can be expressed as `mul`, // except vector * vector, which needs to use the "*" operator. Expression::Binary { op: crate::BinaryOperator::Multiply, left, right, } if func_ctx.resolve_type(left, &module.types).is_matrix() || func_ctx.resolve_type(right, &module.types).is_matrix() => { // We intentionally flip the order of multiplication as our matrices are implicitly transposed. write!(self.out, "mul(")?; self.write_expr(module, right, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, left, func_ctx)?; write!(self.out, ")")?; } // WGSL says that floating-point division by zero should return // infinity. Microsoft's Direct3D 11 functional specification // (https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm) // says: // // Divide by 0 produces +/- INF, except 0/0 which results in NaN. // // which is what we want. The DXIL specification for the FDiv // instruction corroborates this: // // https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#fdiv Expression::Binary { op: crate::BinaryOperator::Divide, left, right, } if matches!( func_ctx.resolve_type(expr, &module.types).scalar_kind(), Some(ScalarKind::Sint | ScalarKind::Uint) ) => { write!(self.out, "{DIV_FUNCTION}(")?; self.write_expr(module, left, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, right, func_ctx)?; write!(self.out, ")")?; } Expression::Binary { op: crate::BinaryOperator::Modulo, left, right, } if matches!( func_ctx.resolve_type(expr, &module.types).scalar_kind(), Some(ScalarKind::Sint | ScalarKind::Uint | ScalarKind::Float) ) => { write!(self.out, "{MOD_FUNCTION}(")?; self.write_expr(module, left, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, right, func_ctx)?; write!(self.out, ")")?; } Expression::Binary { op, left, right } => { write!(self.out, "(")?; self.write_expr(module, left, func_ctx)?; write!(self.out, " {} ", back::binary_operation_str(op))?; self.write_expr(module, right, func_ctx)?; write!(self.out, ")")?; } Expression::Access { base, index } => { if let Some(crate::AddressSpace::Storage { .. }) = func_ctx.resolve_type(expr, &module.types).pointer_space() { // do nothing, the chain is written on `Load`/`Store` } else { // We use the function __get_col_of_matCx2 here in cases // where `base`s type resolves to a matCx2 and is part of a // struct member with type of (possibly nested) array of matCx2's. // // Note that this only works for `Load`s and we handle // `Store`s differently in `Statement::Store`. if let Some(MatrixType { columns, rows: crate::VectorSize::Bi, width: 4, }) = get_inner_matrix_of_struct_array_member(module, base, func_ctx, true) .or_else(|| get_global_uniform_matrix(module, base, func_ctx)) { write!(self.out, "__get_col_of_mat{}x2(", columns as u8)?; self.write_expr(module, base, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, index, func_ctx)?; write!(self.out, ")")?; return Ok(()); } let resolved = func_ctx.resolve_type(base, &module.types); let (indexing_binding_array, non_uniform_qualifier) = match *resolved { TypeInner::BindingArray { .. } => { let uniformity = &func_ctx.info[index].uniformity; (true, uniformity.non_uniform_result.is_some()) } _ => (false, false), }; self.write_expr(module, base, func_ctx)?; let array_sampler_info = self.sampler_binding_array_info_from_expression( module, func_ctx, base, resolved, ); if let Some(ref info) = array_sampler_info { write!(self.out, "{}[", info.sampler_heap_name)?; } else { write!(self.out, "[")?; } let needs_bound_check = self.options.restrict_indexing && !indexing_binding_array && match resolved.pointer_space() { Some( crate::AddressSpace::Function | crate::AddressSpace::Private | crate::AddressSpace::WorkGroup | crate::AddressSpace::Immediate | crate::AddressSpace::TaskPayload | crate::AddressSpace::RayPayload | crate::AddressSpace::IncomingRayPayload, ) | None => true, Some(crate::AddressSpace::Uniform) => { // check if BindTarget.restrict_indexing is set, this is used for dynamic buffers let var_handle = self.fill_access_chain(module, base, func_ctx)?; let bind_target = self .options .resolve_resource_binding( module.global_variables[var_handle] .binding .as_ref() .unwrap(), ) .unwrap(); bind_target.restrict_indexing } Some( crate::AddressSpace::Handle | crate::AddressSpace::Storage { .. }, ) => unreachable!(), }; // Decide whether this index needs to be clamped to fall within range. let restriction_needed = if needs_bound_check { index::access_needs_check( base, index::GuardedIndex::Expression(index), module, func_ctx.expressions, func_ctx.info, ) } else { None }; if let Some(limit) = restriction_needed { write!(self.out, "min(uint(")?; self.write_expr(module, index, func_ctx)?; write!(self.out, "), ")?; match limit { index::IndexableLength::Known(limit) => { write!(self.out, "{}u", limit - 1)?; } index::IndexableLength::Dynamic => unreachable!(), } write!(self.out, ")")?; } else { if non_uniform_qualifier { write!(self.out, "NonUniformResourceIndex(")?; } if let Some(ref info) = array_sampler_info { write!( self.out, "{}[{} + ", info.sampler_index_buffer_name, info.binding_array_base_index_name, )?; } self.write_expr(module, index, func_ctx)?; if array_sampler_info.is_some() { write!(self.out, "]")?; } if non_uniform_qualifier { write!(self.out, ")")?; } } write!(self.out, "]")?; } } Expression::AccessIndex { base, index } => { if let Some(crate::AddressSpace::Storage { .. }) = func_ctx.resolve_type(expr, &module.types).pointer_space() { // do nothing, the chain is written on `Load`/`Store` } else { // See if we need to write the matrix column access in a // special way since the type of `base` is our special // __matCx2 struct. if let Some(MatrixType { rows: crate::VectorSize::Bi, width: 4, .. }) = get_inner_matrix_of_struct_array_member(module, base, func_ctx, true) .or_else(|| get_global_uniform_matrix(module, base, func_ctx)) { self.write_expr(module, base, func_ctx)?; write!(self.out, "._{index}")?; return Ok(()); } let base_ty_res = &func_ctx.info[base].ty; let mut resolved = base_ty_res.inner_with(&module.types); let base_ty_handle = match *resolved { TypeInner::Pointer { base, .. } => { resolved = &module.types[base].inner; Some(base) } _ => base_ty_res.handle(), }; // We treat matrices of the form `matCx2` as a sequence of C `vec2`s. // See the module-level block comment in mod.rs for details. // // We handle matrix reconstruction here for Loads. // Stores are handled directly by `Statement::Store`. if let TypeInner::Struct { ref members, .. } = *resolved { let member = &members[index as usize]; match module.types[member.ty].inner { TypeInner::Matrix { rows: crate::VectorSize::Bi, .. } if member.binding.is_none() => { let ty = base_ty_handle.unwrap(); self.write_wrapped_struct_matrix_get_function_name( WrappedStructMatrixAccess { ty, index }, )?; write!(self.out, "(")?; self.write_expr(module, base, func_ctx)?; write!(self.out, ")")?; return Ok(()); } _ => {} } } let array_sampler_info = self.sampler_binding_array_info_from_expression( module, func_ctx, base, resolved, ); if let Some(ref info) = array_sampler_info { write!( self.out, "{}[{}", info.sampler_heap_name, info.sampler_index_buffer_name )?; } self.write_expr(module, base, func_ctx)?; match *resolved { // We specifically lift the ValuePointer to this case. While `[0]` is valid // HLSL for any vector behind a value pointer, FXC completely miscompiles // it and generates completely nonsensical DXBC. // // See https://github.com/gfx-rs/naga/issues/2095 for more details. TypeInner::Vector { .. } | TypeInner::ValuePointer { .. } => { // Write vector access as a swizzle write!(self.out, ".{}", back::COMPONENTS[index as usize])? } TypeInner::Matrix { .. } | TypeInner::Array { .. } | TypeInner::BindingArray { .. } => { if let Some(ref info) = array_sampler_info { write!( self.out, "[{} + {index}]", info.binding_array_base_index_name )?; } else { write!(self.out, "[{index}]")?; } } TypeInner::Struct { .. } => { // This will never panic in case the type is a `Struct`, this is not true // for other types so we can only check while inside this match arm let ty = base_ty_handle.unwrap(); write!( self.out, ".{}", &self.names[&NameKey::StructMember(ty, index)] )? } ref other => return Err(Error::Custom(format!("Cannot index {other:?}"))), } if array_sampler_info.is_some() { write!(self.out, "]")?; } } } Expression::FunctionArgument(pos) => { let ty = func_ctx.resolve_type(expr, &module.types); // We know that any external texture function argument has been expanded into // separate consecutive arguments for each plane and the parameters buffer. And we // also know that external textures can only ever be used as an argument to another // function. Therefore we can simply emit each of the expanded arguments in a // consecutive comma-separated list. if let TypeInner::Image { class: crate::ImageClass::External, .. } = *ty { let plane_names = [0, 1, 2].map(|i| { &self.names[&func_ctx .external_texture_argument_key(pos, ExternalTextureNameKey::Plane(i))] }); let params_name = &self.names[&func_ctx .external_texture_argument_key(pos, ExternalTextureNameKey::Params)]; write!( self.out, "{}, {}, {}, {}", plane_names[0], plane_names[1], plane_names[2], params_name )?; } else { let key = func_ctx.argument_key(pos); let name = &self.names[&key]; write!(self.out, "{name}")?; } } Expression::ImageSample { coordinate, image, sampler, clamp_to_edge: true, gather: None, array_index: None, offset: None, level: crate::SampleLevel::Zero, depth_ref: None, } => { write!(self.out, "{IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION}(")?; self.write_expr(module, image, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, sampler, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, coordinate, func_ctx)?; write!(self.out, ")")?; } Expression::ImageSample { image, sampler, gather, coordinate, array_index, offset, level, depth_ref, clamp_to_edge, } => { if clamp_to_edge { return Err(Error::Custom( "ImageSample::clamp_to_edge should have been validated out".to_string(), )); } use crate::SampleLevel as Sl; const COMPONENTS: [&str; 4] = ["", "Green", "Blue", "Alpha"]; let (base_str, component_str) = match gather { Some(component) => ("Gather", COMPONENTS[component as usize]), None => ("Sample", ""), }; let cmp_str = match depth_ref { Some(_) => "Cmp", None => "", }; let level_str = match level { Sl::Zero if gather.is_none() => "LevelZero", Sl::Auto | Sl::Zero => "", Sl::Exact(_) => "Level", Sl::Bias(_) => "Bias", Sl::Gradient { .. } => "Grad", }; self.write_expr(module, image, func_ctx)?; write!(self.out, ".{base_str}{cmp_str}{component_str}{level_str}(")?; self.write_expr(module, sampler, func_ctx)?; write!(self.out, ", ")?; self.write_texture_coordinates( "float", coordinate, array_index, None, module, func_ctx, )?; if let Some(depth_ref) = depth_ref { write!(self.out, ", ")?; self.write_expr(module, depth_ref, func_ctx)?; } match level { Sl::Auto | Sl::Zero => {} Sl::Exact(expr) => { write!(self.out, ", ")?; self.write_expr(module, expr, func_ctx)?; } Sl::Bias(expr) => { write!(self.out, ", ")?; self.write_expr(module, expr, func_ctx)?; } Sl::Gradient { x, y } => { write!(self.out, ", ")?; self.write_expr(module, x, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, y, func_ctx)?; } } if let Some(offset) = offset { write!(self.out, ", ")?; write!(self.out, "int2(")?; // work around https://github.com/microsoft/DirectXShaderCompiler/issues/5082#issuecomment-1540147807 self.write_const_expression(module, offset, func_ctx.expressions)?; write!(self.out, ")")?; } write!(self.out, ")")?; } Expression::ImageQuery { image, query } => { // use wrapped image query function if let TypeInner::Image { dim, arrayed, class, } = *func_ctx.resolve_type(image, &module.types) { let wrapped_image_query = WrappedImageQuery { dim, arrayed, class, query: query.into(), }; self.write_wrapped_image_query_function_name(wrapped_image_query)?; write!(self.out, "(")?; // Image always first param self.write_expr(module, image, func_ctx)?; if let crate::ImageQuery::Size { level: Some(level) } = query { write!(self.out, ", ")?; self.write_expr(module, level, func_ctx)?; } write!(self.out, ")")?; } } Expression::ImageLoad { image, coordinate, array_index, sample, level, } => self.write_image_load( &module, expr, func_ctx, image, coordinate, array_index, sample, level, )?, Expression::GlobalVariable(handle) => { let global_variable = &module.global_variables[handle]; let ty = &module.types[global_variable.ty].inner; // In the case of binding arrays of samplers, we need to not write anything // as the we are in the wrong position to fully write the expression. // // The entire writing is done by AccessIndex. let is_binding_array_of_samplers = match *ty { TypeInner::BindingArray { base, .. } => { let base_ty = &module.types[base].inner; matches!(*base_ty, TypeInner::Sampler { .. }) } _ => false, }; let is_storage_space = matches!(global_variable.space, crate::AddressSpace::Storage { .. }); // Our external texture global variable has been expanded into multiple // global variables, one for each plane and the parameters buffer. // External textures can only ever be used as arguments to a function // call, and we know that an external texture argument to any function // will have been expanded to separate consecutive arguments for each // plane and the parameters buffer. Therefore we can simply emit each of // the expanded global variables in a consecutive comma-separated list. if let TypeInner::Image { class: crate::ImageClass::External, .. } = *ty { let plane_names = [0, 1, 2].map(|i| { &self.names[&NameKey::ExternalTextureGlobalVariable( handle, ExternalTextureNameKey::Plane(i), )] }); let params_name = &self.names[&NameKey::ExternalTextureGlobalVariable( handle, ExternalTextureNameKey::Params, )]; write!( self.out, "{}, {}, {}, {}", plane_names[0], plane_names[1], plane_names[2], params_name )?; } else if !is_binding_array_of_samplers && !is_storage_space { let name = &self.names[&NameKey::GlobalVariable(handle)]; write!(self.out, "{name}")?; } } Expression::LocalVariable(handle) => { write!(self.out, "{}", self.names[&func_ctx.name_key(handle)])? } Expression::Load { pointer } => { match func_ctx .resolve_type(pointer, &module.types) .pointer_space() { Some(crate::AddressSpace::Storage { .. }) => { let var_handle = self.fill_access_chain(module, pointer, func_ctx)?; let result_ty = func_ctx.info[expr].ty.clone(); self.write_storage_load(module, var_handle, result_ty, func_ctx)?; } _ => { let mut close_paren = false; // We cast the value loaded to a native HLSL floatCx2 // in cases where it is of type: // - __matCx2 or // - a (possibly nested) array of __matCx2's if let Some(MatrixType { rows: crate::VectorSize::Bi, width: 4, .. }) = get_inner_matrix_of_struct_array_member( module, pointer, func_ctx, false, ) .or_else(|| get_inner_matrix_of_global_uniform(module, pointer, func_ctx)) { let mut resolved = func_ctx.resolve_type(pointer, &module.types); let ptr_tr = resolved.pointer_base_type(); if let Some(ptr_ty) = ptr_tr.as_ref().map(|tr| tr.inner_with(&module.types)) { resolved = ptr_ty; } write!(self.out, "((")?; if let TypeInner::Array { base, size, .. } = *resolved { self.write_type(module, base)?; self.write_array_size(module, base, size)?; } else { self.write_value_type(module, resolved)?; } write!(self.out, ")")?; close_paren = true; } self.write_expr(module, pointer, func_ctx)?; if close_paren { write!(self.out, ")")?; } } } } Expression::Unary { op, expr } => { // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-operators#unary-operators let op_str = match op { crate::UnaryOperator::Negate => { match func_ctx.resolve_type(expr, &module.types).scalar() { Some(Scalar::I32) => NEG_FUNCTION, _ => "-", } } crate::UnaryOperator::LogicalNot => "!", crate::UnaryOperator::BitwiseNot => "~", }; write!(self.out, "{op_str}(")?; self.write_expr(module, expr, func_ctx)?; write!(self.out, ")")?; } Expression::As { expr, kind, convert, } => { let inner = func_ctx.resolve_type(expr, &module.types); if inner.scalar_kind() == Some(ScalarKind::Float) && (kind == ScalarKind::Sint || kind == ScalarKind::Uint) && convert.is_some() { // Use helper functions for float to int casts in order to // avoid undefined behaviour when value is out of range for // the target type. let fun_name = match (kind, convert) { (ScalarKind::Sint, Some(4)) => F2I32_FUNCTION, (ScalarKind::Uint, Some(4)) => F2U32_FUNCTION, (ScalarKind::Sint, Some(8)) => F2I64_FUNCTION, (ScalarKind::Uint, Some(8)) => F2U64_FUNCTION, _ => unreachable!(), }; write!(self.out, "{fun_name}(")?; self.write_expr(module, expr, func_ctx)?; write!(self.out, ")")?; } else { let close_paren = match convert { Some(dst_width) => { let scalar = Scalar { kind, width: dst_width, }; match *inner { TypeInner::Vector { size, .. } => { write!( self.out, "{}{}(", scalar.to_hlsl_str()?, common::vector_size_str(size) )?; } TypeInner::Scalar(_) => { write!(self.out, "{}(", scalar.to_hlsl_str()?,)?; } TypeInner::Matrix { columns, rows, .. } => { write!( self.out, "{}{}x{}(", scalar.to_hlsl_str()?, common::vector_size_str(columns), common::vector_size_str(rows) )?; } _ => { return Err(Error::Unimplemented(format!( "write_expr expression::as {inner:?}" ))); } }; true } None => { if inner.scalar_width() == Some(8) { false } else { write!(self.out, "{}(", kind.to_hlsl_cast(),)?; true } } }; self.write_expr(module, expr, func_ctx)?; if close_paren { write!(self.out, ")")?; } } } Expression::Math { fun, arg, arg1, arg2, arg3, } => { use crate::MathFunction as Mf; enum Function { Asincosh { is_sin: bool }, Atanh, Pack2x16float, Pack2x16snorm, Pack2x16unorm, Pack4x8snorm, Pack4x8unorm, Pack4xI8, Pack4xU8, Pack4xI8Clamp, Pack4xU8Clamp, Unpack2x16float, Unpack2x16snorm, Unpack2x16unorm, Unpack4x8snorm, Unpack4x8unorm, Unpack4xI8, Unpack4xU8, Dot4I8Packed, Dot4U8Packed, QuantizeToF16, Regular(&'static str), MissingIntOverload(&'static str), MissingIntReturnType(&'static str), CountTrailingZeros, CountLeadingZeros, } let fun = match fun { // comparison Mf::Abs => match func_ctx.resolve_type(arg, &module.types).scalar() { Some(Scalar::I32) => Function::Regular(ABS_FUNCTION), _ => Function::Regular("abs"), }, Mf::Min => Function::Regular("min"), Mf::Max => Function::Regular("max"), Mf::Clamp => Function::Regular("clamp"), Mf::Saturate => Function::Regular("saturate"), // trigonometry Mf::Cos => Function::Regular("cos"), Mf::Cosh => Function::Regular("cosh"), Mf::Sin => Function::Regular("sin"), Mf::Sinh => Function::Regular("sinh"), Mf::Tan => Function::Regular("tan"), Mf::Tanh => Function::Regular("tanh"), Mf::Acos => Function::Regular("acos"), Mf::Asin => Function::Regular("asin"), Mf::Atan => Function::Regular("atan"), Mf::Atan2 => Function::Regular("atan2"), Mf::Asinh => Function::Asincosh { is_sin: true }, Mf::Acosh => Function::Asincosh { is_sin: false }, Mf::Atanh => Function::Atanh, Mf::Radians => Function::Regular("radians"), Mf::Degrees => Function::Regular("degrees"), // decomposition Mf::Ceil => Function::Regular("ceil"), Mf::Floor => Function::Regular("floor"), Mf::Round => Function::Regular("round"), Mf::Fract => Function::Regular("frac"), Mf::Trunc => Function::Regular("trunc"), Mf::Modf => Function::Regular(MODF_FUNCTION), Mf::Frexp => Function::Regular(FREXP_FUNCTION), Mf::Ldexp => Function::Regular("ldexp"), // exponent Mf::Exp => Function::Regular("exp"), Mf::Exp2 => Function::Regular("exp2"), Mf::Log => Function::Regular("log"), Mf::Log2 => Function::Regular("log2"), Mf::Pow => Function::Regular("pow"), // geometry Mf::Dot => Function::Regular("dot"), Mf::Dot4I8Packed => Function::Dot4I8Packed, Mf::Dot4U8Packed => Function::Dot4U8Packed, //Mf::Outer => , Mf::Cross => Function::Regular("cross"), Mf::Distance => Function::Regular("distance"), Mf::Length => Function::Regular("length"), Mf::Normalize => Function::Regular("normalize"), Mf::FaceForward => Function::Regular("faceforward"), Mf::Reflect => Function::Regular("reflect"), Mf::Refract => Function::Regular("refract"), // computational Mf::Sign => Function::Regular("sign"), Mf::Fma => Function::Regular("mad"), Mf::Mix => Function::Regular("lerp"), Mf::Step => Function::Regular("step"), Mf::SmoothStep => Function::Regular("smoothstep"), Mf::Sqrt => Function::Regular("sqrt"), Mf::InverseSqrt => Function::Regular("rsqrt"), //Mf::Inverse =>, Mf::Transpose => Function::Regular("transpose"), Mf::Determinant => Function::Regular("determinant"), Mf::QuantizeToF16 => Function::QuantizeToF16, // bits Mf::CountTrailingZeros => Function::CountTrailingZeros, Mf::CountLeadingZeros => Function::CountLeadingZeros, Mf::CountOneBits => Function::MissingIntOverload("countbits"), Mf::ReverseBits => Function::MissingIntOverload("reversebits"), Mf::FirstTrailingBit => Function::MissingIntReturnType("firstbitlow"), Mf::FirstLeadingBit => Function::MissingIntReturnType("firstbithigh"), Mf::ExtractBits => Function::Regular(EXTRACT_BITS_FUNCTION), Mf::InsertBits => Function::Regular(INSERT_BITS_FUNCTION), // Data Packing Mf::Pack2x16float => Function::Pack2x16float, Mf::Pack2x16snorm => Function::Pack2x16snorm, Mf::Pack2x16unorm => Function::Pack2x16unorm, Mf::Pack4x8snorm => Function::Pack4x8snorm, Mf::Pack4x8unorm => Function::Pack4x8unorm, Mf::Pack4xI8 => Function::Pack4xI8, Mf::Pack4xU8 => Function::Pack4xU8, Mf::Pack4xI8Clamp => Function::Pack4xI8Clamp, Mf::Pack4xU8Clamp => Function::Pack4xU8Clamp, // Data Unpacking Mf::Unpack2x16float => Function::Unpack2x16float, Mf::Unpack2x16snorm => Function::Unpack2x16snorm, Mf::Unpack2x16unorm => Function::Unpack2x16unorm, Mf::Unpack4x8snorm => Function::Unpack4x8snorm, Mf::Unpack4x8unorm => Function::Unpack4x8unorm, Mf::Unpack4xI8 => Function::Unpack4xI8, Mf::Unpack4xU8 => Function::Unpack4xU8, _ => return Err(Error::Unimplemented(format!("write_expr_math {fun:?}"))), }; match fun { Function::Asincosh { is_sin } => { write!(self.out, "log(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " + sqrt(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " * ")?; self.write_expr(module, arg, func_ctx)?; match is_sin { true => write!(self.out, " + 1.0))")?, false => write!(self.out, " - 1.0))")?, } } Function::Atanh => { write!(self.out, "0.5 * log((1.0 + ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ") / (1.0 - ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "))")?; } Function::Pack2x16float => { write!(self.out, "(f32tof16(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "[0]) | f32tof16(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "[1]) << 16)")?; } Function::Pack2x16snorm => { let scale = 32767; write!(self.out, "uint((int(round(clamp(")?; self.write_expr(module, arg, func_ctx)?; write!( self.out, "[0], -1.0, 1.0) * {scale}.0)) & 0xFFFF) | ((int(round(clamp(" )?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "[1], -1.0, 1.0) * {scale}.0)) & 0xFFFF) << 16))",)?; } Function::Pack2x16unorm => { let scale = 65535; write!(self.out, "(uint(round(clamp(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "[0], 0.0, 1.0) * {scale}.0)) | uint(round(clamp(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "[1], 0.0, 1.0) * {scale}.0)) << 16)")?; } Function::Pack4x8snorm => { let scale = 127; write!(self.out, "uint((int(round(clamp(")?; self.write_expr(module, arg, func_ctx)?; write!( self.out, "[0], -1.0, 1.0) * {scale}.0)) & 0xFF) | ((int(round(clamp(" )?; self.write_expr(module, arg, func_ctx)?; write!( self.out, "[1], -1.0, 1.0) * {scale}.0)) & 0xFF) << 8) | ((int(round(clamp(" )?; self.write_expr(module, arg, func_ctx)?; write!( self.out, "[2], -1.0, 1.0) * {scale}.0)) & 0xFF) << 16) | ((int(round(clamp(" )?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "[3], -1.0, 1.0) * {scale}.0)) & 0xFF) << 24))",)?; } Function::Pack4x8unorm => { let scale = 255; write!(self.out, "(uint(round(clamp(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "[0], 0.0, 1.0) * {scale}.0)) | uint(round(clamp(")?; self.write_expr(module, arg, func_ctx)?; write!( self.out, "[1], 0.0, 1.0) * {scale}.0)) << 8 | uint(round(clamp(" )?; self.write_expr(module, arg, func_ctx)?; write!( self.out, "[2], 0.0, 1.0) * {scale}.0)) << 16 | uint(round(clamp(" )?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "[3], 0.0, 1.0) * {scale}.0)) << 24)")?; } fun @ (Function::Pack4xI8 | Function::Pack4xU8 | Function::Pack4xI8Clamp | Function::Pack4xU8Clamp) => { let was_signed = matches!(fun, Function::Pack4xI8 | Function::Pack4xI8Clamp); let clamp_bounds = match fun { Function::Pack4xI8Clamp => Some(("-128", "127")), Function::Pack4xU8Clamp => Some(("0", "255")), _ => None, }; if was_signed { write!(self.out, "uint(")?; } let write_arg = |this: &mut Self| -> BackendResult { if let Some((min, max)) = clamp_bounds { write!(this.out, "clamp(")?; this.write_expr(module, arg, func_ctx)?; write!(this.out, ", {min}, {max})")?; } else { this.write_expr(module, arg, func_ctx)?; } Ok(()) }; write!(self.out, "(")?; write_arg(self)?; write!(self.out, "[0] & 0xFF) | ((")?; write_arg(self)?; write!(self.out, "[1] & 0xFF) << 8) | ((")?; write_arg(self)?; write!(self.out, "[2] & 0xFF) << 16) | ((")?; write_arg(self)?; write!(self.out, "[3] & 0xFF) << 24)")?; if was_signed { write!(self.out, ")")?; } } Function::Unpack2x16float => { write!(self.out, "float2(f16tof32(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "), f16tof32((")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ") >> 16))")?; } Function::Unpack2x16snorm => { let scale = 32767; write!(self.out, "(float2(int2(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " << 16, ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ") >> 16) / {scale}.0)")?; } Function::Unpack2x16unorm => { let scale = 65535; write!(self.out, "(float2(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " & 0xFFFF, ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " >> 16) / {scale}.0)")?; } Function::Unpack4x8snorm => { let scale = 127; write!(self.out, "(float4(int4(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " << 24, ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " << 16, ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " << 8, ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ") >> 24) / {scale}.0)")?; } Function::Unpack4x8unorm => { let scale = 255; write!(self.out, "(float4(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " & 0xFF, ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " >> 8 & 0xFF, ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " >> 16 & 0xFF, ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " >> 24) / {scale}.0)")?; } fun @ (Function::Unpack4xI8 | Function::Unpack4xU8) => { write!(self.out, "(")?; if matches!(fun, Function::Unpack4xU8) { write!(self.out, "u")?; } write!(self.out, "int4(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " >> 8, ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " >> 16, ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " >> 24) << 24 >> 24)")?; } fun @ (Function::Dot4I8Packed | Function::Dot4U8Packed) => { let arg1 = arg1.unwrap(); if self.options.shader_model >= ShaderModel::V6_4 { // Intrinsics `dot4add_{i, u}8packed` are available in SM 6.4 and later. let function_name = match fun { Function::Dot4I8Packed => "dot4add_i8packed", Function::Dot4U8Packed => "dot4add_u8packed", _ => unreachable!(), }; write!(self.out, "{function_name}(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, arg1, func_ctx)?; write!(self.out, ", 0)")?; } else { // Fall back to a polyfill as `dot4add_u8packed` is not available. write!(self.out, "dot(")?; if matches!(fun, Function::Dot4U8Packed) { write!(self.out, "u")?; } write!(self.out, "int4(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " >> 8, ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " >> 16, ")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, " >> 24) << 24 >> 24, ")?; if matches!(fun, Function::Dot4U8Packed) { write!(self.out, "u")?; } write!(self.out, "int4(")?; self.write_expr(module, arg1, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, arg1, func_ctx)?; write!(self.out, " >> 8, ")?; self.write_expr(module, arg1, func_ctx)?; write!(self.out, " >> 16, ")?; self.write_expr(module, arg1, func_ctx)?; write!(self.out, " >> 24) << 24 >> 24)")?; } } Function::QuantizeToF16 => { write!(self.out, "f16tof32(f32tof16(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "))")?; } Function::Regular(fun_name) => { write!(self.out, "{fun_name}(")?; self.write_expr(module, arg, func_ctx)?; if let Some(arg) = arg1 { write!(self.out, ", ")?; self.write_expr(module, arg, func_ctx)?; } if let Some(arg) = arg2 { write!(self.out, ", ")?; self.write_expr(module, arg, func_ctx)?; } if let Some(arg) = arg3 { write!(self.out, ", ")?; self.write_expr(module, arg, func_ctx)?; } write!(self.out, ")")? } // These overloads are only missing on FXC, so this is only needed for 32bit types, // as non-32bit types are DXC only. Function::MissingIntOverload(fun_name) => { let scalar_kind = func_ctx.resolve_type(arg, &module.types).scalar(); if let Some(Scalar::I32) = scalar_kind { write!(self.out, "asint({fun_name}(asuint(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ")))")?; } else { write!(self.out, "{fun_name}(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ")")?; } } // These overloads are only missing on FXC, so this is only needed for 32bit types, // as non-32bit types are DXC only. Function::MissingIntReturnType(fun_name) => { let scalar_kind = func_ctx.resolve_type(arg, &module.types).scalar(); if let Some(Scalar::I32) = scalar_kind { write!(self.out, "asint({fun_name}(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "))")?; } else { write!(self.out, "{fun_name}(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ")")?; } } Function::CountTrailingZeros => { match *func_ctx.resolve_type(arg, &module.types) { TypeInner::Vector { size, scalar } => { let s = match size { crate::VectorSize::Bi => ".xx", crate::VectorSize::Tri => ".xxx", crate::VectorSize::Quad => ".xxxx", }; let scalar_width_bits = scalar.width * 8; if scalar.kind == ScalarKind::Uint || scalar.width != 4 { write!( self.out, "min(({scalar_width_bits}u){s}, firstbitlow(" )?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "))")?; } else { // This is only needed for the FXC path, on 32bit signed integers. write!( self.out, "asint(min(({scalar_width_bits}u){s}, firstbitlow(" )?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ")))")?; } } TypeInner::Scalar(scalar) => { let scalar_width_bits = scalar.width * 8; if scalar.kind == ScalarKind::Uint || scalar.width != 4 { write!(self.out, "min({scalar_width_bits}u, firstbitlow(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "))")?; } else { // This is only needed for the FXC path, on 32bit signed integers. write!( self.out, "asint(min({scalar_width_bits}u, firstbitlow(" )?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ")))")?; } } _ => unreachable!(), } return Ok(()); } Function::CountLeadingZeros => { match *func_ctx.resolve_type(arg, &module.types) { TypeInner::Vector { size, scalar } => { let s = match size { crate::VectorSize::Bi => ".xx", crate::VectorSize::Tri => ".xxx", crate::VectorSize::Quad => ".xxxx", }; // scalar width - 1 let constant = scalar.width * 8 - 1; if scalar.kind == ScalarKind::Uint { write!(self.out, "(({constant}u){s} - firstbithigh(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "))")?; } else { let conversion_func = match scalar.width { 4 => "asint", _ => "", }; write!(self.out, "(")?; self.write_expr(module, arg, func_ctx)?; write!( self.out, " < (0){s} ? (0){s} : ({constant}){s} - {conversion_func}(firstbithigh(" )?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ")))")?; } } TypeInner::Scalar(scalar) => { // scalar width - 1 let constant = scalar.width * 8 - 1; if let ScalarKind::Uint = scalar.kind { write!(self.out, "({constant}u - firstbithigh(")?; self.write_expr(module, arg, func_ctx)?; write!(self.out, "))")?; } else { let conversion_func = match scalar.width { 4 => "asint", _ => "", }; write!(self.out, "(")?; self.write_expr(module, arg, func_ctx)?; write!( self.out, " < 0 ? 0 : {constant} - {conversion_func}(firstbithigh(" )?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ")))")?; } } _ => unreachable!(), } return Ok(()); } } } Expression::Swizzle { size, vector, pattern, } => { self.write_expr(module, vector, func_ctx)?; write!(self.out, ".")?; for &sc in pattern[..size as usize].iter() { self.out.write_char(back::COMPONENTS[sc as usize])?; } } Expression::ArrayLength(expr) => { let var_handle = match func_ctx.expressions[expr] { Expression::AccessIndex { base, index: _ } => { match func_ctx.expressions[base] { Expression::GlobalVariable(handle) => handle, _ => unreachable!(), } } Expression::GlobalVariable(handle) => handle, _ => unreachable!(), }; let var = &module.global_variables[var_handle]; let (offset, stride) = match module.types[var.ty].inner { TypeInner::Array { stride, .. } => (0, stride), TypeInner::Struct { ref members, .. } => { let last = members.last().unwrap(); let stride = match module.types[last.ty].inner { TypeInner::Array { stride, .. } => stride, _ => unreachable!(), }; (last.offset, stride) } _ => unreachable!(), }; let storage_access = match var.space { crate::AddressSpace::Storage { access } => access, _ => crate::StorageAccess::default(), }; let wrapped_array_length = WrappedArrayLength { writable: storage_access.contains(crate::StorageAccess::STORE), }; write!(self.out, "((")?; self.write_wrapped_array_length_function_name(wrapped_array_length)?; let var_name = &self.names[&NameKey::GlobalVariable(var_handle)]; write!(self.out, "({var_name}) - {offset}) / {stride})")? } Expression::Derivative { axis, ctrl, expr } => { use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; if axis == Axis::Width && (ctrl == Ctrl::Coarse || ctrl == Ctrl::Fine) { let tail = match ctrl { Ctrl::Coarse => "coarse", Ctrl::Fine => "fine", Ctrl::None => unreachable!(), }; write!(self.out, "abs(ddx_{tail}(")?; self.write_expr(module, expr, func_ctx)?; write!(self.out, ")) + abs(ddy_{tail}(")?; self.write_expr(module, expr, func_ctx)?; write!(self.out, "))")? } else { let fun_str = match (axis, ctrl) { (Axis::X, Ctrl::Coarse) => "ddx_coarse", (Axis::X, Ctrl::Fine) => "ddx_fine", (Axis::X, Ctrl::None) => "ddx", (Axis::Y, Ctrl::Coarse) => "ddy_coarse", (Axis::Y, Ctrl::Fine) => "ddy_fine", (Axis::Y, Ctrl::None) => "ddy", (Axis::Width, Ctrl::Coarse | Ctrl::Fine) => unreachable!(), (Axis::Width, Ctrl::None) => "fwidth", }; write!(self.out, "{fun_str}(")?; self.write_expr(module, expr, func_ctx)?; write!(self.out, ")")? } } Expression::Relational { fun, argument } => { use crate::RelationalFunction as Rf; let fun_str = match fun { Rf::All => "all", Rf::Any => "any", Rf::IsNan => "isnan", Rf::IsInf => "isinf", }; write!(self.out, "{fun_str}(")?; self.write_expr(module, argument, func_ctx)?; write!(self.out, ")")? } Expression::Select { condition, accept, reject, } => { write!(self.out, "(")?; self.write_expr(module, condition, func_ctx)?; write!(self.out, " ? ")?; self.write_expr(module, accept, func_ctx)?; write!(self.out, " : ")?; self.write_expr(module, reject, func_ctx)?; write!(self.out, ")")? } Expression::RayQueryGetIntersection { query, committed } => { // For reasoning, see write_stmt let Expression::LocalVariable(query_var) = func_ctx.expressions[query] else { unreachable!() }; let tracker_expr_name = format!( "{RAY_QUERY_TRACKER_VARIABLE_PREFIX}{}", self.names[&func_ctx.name_key(query_var)] ); if committed { write!(self.out, "GetCommittedIntersection(")?; self.write_expr(module, query, func_ctx)?; write!(self.out, ", {tracker_expr_name})")?; } else { write!(self.out, "GetCandidateIntersection(")?; self.write_expr(module, query, func_ctx)?; write!(self.out, ", {tracker_expr_name})")?; } } // Not supported yet Expression::RayQueryVertexPositions { .. } | Expression::CooperativeLoad { .. } | Expression::CooperativeMultiplyAdd { .. } => { unreachable!() } // Nothing to do here, since call expression already cached Expression::CallResult(_) | Expression::AtomicResult { .. } | Expression::WorkGroupUniformLoadResult { .. } | Expression::RayQueryProceedResult | Expression::SubgroupBallotResult | Expression::SubgroupOperationResult { .. } => {} } if !closing_bracket.is_empty() { write!(self.out, "{closing_bracket}")?; } Ok(()) } #[allow(clippy::too_many_arguments)] fn write_image_load( &mut self, module: &&Module, expr: Handle, func_ctx: &back::FunctionCtx, image: Handle, coordinate: Handle, array_index: Option>, sample: Option>, level: Option>, ) -> Result<(), Error> { let mut wrapping_type = None; match *func_ctx.resolve_type(image, &module.types) { TypeInner::Image { class: crate::ImageClass::External, .. } => { write!(self.out, "{IMAGE_LOAD_EXTERNAL_FUNCTION}(")?; self.write_expr(module, image, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, coordinate, func_ctx)?; write!(self.out, ")")?; return Ok(()); } TypeInner::Image { class: crate::ImageClass::Storage { format, .. }, .. } => { if format.single_component() { wrapping_type = Some(Scalar::from(format)); } } _ => {} } if let Some(scalar) = wrapping_type { write!( self.out, "{}{}(", help::IMAGE_STORAGE_LOAD_SCALAR_WRAPPER, scalar.to_hlsl_str()? )?; } // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-to-load self.write_expr(module, image, func_ctx)?; write!(self.out, ".Load(")?; self.write_texture_coordinates("int", coordinate, array_index, level, module, func_ctx)?; if let Some(sample) = sample { write!(self.out, ", ")?; self.write_expr(module, sample, func_ctx)?; } // close bracket for Load function write!(self.out, ")")?; if wrapping_type.is_some() { write!(self.out, ")")?; } // return x component if return type is scalar if let TypeInner::Scalar(_) = *func_ctx.resolve_type(expr, &module.types) { write!(self.out, ".x")?; } Ok(()) } /// Find the [`BindingArraySamplerInfo`] from an expression so that such an access /// can be generated later. fn sampler_binding_array_info_from_expression( &mut self, module: &Module, func_ctx: &back::FunctionCtx<'_>, base: Handle, resolved: &TypeInner, ) -> Option { if let TypeInner::BindingArray { base: base_ty_handle, .. } = *resolved { let base_ty = &module.types[base_ty_handle].inner; if let TypeInner::Sampler { comparison, .. } = *base_ty { let base = &func_ctx.expressions[base]; if let crate::Expression::GlobalVariable(handle) = *base { let variable = &module.global_variables[handle]; let sampler_heap_name = match comparison { true => COMPARISON_SAMPLER_HEAP_VAR, false => SAMPLER_HEAP_VAR, }; return Some(BindingArraySamplerInfo { sampler_heap_name, sampler_index_buffer_name: self .wrapped .sampler_index_buffers .get(&super::SamplerIndexBufferKey { group: variable.binding.unwrap().group, }) .unwrap() .clone(), binding_array_base_index_name: self.names[&NameKey::GlobalVariable(handle)] .clone(), }); } } } None } fn write_named_expr( &mut self, module: &Module, handle: Handle, name: String, // The expression which is being named. // Generally, this is the same as handle, except in WorkGroupUniformLoad named: Handle, ctx: &back::FunctionCtx, ) -> BackendResult { match ctx.info[named].ty { proc::TypeResolution::Handle(ty_handle) => match module.types[ty_handle].inner { TypeInner::Struct { .. } => { let ty_name = &self.names[&NameKey::Type(ty_handle)]; write!(self.out, "{ty_name}")?; } _ => { self.write_type(module, ty_handle)?; } }, proc::TypeResolution::Value(ref inner) => { self.write_value_type(module, inner)?; } } let resolved = ctx.resolve_type(named, &module.types); write!(self.out, " {name}")?; // If rhs is a array type, we should write array size if let TypeInner::Array { base, size, .. } = *resolved { self.write_array_size(module, base, size)?; } write!(self.out, " = ")?; self.write_expr(module, handle, ctx)?; writeln!(self.out, ";")?; self.named_expressions.insert(named, name); Ok(()) } /// Helper function that write default zero initialization pub(super) fn write_default_init( &mut self, module: &Module, ty: Handle, ) -> BackendResult { write!(self.out, "(")?; self.write_type(module, ty)?; if let TypeInner::Array { base, size, .. } = module.types[ty].inner { self.write_array_size(module, base, size)?; } write!(self.out, ")0")?; Ok(()) } fn write_control_barrier( &mut self, barrier: crate::Barrier, level: back::Level, ) -> BackendResult { if barrier.contains(crate::Barrier::STORAGE) { writeln!(self.out, "{level}DeviceMemoryBarrierWithGroupSync();")?; } if barrier.contains(crate::Barrier::WORK_GROUP) { writeln!(self.out, "{level}GroupMemoryBarrierWithGroupSync();")?; } if barrier.contains(crate::Barrier::SUB_GROUP) { // Does not exist in DirectX } if barrier.contains(crate::Barrier::TEXTURE) { writeln!(self.out, "{level}DeviceMemoryBarrierWithGroupSync();")?; } Ok(()) } fn write_memory_barrier( &mut self, barrier: crate::Barrier, level: back::Level, ) -> BackendResult { if barrier.contains(crate::Barrier::STORAGE) { writeln!(self.out, "{level}DeviceMemoryBarrier();")?; } if barrier.contains(crate::Barrier::WORK_GROUP) { writeln!(self.out, "{level}GroupMemoryBarrier();")?; } if barrier.contains(crate::Barrier::SUB_GROUP) { // Does not exist in DirectX } if barrier.contains(crate::Barrier::TEXTURE) { writeln!(self.out, "{level}DeviceMemoryBarrier();")?; } Ok(()) } /// Helper to emit the shared tail of an HLSL atomic call (arguments, value, result) fn emit_hlsl_atomic_tail( &mut self, module: &Module, func_ctx: &back::FunctionCtx<'_>, fun: &crate::AtomicFunction, compare_expr: Option>, value: Handle, res_var_info: &Option<(Handle, String)>, ) -> BackendResult { if let Some(cmp) = compare_expr { write!(self.out, ", ")?; self.write_expr(module, cmp, func_ctx)?; } write!(self.out, ", ")?; if let crate::AtomicFunction::Subtract = *fun { // we just wrote `InterlockedAdd`, so negate the argument write!(self.out, "-")?; } self.write_expr(module, value, func_ctx)?; if let Some(&(_res_handle, ref res_name)) = res_var_info.as_ref() { write!(self.out, ", ")?; if compare_expr.is_some() { write!(self.out, "{res_name}.old_value")?; } else { write!(self.out, "{res_name}")?; } } writeln!(self.out, ");")?; Ok(()) } } pub(super) struct MatrixType { pub(super) columns: crate::VectorSize, pub(super) rows: crate::VectorSize, pub(super) width: crate::Bytes, } pub(super) fn get_inner_matrix_data( module: &Module, handle: Handle, ) -> Option { match module.types[handle].inner { TypeInner::Matrix { columns, rows, scalar, } => Some(MatrixType { columns, rows, width: scalar.width, }), TypeInner::Array { base, .. } => get_inner_matrix_data(module, base), _ => None, } } /// If `base` is an access chain of the form `mat`, `mat[col]`, or `mat[col][row]`, /// returns a tuple of the matrix, the column (vector) index (if present), and /// the row (scalar) index (if present). fn find_matrix_in_access_chain( module: &Module, base: Handle, func_ctx: &back::FunctionCtx<'_>, ) -> Option<(Handle, Option, Option)> { let mut current_base = base; let mut vector = None; let mut scalar = None; loop { let resolved_tr = func_ctx .resolve_type(current_base, &module.types) .pointer_base_type(); let resolved = resolved_tr.as_ref()?.inner_with(&module.types); match *resolved { TypeInner::Matrix { .. } => return Some((current_base, vector, scalar)), TypeInner::Scalar(_) | TypeInner::Vector { .. } => {} _ => return None, } let index; (current_base, index) = match func_ctx.expressions[current_base] { crate::Expression::Access { base, index } => (base, Index::Expression(index)), crate::Expression::AccessIndex { base, index } => (base, Index::Static(index)), _ => return None, }; match *resolved { TypeInner::Scalar(_) => scalar = Some(index), TypeInner::Vector { .. } => vector = Some(index), _ => unreachable!(), } } } /// Returns the matrix data if the access chain starting at `base`: /// - starts with an expression with resolved type of [`TypeInner::Matrix`] if `direct = true` /// - contains one or more expressions with resolved type of [`TypeInner::Array`] of [`TypeInner::Matrix`] /// - ends at an expression with resolved type of [`TypeInner::Struct`] pub(super) fn get_inner_matrix_of_struct_array_member( module: &Module, base: Handle, func_ctx: &back::FunctionCtx<'_>, direct: bool, ) -> Option { let mut mat_data = None; let mut array_base = None; let mut current_base = base; loop { let mut resolved = func_ctx.resolve_type(current_base, &module.types); if let TypeInner::Pointer { base, .. } = *resolved { resolved = &module.types[base].inner; }; match *resolved { TypeInner::Matrix { columns, rows, scalar, } => { mat_data = Some(MatrixType { columns, rows, width: scalar.width, }) } TypeInner::Array { base, .. } => { array_base = Some(base); } TypeInner::Struct { .. } => { if let Some(array_base) = array_base { if direct { return mat_data; } else { return get_inner_matrix_data(module, array_base); } } break; } _ => break, } current_base = match func_ctx.expressions[current_base] { crate::Expression::Access { base, .. } => base, crate::Expression::AccessIndex { base, .. } => base, _ => break, }; } None } /// Simpler version of get_inner_matrix_of_global_uniform that only looks at the /// immediate expression, rather than traversing an access chain. fn get_global_uniform_matrix( module: &Module, base: Handle, func_ctx: &back::FunctionCtx<'_>, ) -> Option { let base_tr = func_ctx .resolve_type(base, &module.types) .pointer_base_type(); let base_ty = base_tr.as_ref().map(|tr| tr.inner_with(&module.types)); match (&func_ctx.expressions[base], base_ty) { ( &crate::Expression::GlobalVariable(handle), Some(&TypeInner::Matrix { columns, rows, scalar, }), ) if module.global_variables[handle].space == crate::AddressSpace::Uniform => { Some(MatrixType { columns, rows, width: scalar.width, }) } _ => None, } } /// Returns the matrix data if the access chain starting at `base`: /// - starts with an expression with resolved type of [`TypeInner::Matrix`] /// - contains zero or more expressions with resolved type of [`TypeInner::Array`] of [`TypeInner::Matrix`] /// - ends with an [`Expression::GlobalVariable`](crate::Expression::GlobalVariable) in [`AddressSpace::Uniform`](crate::AddressSpace::Uniform) fn get_inner_matrix_of_global_uniform( module: &Module, base: Handle, func_ctx: &back::FunctionCtx<'_>, ) -> Option { let mut mat_data = None; let mut array_base = None; let mut current_base = base; loop { let mut resolved = func_ctx.resolve_type(current_base, &module.types); if let TypeInner::Pointer { base, .. } = *resolved { resolved = &module.types[base].inner; }; match *resolved { TypeInner::Matrix { columns, rows, scalar, } => { mat_data = Some(MatrixType { columns, rows, width: scalar.width, }) } TypeInner::Array { base, .. } => { array_base = Some(base); } _ => break, } current_base = match func_ctx.expressions[current_base] { crate::Expression::Access { base, .. } => base, crate::Expression::AccessIndex { base, .. } => base, crate::Expression::GlobalVariable(handle) if module.global_variables[handle].space == crate::AddressSpace::Uniform => { return mat_data.or_else(|| { array_base.and_then(|array_base| get_inner_matrix_data(module, array_base)) }) } _ => break, }; } None } ================================================ FILE: naga/src/back/mod.rs ================================================ /*! Backend functions that export shader [`Module`](super::Module)s into binary and text formats. */ #![cfg_attr( not(any(dot_out, glsl_out, hlsl_out, msl_out, spv_out, wgsl_out)), allow( dead_code, reason = "shared helpers can be dead if none of the enabled backends need it" ) )] use alloc::string::String; #[cfg(dot_out)] pub mod dot; #[cfg(glsl_out)] pub mod glsl; #[cfg(hlsl_out)] pub mod hlsl; #[cfg(msl_out)] pub mod msl; #[cfg(spv_out)] pub mod spv; #[cfg(wgsl_out)] pub mod wgsl; #[cfg(any(hlsl_out, msl_out, spv_out, glsl_out))] pub mod pipeline_constants; #[cfg(any(hlsl_out, glsl_out))] mod continue_forward; /// Names of vector components. pub const COMPONENTS: &[char] = &['x', 'y', 'z', 'w']; /// Indent for backends. pub const INDENT: &str = " "; /// Expressions that need baking. pub type NeedBakeExpressions = crate::FastHashSet>; /// A type for displaying expression handles as baking identifiers. /// /// Given an [`Expression`] [`Handle`] `h`, `Baked(h)` implements /// [`core::fmt::Display`], showing the handle's index prefixed by /// `_e`. /// /// [`Expression`]: crate::Expression /// [`Handle`]: crate::Handle #[cfg_attr( not(any(glsl_out, hlsl_out, msl_out, wgsl_out)), allow( dead_code, reason = "shared helpers can be dead if none of the enabled backends need it" ) )] struct Baked(crate::Handle); impl core::fmt::Display for Baked { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.0.write_prefixed(f, "_e") } } bitflags::bitflags! { /// How far through a ray query are we #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[cfg_attr( not(any(hlsl_out, spv_out)), allow( dead_code, reason = "shared helpers can be dead if none of the enabled backends need it" ) )] pub(super) struct RayQueryPoint: u32 { /// Ray query has been successfully initialized. const INITIALIZED = 1 << 0; /// Proceed has been called on ray query. const PROCEED = 1 << 1; /// Proceed has returned false (have finished traversal). const FINISHED_TRAVERSAL = 1 << 2; } } /// Specifies the values of pipeline-overridable constants in the shader module. /// /// If an `@id` attribute was specified on the declaration, /// the key must be the pipeline constant ID as a decimal ASCII number; if not, /// the key must be the constant's identifier name. /// /// The value may represent any of WGSL's concrete scalar types. pub type PipelineConstants = hashbrown::HashMap; /// Indentation level. #[derive(Clone, Copy)] pub struct Level(pub usize); impl Level { pub const fn next(&self) -> Self { Level(self.0 + 1) } } impl core::fmt::Display for Level { fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { (0..self.0).try_for_each(|_| formatter.write_str(INDENT)) } } /// Locate the entry point(s) to write. /// /// If `entry_point` is given, and the specified entry point exists, returns a /// length-1 `Range` containing the index of that entry point. If no /// `entry_point` is given, returns the complete range of entry point indices. /// If `entry_point` is given but does not exist, returns an error. #[cfg(any(hlsl_out, msl_out))] fn get_entry_points( module: &crate::ir::Module, entry_point: Option<&(crate::ir::ShaderStage, String)>, ) -> Result, (crate::ir::ShaderStage, String)> { use alloc::borrow::ToOwned; if let Some(&(stage, ref name)) = entry_point { let Some(ep_index) = module .entry_points .iter() .position(|ep| ep.stage == stage && ep.name == *name) else { return Err((stage, name.to_owned())); }; Ok(ep_index..ep_index + 1) } else { Ok(0..module.entry_points.len()) } } /// Whether we're generating an entry point or a regular function. /// /// Backend languages often require different code for a [`Function`] /// depending on whether it represents an [`EntryPoint`] or not. /// Backends can pass common code one of these values to select the /// right behavior. /// /// These values also carry enough information to find the `Function` /// in the [`Module`]: the `Handle` for a regular function, or the /// index into [`Module::entry_points`] for an entry point. /// /// [`Function`]: crate::Function /// [`EntryPoint`]: crate::EntryPoint /// [`Module`]: crate::Module /// [`Module::entry_points`]: crate::Module::entry_points #[derive(Clone, Copy, Debug)] pub enum FunctionType { /// A regular function. Function(crate::Handle), /// An [`EntryPoint`], and its index in [`Module::entry_points`]. /// /// [`EntryPoint`]: crate::EntryPoint /// [`Module::entry_points`]: crate::Module::entry_points EntryPoint(crate::proc::EntryPointIndex), } impl FunctionType { /// Returns true if the function is an entry point for a compute-like shader. pub fn is_compute_like_entry_point(&self, module: &crate::Module) -> bool { match *self { FunctionType::EntryPoint(index) => { module.entry_points[index as usize].stage.compute_like() } FunctionType::Function(_) => false, } } } /// Helper structure that stores data needed when writing the function pub struct FunctionCtx<'a> { /// The current function being written pub ty: FunctionType, /// Analysis about the function pub info: &'a crate::valid::FunctionInfo, /// The expression arena of the current function being written pub expressions: &'a crate::Arena, /// Map of expressions that have associated variable names pub named_expressions: &'a crate::NamedExpressions, } impl FunctionCtx<'_> { /// Helper method that resolves a type of a given expression. pub fn resolve_type<'a>( &'a self, handle: crate::Handle, types: &'a crate::UniqueArena, ) -> &'a crate::TypeInner { self.info[handle].ty.inner_with(types) } /// Helper method that generates a [`NameKey`](crate::proc::NameKey) for a local in the current function pub const fn name_key( &self, local: crate::Handle, ) -> crate::proc::NameKey { match self.ty { FunctionType::Function(handle) => crate::proc::NameKey::FunctionLocal(handle, local), FunctionType::EntryPoint(idx) => crate::proc::NameKey::EntryPointLocal(idx, local), } } /// Helper method that generates a [`NameKey`](crate::proc::NameKey) for a function argument. /// /// # Panics /// - If the function arguments are less or equal to `arg` pub const fn argument_key(&self, arg: u32) -> crate::proc::NameKey { match self.ty { FunctionType::Function(handle) => crate::proc::NameKey::FunctionArgument(handle, arg), FunctionType::EntryPoint(ep_index) => { crate::proc::NameKey::EntryPointArgument(ep_index, arg) } } } /// Helper method that generates a [`NameKey`](crate::proc::NameKey) for an external texture /// function argument. /// /// # Panics /// - If the function arguments are less or equal to `arg` /// - If `self.ty` is not `FunctionType::Function`. pub const fn external_texture_argument_key( &self, arg: u32, external_texture_key: crate::proc::ExternalTextureNameKey, ) -> crate::proc::NameKey { match self.ty { FunctionType::Function(handle) => { crate::proc::NameKey::ExternalTextureFunctionArgument( handle, arg, external_texture_key, ) } // This is a const function, which _sometimes_ gets called, // so this lint is _sometimes_ triggered, depending on feature set. #[expect(clippy::allow_attributes)] #[allow(clippy::panic)] FunctionType::EntryPoint(_) => { panic!("External textures cannot be used as arguments to entry points") } } } /// Returns true if the given expression points to a fixed-function pipeline input. pub fn is_fixed_function_input( &self, mut expression: crate::Handle, module: &crate::Module, ) -> Option { let ep_function = match self.ty { FunctionType::Function(_) => return None, FunctionType::EntryPoint(ep_index) => &module.entry_points[ep_index as usize].function, }; let mut built_in = None; loop { match self.expressions[expression] { crate::Expression::FunctionArgument(arg_index) => { return match ep_function.arguments[arg_index as usize].binding { Some(crate::Binding::BuiltIn(bi)) => Some(bi), _ => built_in, }; } crate::Expression::AccessIndex { base, index } => { match *self.resolve_type(base, &module.types) { crate::TypeInner::Struct { ref members, .. } => { if let Some(crate::Binding::BuiltIn(bi)) = members[index as usize].binding { built_in = Some(bi); } } _ => return None, } expression = base; } _ => return None, } } } } impl crate::Expression { /// Returns the ref count, upon reaching which this expression /// should be considered for baking. /// /// Note: we have to cache any expressions that depend on the control flow, /// or otherwise they may be moved into a non-uniform control flow, accidentally. /// See the [module-level documentation][emit] for details. /// /// [emit]: index.html#expression-evaluation-time pub const fn bake_ref_count(&self) -> usize { match *self { // accesses are never cached, only loads are crate::Expression::Access { .. } | crate::Expression::AccessIndex { .. } => usize::MAX, // sampling may use the control flow, and image ops look better by themselves crate::Expression::ImageSample { .. } | crate::Expression::ImageLoad { .. } => 1, // derivatives use the control flow crate::Expression::Derivative { .. } => 1, // TODO: We need a better fix for named `Load` expressions // More info - https://github.com/gfx-rs/naga/pull/914 // And https://github.com/gfx-rs/naga/issues/910 crate::Expression::Load { .. } => 1, // cache expressions that are referenced multiple times _ => 2, } } } /// Helper function that returns the string corresponding to the [`BinaryOperator`](crate::BinaryOperator) pub const fn binary_operation_str(op: crate::BinaryOperator) -> &'static str { use crate::BinaryOperator as Bo; match op { Bo::Add => "+", Bo::Subtract => "-", Bo::Multiply => "*", Bo::Divide => "/", Bo::Modulo => "%", Bo::Equal => "==", Bo::NotEqual => "!=", Bo::Less => "<", Bo::LessEqual => "<=", Bo::Greater => ">", Bo::GreaterEqual => ">=", Bo::And => "&", Bo::ExclusiveOr => "^", Bo::InclusiveOr => "|", Bo::LogicalAnd => "&&", Bo::LogicalOr => "||", Bo::ShiftLeft => "<<", Bo::ShiftRight => ">>", } } impl crate::TypeInner { /// Returns true if a variable of this type is a handle. pub const fn is_handle(&self) -> bool { match *self { Self::Image { .. } | Self::Sampler { .. } | Self::AccelerationStructure { .. } => true, _ => false, } } } impl crate::Statement { /// Returns true if the statement directly terminates the current block. /// /// Used to decide whether case blocks require a explicit `break`. pub const fn is_terminator(&self) -> bool { match *self { crate::Statement::Break | crate::Statement::Continue | crate::Statement::Return { .. } | crate::Statement::Kill => true, _ => false, } } } bitflags::bitflags! { /// Ray flags, for a [`RayDesc`]'s `flags` field. /// /// Note that these exactly correspond to the SPIR-V "Ray Flags" mask, and /// the SPIR-V backend passes them directly through to the /// [`OpRayQueryInitializeKHR`][op] instruction. (We have to choose something, so /// we might as well make one back end's life easier.) /// /// [`RayDesc`]: crate::Module::generate_ray_desc_type /// [op]: https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpRayQueryInitializeKHR #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct RayFlag: u32 { const OPAQUE = 0x01; const NO_OPAQUE = 0x02; const TERMINATE_ON_FIRST_HIT = 0x04; const SKIP_CLOSEST_HIT_SHADER = 0x08; const CULL_BACK_FACING = 0x10; const CULL_FRONT_FACING = 0x20; const CULL_OPAQUE = 0x40; const CULL_NO_OPAQUE = 0x80; const SKIP_TRIANGLES = 0x100; const SKIP_AABBS = 0x200; } } /// The intersection test to use for ray queries. #[repr(u32)] pub enum RayIntersectionType { Triangle = 1, BoundingBox = 4, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct TaskDispatchLimits { pub max_mesh_workgroups_per_dim: u32, pub max_mesh_workgroups_total: u32, } ================================================ FILE: naga/src/back/msl/keywords.rs ================================================ use crate::proc::{concrete_int_scalars, vector_size_str, vector_sizes, KeywordSet}; use crate::racy_lock::RacyLock; use alloc::{format, string::String, vec::Vec}; // MSLS - Metal Shading Language Specification: // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf // // C++ - Standard for Programming Language C++ (N4431) // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4431.pdf const RESERVED: &[&str] = &[ // Undocumented "assert", // found in https://github.com/gfx-rs/wgpu/issues/5347 // Standard for Programming Language C++ (N4431): 2.5 Alternative tokens "and", "bitor", "or", "xor", "compl", "bitand", "and_eq", "or_eq", "xor_eq", "not", "not_eq", // Standard for Programming Language C++ (N4431): 2.11 Keywords "alignas", "alignof", "asm", "auto", "bool", "break", "case", "catch", "char", "char16_t", "char32_t", "class", "const", "constexpr", "const_cast", "continue", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable", "namespace", "new", "noexcept", "nullptr", "operator", "private", "protected", "public", "register", "reinterpret_cast", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "template", "this", "thread_local", "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", // Metal Shading Language Specification: 1.4.4 Restrictions "main", // Metal Shading Language Specification: 2.1 Scalar Data Types "int8_t", "uchar", "uint8_t", "int16_t", "ushort", "uint16_t", "int32_t", "uint", "uint32_t", "int64_t", "uint64_t", "half", "bfloat", "size_t", "ptrdiff_t", // Metal Shading Language Specification: 2.2 Vector Data Types "bool2", "bool3", "bool4", "char2", "char3", "char4", "short2", "short3", "short4", "int2", "int3", "int4", "long2", "long3", "long4", "uchar2", "uchar3", "uchar4", "ushort2", "ushort3", "ushort4", "uint2", "uint3", "uint4", "ulong2", "ulong3", "ulong4", "half2", "half3", "half4", "bfloat2", "bfloat3", "bfloat4", "float2", "float3", "float4", "vec", // Metal Shading Language Specification: 2.2.3 Packed Vector Types "packed_bool2", "packed_bool3", "packed_bool4", "packed_char2", "packed_char3", "packed_char4", "packed_short2", "packed_short3", "packed_short4", "packed_int2", "packed_int3", "packed_int4", "packed_uchar2", "packed_uchar3", "packed_uchar4", "packed_ushort2", "packed_ushort3", "packed_ushort4", "packed_uint2", "packed_uint3", "packed_uint4", "packed_half2", "packed_half3", "packed_half4", "packed_bfloat2", "packed_bfloat3", "packed_bfloat4", "packed_float2", "packed_float3", "packed_float4", "packed_long2", "packed_long3", "packed_long4", "packed_vec", // Metal Shading Language Specification: 2.3 Matrix Data Types "half2x2", "half2x3", "half2x4", "half3x2", "half3x3", "half3x4", "half4x2", "half4x3", "half4x4", "float2x2", "float2x3", "float2x4", "float3x2", "float3x3", "float3x4", "float4x2", "float4x3", "float4x4", "matrix", // Metal Shading Language Specification: 2.6 Atomic Data Types "atomic", "atomic_int", "atomic_uint", "atomic_bool", "atomic_ulong", "atomic_float", // Metal Shading Language Specification: 2.20 Type Conversions and Re-interpreting Data "as_type", // Metal Shading Language Specification: 4 Address Spaces "device", "constant", "thread", "threadgroup", "threadgroup_imageblock", "ray_data", "object_data", // Metal Shading Language Specification: 5.1 Functions "vertex", "fragment", "kernel", // Metal Shading Language Specification: 6.1 Namespace and Header Files "metal", // C99 / C++ extension: "restrict", // Metal reserved types in : "llong", "ullong", "quad", "complex", "imaginary", // Constants in : "CHAR_BIT", "SCHAR_MAX", "SCHAR_MIN", "UCHAR_MAX", "CHAR_MAX", "CHAR_MIN", "USHRT_MAX", "SHRT_MAX", "SHRT_MIN", "UINT_MAX", "INT_MAX", "INT_MIN", "ULONG_MAX", "LONG_MAX", "LONG_MIN", "ULLONG_MAX", "LLONG_MAX", "LLONG_MIN", "FLT_DIG", "FLT_MANT_DIG", "FLT_MAX_10_EXP", "FLT_MAX_EXP", "FLT_MIN_10_EXP", "FLT_MIN_EXP", "FLT_RADIX", "FLT_MAX", "FLT_MIN", "FLT_EPSILON", "FLT_DECIMAL_DIG", "FP_ILOGB0", "FP_ILOGB0", "FP_ILOGBNAN", "FP_ILOGBNAN", "MAXFLOAT", "HUGE_VALF", "INFINITY", "NAN", "M_E_F", "M_LOG2E_F", "M_LOG10E_F", "M_LN2_F", "M_LN10_F", "M_PI_F", "M_PI_2_F", "M_PI_4_F", "M_1_PI_F", "M_2_PI_F", "M_2_SQRTPI_F", "M_SQRT2_F", "M_SQRT1_2_F", "HALF_DIG", "HALF_MANT_DIG", "HALF_MAX_10_EXP", "HALF_MAX_EXP", "HALF_MIN_10_EXP", "HALF_MIN_EXP", "HALF_RADIX", "HALF_MAX", "HALF_MIN", "HALF_EPSILON", "HALF_DECIMAL_DIG", "MAXHALF", "HUGE_VALH", "M_E_H", "M_LOG2E_H", "M_LOG10E_H", "M_LN2_H", "M_LN10_H", "M_PI_H", "M_PI_2_H", "M_PI_4_H", "M_1_PI_H", "M_2_PI_H", "M_2_SQRTPI_H", "M_SQRT2_H", "M_SQRT1_2_H", "DBL_DIG", "DBL_MANT_DIG", "DBL_MAX_10_EXP", "DBL_MAX_EXP", "DBL_MIN_10_EXP", "DBL_MIN_EXP", "DBL_RADIX", "DBL_MAX", "DBL_MIN", "DBL_EPSILON", "DBL_DECIMAL_DIG", "MAXDOUBLE", "HUGE_VAL", "M_E", "M_LOG2E", "M_LOG10E", "M_LN2", "M_LN10", "M_PI", "M_PI_2", "M_PI_4", "M_1_PI", "M_2_PI", "M_2_SQRTPI", "M_SQRT2", "M_SQRT1_2", // Naga utilities "DefaultConstructible", // Naga builtin names "__local_invocation_id", super::writer::FREXP_FUNCTION, super::writer::MODF_FUNCTION, super::writer::ABS_FUNCTION, super::writer::DIV_FUNCTION, // DOT_FUNCTION_PREFIX variants are added dynamically below super::writer::MOD_FUNCTION, super::writer::NEG_FUNCTION, super::writer::F2I32_FUNCTION, super::writer::F2U32_FUNCTION, super::writer::F2I64_FUNCTION, super::writer::F2U64_FUNCTION, super::writer::IMAGE_LOAD_EXTERNAL_FUNCTION, super::writer::IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION, super::writer::IMAGE_SIZE_EXTERNAL_FUNCTION, super::writer::ARGUMENT_BUFFER_WRAPPER_STRUCT, super::writer::EXTERNAL_TEXTURE_WRAPPER_STRUCT, super::writer::COOPERATIVE_LOAD_FUNCTION, super::writer::COOPERATIVE_MULTIPLY_ADD_FUNCTION, ]; // The set of concrete integer dot product function variants. // This must match the set of names that could be produced by // `Writer::get_dot_wrapper_function_helper_name`. static DOT_FUNCTION_NAMES: RacyLock> = RacyLock::new(|| { let mut names = Vec::new(); for scalar in concrete_int_scalars().map(crate::Scalar::to_msl_name) { for size_suffix in vector_sizes().map(vector_size_str) { let fun_name = format!( "{}_{}{}", super::writer::DOT_FUNCTION_PREFIX, scalar, size_suffix ); names.push(fun_name); } } names }); /// The above set of reserved keywords, turned into a cached HashSet. This saves /// significant time during [`Namer::reset`](crate::proc::Namer::reset). /// /// See for benchmarks. pub static RESERVED_SET: RacyLock = RacyLock::new(|| { let mut set = KeywordSet::from_iter(RESERVED); set.extend(DOT_FUNCTION_NAMES.iter().map(String::as_str)); set }); ================================================ FILE: naga/src/back/msl/mod.rs ================================================ /*! Backend for [MSL][msl] (Metal Shading Language). This backend does not support the [`SHADER_INT64_ATOMIC_ALL_OPS`][all-atom] capability. ## Binding model Metal's bindings are flat per resource. Since there isn't an obvious mapping from SPIR-V's descriptor sets, we require a separate mapping provided in the options. This mapping may have one or more resource end points for each descriptor set + index pair. ## Entry points Even though MSL and our IR appear to be similar in that the entry points in both can accept arguments and return values, the restrictions are different. MSL allows the varyings to be either in separate arguments, or inside a single `[[stage_in]]` struct. We gather input varyings and form this artificial structure. We also add all the (non-Private) globals into the arguments. At the beginning of the entry point, we assign the local constants and re-compose the arguments as they are declared on IR side, so that the rest of the logic can pretend that MSL doesn't have all the restrictions it has. For the result type, if it's a structure, we re-compose it with a temporary value holding the result. [msl]: https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf [all-atom]: crate::valid::Capabilities::SHADER_INT64_ATOMIC_ALL_OPS ## Pointer-typed bounds-checked expressions and OOB locals MSL (unlike HLSL and GLSL) has native support for pointer-typed function arguments. When the [`BoundsCheckPolicy`] is `ReadZeroSkipWrite` and an out-of-bounds index expression is used for such an argument, our strategy is to pass a pointer to a dummy variable. These dummy variables are called "OOB locals". We emit at most one OOB local per function for each type, since all expressions producing a result of that type can share the same OOB local. (Note that the OOB local mechanism is not actually implementing "skip write", nor even "read zero" in some cases of read-after-write, but doing so would require additional effort and the difference is unlikely to matter.) [`BoundsCheckPolicy`]: crate::proc::BoundsCheckPolicy ## External textures Support for [`crate::ImageClass::External`] textures is implemented by lowering each external texture global variable to 3 `texture2d`s, and a constant buffer of type `NagaExternalTextureParams`. This provides up to 3 planes of texture data (for example single planar RGBA, or separate Y, Cb, and Cr planes), and the parameters buffer containing information describing how to handle these correctly. The bind target to use for each of these globals is specified via the [`BindTarget::external_texture`] field of the relevant entries in [`EntryPointResources::resources`]. External textures are supported by WGSL's `textureDimensions()`, `textureLoad()`, and `textureSampleBaseClampToEdge()` built-in functions. These are implemented using helper functions. See the following functions for how these are generated: * `Writer::write_wrapped_image_query` * `Writer::write_wrapped_image_load` * `Writer::write_wrapped_image_sample` The lowered global variables for each external texture global are passed to the entry point as separate arguments (see "Entry points" above). However, they are then wrapped in a struct to allow them to be conveniently passed to user defined and helper functions. See `writer::EXTERNAL_TEXTURE_WRAPPER_STRUCT`. */ use alloc::{ format, string::{String, ToString}, vec::Vec, }; use core::fmt::{Error as FmtError, Write}; use crate::{arena::Handle, ir, proc::index, valid::ModuleInfo}; mod keywords; pub mod sampler; mod writer; pub use writer::Writer; pub type Slot = u8; pub type InlineSamplerIndex = u8; #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub enum BindSamplerTarget { Resource(Slot), Inline(InlineSamplerIndex), } /// Binding information for a Naga [`External`] image global variable. /// /// See the module documentation's section on external textures for details. /// /// [`External`]: crate::ir::ImageClass::External #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct BindExternalTextureTarget { pub planes: [Slot; 3], pub params: Slot, } #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))] pub struct BindTarget { pub buffer: Option, pub texture: Option, pub sampler: Option, pub external_texture: Option, pub mutable: bool, } #[cfg(feature = "deserialize")] #[derive(serde::Deserialize)] struct BindingMapSerialization { resource_binding: crate::ResourceBinding, bind_target: BindTarget, } #[cfg(feature = "deserialize")] fn deserialize_binding_map<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, { use serde::Deserialize; let vec = Vec::::deserialize(deserializer)?; let mut map = BindingMap::default(); for item in vec { map.insert(item.resource_binding, item.bind_target); } Ok(map) } // Using `BTreeMap` instead of `HashMap` so that we can hash itself. pub type BindingMap = alloc::collections::BTreeMap; #[derive(Clone, Debug, Default, Hash, Eq, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))] pub struct EntryPointResources { #[cfg_attr( feature = "deserialize", serde(deserialize_with = "deserialize_binding_map") )] pub resources: BindingMap, pub immediates_buffer: Option, /// The slot of a buffer that contains an array of `u32`, /// one for the size of each bound buffer that contains a runtime array, /// in order of [`crate::GlobalVariable`] declarations. pub sizes_buffer: Option, } pub type EntryPointResourceMap = alloc::collections::BTreeMap; enum ResolvedBinding { BuiltIn(crate::BuiltIn), Attribute(u32), Color { location: u32, blend_src: Option, }, User { prefix: &'static str, index: u32, interpolation: Option, }, Resource(BindTarget), } #[derive(Copy, Clone)] enum ResolvedInterpolation { CenterPerspective, CenterNoPerspective, CentroidPerspective, CentroidNoPerspective, SamplePerspective, SampleNoPerspective, Flat, } // Note: some of these should be removed in favor of proper IR validation. #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] Format(#[from] FmtError), #[error("bind target {0:?} is empty")] UnimplementedBindTarget(BindTarget), #[error("composing of {0:?} is not implemented yet")] UnsupportedCompose(Handle), #[error("operation {0:?} is not implemented yet")] UnsupportedBinaryOp(crate::BinaryOperator), #[error("standard function '{0}' is not implemented yet")] UnsupportedCall(String), #[error("feature '{0}' is not implemented yet")] FeatureNotImplemented(String), #[error("internal naga error: module should not have validated: {0}")] GenericValidation(String), #[error("BuiltIn {0:?} is not supported")] UnsupportedBuiltIn(crate::BuiltIn), #[error("capability {0:?} is not supported")] CapabilityNotSupported(crate::valid::Capabilities), #[error("attribute '{0}' is not supported for target MSL version")] UnsupportedAttribute(String), #[error("function '{0}' is not supported for target MSL version")] UnsupportedFunction(String), #[error("can not use writeable storage buffers in fragment stage prior to MSL 1.2")] UnsupportedWriteableStorageBuffer, #[error("can not use writeable storage textures in {0:?} stage prior to MSL 1.2")] UnsupportedWriteableStorageTexture(ir::ShaderStage), #[error("can not use read-write storage textures prior to MSL 1.2")] UnsupportedRWStorageTexture, #[error("array of '{0}' is not supported for target MSL version")] UnsupportedArrayOf(String), #[error("array of type '{0:?}' is not supported")] UnsupportedArrayOfType(Handle), #[error("ray tracing is not supported prior to MSL 2.4")] UnsupportedRayTracing, #[error("cooperative matrix is not supported prior to MSL 2.3")] UnsupportedCooperativeMatrix, #[error("overrides should not be present at this stage")] Override, #[error("bitcasting to {0:?} is not supported")] UnsupportedBitCast(crate::TypeInner), #[error(transparent)] ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError), #[error("entry point with stage {0:?} and name '{1}' not found")] EntryPointNotFound(ir::ShaderStage, String), } #[derive(Clone, Debug, PartialEq, thiserror::Error)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub enum EntryPointError { #[error("global '{0}' doesn't have a binding")] MissingBinding(String), #[error("mapping of {0:?} is missing")] MissingBindTarget(crate::ResourceBinding), #[error("mapping for immediates is missing")] MissingImmediateData, #[error("mapping for sizes buffer is missing")] MissingSizesBuffer, } /// Points in the MSL code where we might emit a pipeline input or output. /// /// Note that, even though vertex shaders' outputs are always fragment /// shaders' inputs, we still need to distinguish `VertexOutput` and /// `FragmentInput`, since there are certain differences in the way /// [`ResolvedBinding`s] are represented on either side. /// /// [`ResolvedBinding`s]: ResolvedBinding #[derive(Clone, Copy, Debug)] enum LocationMode { /// Input to the vertex shader. VertexInput, /// Output from the vertex shader. VertexOutput, /// Input to the fragment shader. FragmentInput, /// Output from the fragment shader. FragmentOutput, /// Compute shader input or output. Uniform, } #[derive(Clone, Debug, Hash, PartialEq, Eq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[cfg_attr(feature = "deserialize", serde(default))] pub struct Options { /// (Major, Minor) target version of the Metal Shading Language. pub lang_version: (u8, u8), /// Map of entry-point resources, indexed by entry point function name, to slots. pub per_entry_point_map: EntryPointResourceMap, /// Samplers to be inlined into the code. pub inline_samplers: Vec, /// Make it possible to link different stages via SPIRV-Cross. pub spirv_cross_compatibility: bool, /// Don't panic on missing bindings, instead generate invalid MSL. pub fake_missing_bindings: bool, /// Bounds checking policies. pub bounds_check_policies: index::BoundsCheckPolicies, /// Should workgroup variables be zero initialized (by polyfilling)? pub zero_initialize_workgroup_memory: bool, /// If set, loops will have code injected into them, forcing the compiler /// to think the number of iterations is bounded. pub force_loop_bounding: bool, } impl Default for Options { fn default() -> Self { Options { lang_version: (1, 0), per_entry_point_map: EntryPointResourceMap::default(), inline_samplers: Vec::new(), spirv_cross_compatibility: false, fake_missing_bindings: true, bounds_check_policies: index::BoundsCheckPolicies::default(), zero_initialize_workgroup_memory: true, force_loop_bounding: true, } } } /// Corresponds to [WebGPU `GPUVertexFormat`]( /// https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat). #[repr(u32)] #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub enum VertexFormat { /// One unsigned byte (u8). `u32` in shaders. Uint8 = 0, /// Two unsigned bytes (u8). `vec2` in shaders. Uint8x2 = 1, /// Four unsigned bytes (u8). `vec4` in shaders. Uint8x4 = 2, /// One signed byte (i8). `i32` in shaders. Sint8 = 3, /// Two signed bytes (i8). `vec2` in shaders. Sint8x2 = 4, /// Four signed bytes (i8). `vec4` in shaders. Sint8x4 = 5, /// One unsigned byte (u8). [0, 255] converted to float [0, 1] `f32` in shaders. Unorm8 = 6, /// Two unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec2` in shaders. Unorm8x2 = 7, /// Four unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec4` in shaders. Unorm8x4 = 8, /// One signed byte (i8). [-127, 127] converted to float [-1, 1] `f32` in shaders. Snorm8 = 9, /// Two signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec2` in shaders. Snorm8x2 = 10, /// Four signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec4` in shaders. Snorm8x4 = 11, /// One unsigned short (u16). `u32` in shaders. Uint16 = 12, /// Two unsigned shorts (u16). `vec2` in shaders. Uint16x2 = 13, /// Four unsigned shorts (u16). `vec4` in shaders. Uint16x4 = 14, /// One signed short (u16). `i32` in shaders. Sint16 = 15, /// Two signed shorts (i16). `vec2` in shaders. Sint16x2 = 16, /// Four signed shorts (i16). `vec4` in shaders. Sint16x4 = 17, /// One unsigned short (u16). [0, 65535] converted to float [0, 1] `f32` in shaders. Unorm16 = 18, /// Two unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec2` in shaders. Unorm16x2 = 19, /// Four unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec4` in shaders. Unorm16x4 = 20, /// One signed short (i16). [-32767, 32767] converted to float [-1, 1] `f32` in shaders. Snorm16 = 21, /// Two signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec2` in shaders. Snorm16x2 = 22, /// Four signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec4` in shaders. Snorm16x4 = 23, /// One half-precision float (no Rust equiv). `f32` in shaders. Float16 = 24, /// Two half-precision floats (no Rust equiv). `vec2` in shaders. Float16x2 = 25, /// Four half-precision floats (no Rust equiv). `vec4` in shaders. Float16x4 = 26, /// One single-precision float (f32). `f32` in shaders. Float32 = 27, /// Two single-precision floats (f32). `vec2` in shaders. Float32x2 = 28, /// Three single-precision floats (f32). `vec3` in shaders. Float32x3 = 29, /// Four single-precision floats (f32). `vec4` in shaders. Float32x4 = 30, /// One unsigned int (u32). `u32` in shaders. Uint32 = 31, /// Two unsigned ints (u32). `vec2` in shaders. Uint32x2 = 32, /// Three unsigned ints (u32). `vec3` in shaders. Uint32x3 = 33, /// Four unsigned ints (u32). `vec4` in shaders. Uint32x4 = 34, /// One signed int (i32). `i32` in shaders. Sint32 = 35, /// Two signed ints (i32). `vec2` in shaders. Sint32x2 = 36, /// Three signed ints (i32). `vec3` in shaders. Sint32x3 = 37, /// Four signed ints (i32). `vec4` in shaders. Sint32x4 = 38, /// Three unsigned 10-bit integers and one 2-bit integer, packed into a 32-bit integer (u32). [0, 1024] converted to float [0, 1] `vec4` in shaders. #[cfg_attr( any(feature = "serialize", feature = "deserialize"), serde(rename = "unorm10-10-10-2") )] Unorm10_10_10_2 = 43, /// Four unsigned 8-bit integers, packed into a 32-bit integer (u32). [0, 255] converted to float [0, 1] `vec4` in shaders. #[cfg_attr( any(feature = "serialize", feature = "deserialize"), serde(rename = "unorm8x4-bgra") )] Unorm8x4Bgra = 44, } /// Defines how to advance the data in vertex buffers. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub enum VertexBufferStepMode { Constant, #[default] ByVertex, ByInstance, } /// A mapping of vertex buffers and their attributes to shader /// locations. #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct AttributeMapping { /// Shader location associated with this attribute pub shader_location: u32, /// Offset in bytes from start of vertex buffer structure pub offset: u32, /// Format code to help us unpack the attribute into the type /// used by the shader. Codes correspond to a 0-based index of /// . /// The conversion process is described by /// . pub format: VertexFormat, } /// A description of a vertex buffer with all the information we /// need to address the attributes within it. #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct VertexBufferMapping { /// Shader location associated with this buffer pub id: u32, /// Size of the structure in bytes pub stride: u32, /// Vertex buffer step mode pub step_mode: VertexBufferStepMode, /// Vec of the attributes within the structure pub attributes: Vec, } /// A subset of options that are meant to be changed per pipeline. #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[cfg_attr(feature = "deserialize", serde(default))] pub struct PipelineOptions { /// The entry point to write. /// /// Entry points are identified by a shader stage specification, /// and a name. /// /// If `None`, all entry points will be written. If `Some` and the entry /// point is not found, an error will be thrown while writing. pub entry_point: Option<(ir::ShaderStage, String)>, /// Allow `BuiltIn::PointSize` and inject it if doesn't exist. /// /// Metal doesn't like this for non-point primitive topologies and requires it for /// point primitive topologies. /// /// Enable this for vertex shaders with point primitive topologies. pub allow_and_force_point_size: bool, /// If set, when generating the Metal vertex shader, transform it /// to receive the vertex buffers, lengths, and vertex id as args, /// and bounds-check the vertex id and use the index into the /// vertex buffers to access attributes, rather than using Metal's /// [[stage-in]] assembled attribute data. This is true by default, /// but remains configurable for use by tests via deserialization /// of this struct. There is no user-facing way to set this value. pub vertex_pulling_transform: bool, /// vertex_buffer_mappings are used during shader translation to /// support vertex pulling. pub vertex_buffer_mappings: Vec, } impl Options { fn resolve_local_binding( &self, binding: &crate::Binding, mode: LocationMode, ) -> Result { match *binding { crate::Binding::BuiltIn(mut built_in) => { match built_in { crate::BuiltIn::Position { ref mut invariant } => { if *invariant && self.lang_version < (2, 1) { return Err(Error::UnsupportedAttribute("invariant".to_string())); } // The 'invariant' attribute may only appear on vertex // shader outputs, not fragment shader inputs. if !matches!(mode, LocationMode::VertexOutput) { *invariant = false; } } crate::BuiltIn::BaseInstance if self.lang_version < (1, 2) => { return Err(Error::UnsupportedAttribute("base_instance".to_string())); } crate::BuiltIn::InstanceIndex if self.lang_version < (1, 2) => { return Err(Error::UnsupportedAttribute("instance_id".to_string())); } // macOS: Since Metal 2.2 // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/wgpu/issues/4414) crate::BuiltIn::PrimitiveIndex if self.lang_version < (2, 3) => { return Err(Error::UnsupportedAttribute("primitive_id".to_string())); } // macOS: since Metal 2.3 // iOS: Since Metal 2.2 // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf#page=114 crate::BuiltIn::ViewIndex if self.lang_version < (2, 2) => { return Err(Error::UnsupportedAttribute("amplification_id".to_string())); } // macOS: Since Metal 2.2 // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/wgpu/issues/4414) crate::BuiltIn::Barycentric { .. } if self.lang_version < (2, 3) => { return Err(Error::UnsupportedAttribute("barycentric_coord".to_string())); } _ => {} } Ok(ResolvedBinding::BuiltIn(built_in)) } crate::Binding::Location { location, interpolation, sampling, blend_src, per_primitive: _, } => match mode { LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)), LocationMode::FragmentOutput => { if blend_src.is_some() && self.lang_version < (1, 2) { return Err(Error::UnsupportedAttribute("blend_src".to_string())); } Ok(ResolvedBinding::Color { location, blend_src, }) } LocationMode::VertexOutput | LocationMode::FragmentInput => { Ok(ResolvedBinding::User { prefix: if self.spirv_cross_compatibility { "locn" } else { "loc" }, index: location, interpolation: { // unwrap: The verifier ensures that vertex shader outputs and fragment // shader inputs always have fully specified interpolation, and that // sampling is `None` only for Flat interpolation. let interpolation = interpolation.unwrap(); let sampling = sampling.unwrap_or(crate::Sampling::Center); Some(ResolvedInterpolation::from_binding(interpolation, sampling)) }, }) } LocationMode::Uniform => Err(Error::GenericValidation(format!( "Unexpected Binding::Location({location}) for the Uniform mode" ))), }, } } fn get_entry_point_resources(&self, ep: &crate::EntryPoint) -> Option<&EntryPointResources> { self.per_entry_point_map.get(&ep.name) } fn get_resource_binding_target( &self, ep: &crate::EntryPoint, res_binding: &crate::ResourceBinding, ) -> Option<&BindTarget> { self.get_entry_point_resources(ep) .and_then(|res| res.resources.get(res_binding)) } fn resolve_resource_binding( &self, ep: &crate::EntryPoint, res_binding: &crate::ResourceBinding, ) -> Result { let target = self.get_resource_binding_target(ep, res_binding); match target { Some(target) => Ok(ResolvedBinding::Resource(target.clone())), None if self.fake_missing_bindings => Ok(ResolvedBinding::User { prefix: "fake", index: 0, interpolation: None, }), None => Err(EntryPointError::MissingBindTarget(*res_binding)), } } fn resolve_immediates( &self, ep: &crate::EntryPoint, ) -> Result { let slot = self .get_entry_point_resources(ep) .and_then(|res| res.immediates_buffer); match slot { Some(slot) => Ok(ResolvedBinding::Resource(BindTarget { buffer: Some(slot), ..Default::default() })), None if self.fake_missing_bindings => Ok(ResolvedBinding::User { prefix: "fake", index: 0, interpolation: None, }), None => Err(EntryPointError::MissingImmediateData), } } fn resolve_sizes_buffer( &self, ep: &crate::EntryPoint, ) -> Result { let slot = self .get_entry_point_resources(ep) .and_then(|res| res.sizes_buffer); match slot { Some(slot) => Ok(ResolvedBinding::Resource(BindTarget { buffer: Some(slot), ..Default::default() })), None if self.fake_missing_bindings => Ok(ResolvedBinding::User { prefix: "fake", index: 0, interpolation: None, }), None => Err(EntryPointError::MissingSizesBuffer), } } } impl ResolvedBinding { fn as_inline_sampler<'a>(&self, options: &'a Options) -> Option<&'a sampler::InlineSampler> { match *self { Self::Resource(BindTarget { sampler: Some(BindSamplerTarget::Inline(index)), .. }) => Some(&options.inline_samplers[index as usize]), _ => None, } } fn try_fmt(&self, out: &mut W) -> Result<(), Error> { write!(out, " [[")?; match *self { Self::BuiltIn(built_in) => { use crate::BuiltIn as Bi; let name = match built_in { Bi::Position { invariant: false } => "position", Bi::Position { invariant: true } => "position, invariant", Bi::ViewIndex => "amplification_id", // vertex Bi::BaseInstance => "base_instance", Bi::BaseVertex => "base_vertex", Bi::ClipDistances => "clip_distance", Bi::InstanceIndex => "instance_id", Bi::PointSize => "point_size", Bi::VertexIndex => "vertex_id", // fragment Bi::FragDepth => "depth(any)", Bi::PointCoord => "point_coord", Bi::FrontFacing => "front_facing", Bi::PrimitiveIndex => "primitive_id", Bi::Barycentric { perspective: true } => "barycentric_coord", Bi::Barycentric { perspective: false } => { "barycentric_coord, center_no_perspective" } Bi::SampleIndex => "sample_id", Bi::SampleMask => "sample_mask", // compute Bi::GlobalInvocationId => "thread_position_in_grid", Bi::LocalInvocationId => "thread_position_in_threadgroup", Bi::LocalInvocationIndex => "thread_index_in_threadgroup", Bi::WorkGroupId => "threadgroup_position_in_grid", Bi::WorkGroupSize => "dispatch_threads_per_threadgroup", Bi::NumWorkGroups => "threadgroups_per_grid", // subgroup Bi::NumSubgroups => "simdgroups_per_threadgroup", Bi::SubgroupId => "simdgroup_index_in_threadgroup", Bi::SubgroupSize => "threads_per_simdgroup", Bi::SubgroupInvocationId => "thread_index_in_simdgroup", Bi::CullDistance | Bi::DrawIndex => { return Err(Error::UnsupportedBuiltIn(built_in)) } Bi::CullPrimitive => "primitive_culled", // TODO: figure out how to make this written as a function call Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices => unimplemented!(), Bi::MeshTaskSize | Bi::VertexCount | Bi::PrimitiveCount | Bi::Vertices | Bi::Primitives | Bi::RayInvocationId | Bi::NumRayInvocations | Bi::InstanceCustomData | Bi::GeometryIndex | Bi::WorldRayOrigin | Bi::WorldRayDirection | Bi::ObjectRayOrigin | Bi::ObjectRayDirection | Bi::RayTmin | Bi::RayTCurrentMax | Bi::ObjectToWorld | Bi::WorldToObject | Bi::HitKind => unreachable!(), }; write!(out, "{name}")?; } Self::Attribute(index) => write!(out, "attribute({index})")?, Self::Color { location, blend_src, } => { if let Some(blend_src) = blend_src { write!(out, "color({location}) index({blend_src})")? } else { write!(out, "color({location})")? } } Self::User { prefix, index, interpolation, } => { write!(out, "user({prefix}{index})")?; if let Some(interpolation) = interpolation { write!(out, ", ")?; interpolation.try_fmt(out)?; } } Self::Resource(ref target) => { if let Some(id) = target.buffer { write!(out, "buffer({id})")?; } else if let Some(id) = target.texture { write!(out, "texture({id})")?; } else if let Some(BindSamplerTarget::Resource(id)) = target.sampler { write!(out, "sampler({id})")?; } else { return Err(Error::UnimplementedBindTarget(target.clone())); } } } write!(out, "]]")?; Ok(()) } } impl ResolvedInterpolation { const fn from_binding(interpolation: crate::Interpolation, sampling: crate::Sampling) -> Self { use crate::Interpolation as I; use crate::Sampling as S; match (interpolation, sampling) { (I::Perspective, S::Center) => Self::CenterPerspective, (I::Perspective, S::Centroid) => Self::CentroidPerspective, (I::Perspective, S::Sample) => Self::SamplePerspective, (I::Linear, S::Center) => Self::CenterNoPerspective, (I::Linear, S::Centroid) => Self::CentroidNoPerspective, (I::Linear, S::Sample) => Self::SampleNoPerspective, (I::Flat, _) => Self::Flat, _ => unreachable!(), } } fn try_fmt(self, out: &mut W) -> Result<(), Error> { let identifier = match self { Self::CenterPerspective => "center_perspective", Self::CenterNoPerspective => "center_no_perspective", Self::CentroidPerspective => "centroid_perspective", Self::CentroidNoPerspective => "centroid_no_perspective", Self::SamplePerspective => "sample_perspective", Self::SampleNoPerspective => "sample_no_perspective", Self::Flat => "flat", }; out.write_str(identifier)?; Ok(()) } } /// Information about a translated module that is required /// for the use of the result. pub struct TranslationInfo { /// Mapping of the entry point names. Each item in the array /// corresponds to an entry point index. /// ///Note: Some entry points may fail translation because of missing bindings. pub entry_point_names: Vec>, } pub fn write_string( module: &crate::Module, info: &ModuleInfo, options: &Options, pipeline_options: &PipelineOptions, ) -> Result<(String, TranslationInfo), Error> { let mut w = Writer::new(String::new()); let info = w.write(module, info, options, pipeline_options)?; Ok((w.finish(), info)) } pub fn supported_capabilities() -> crate::valid::Capabilities { use crate::valid::Capabilities as Caps; Caps::IMMEDIATES // No FLOAT64 | Caps::PRIMITIVE_INDEX | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY // No BUFFER_BINDING_ARRAY | Caps::STORAGE_TEXTURE_BINDING_ARRAY | Caps::STORAGE_BUFFER_BINDING_ARRAY | Caps::CLIP_DISTANCES // CLIP_DISTANCES isn't supported by metal backend? But is supported by MSL writer // No CULL_DISTANCE | Caps::STORAGE_TEXTURE_16BIT_NORM_FORMATS | Caps::MULTIVIEW // No EARLY_DEPTH_TEST | Caps::MULTISAMPLED_SHADING | Caps::RAY_QUERY | Caps::DUAL_SOURCE_BLENDING | Caps::CUBE_ARRAY_TEXTURES | Caps::SHADER_INT64 | Caps::SUBGROUP | Caps::SUBGROUP_BARRIER // No SUBGROUP_VERTEX_STAGE | Caps::SHADER_INT64_ATOMIC_MIN_MAX // No SHADER_INT64_ATOMIC_ALL_OPS | Caps::SHADER_FLOAT32_ATOMIC | Caps::TEXTURE_ATOMIC | Caps::TEXTURE_INT64_ATOMIC // No RAY_HIT_VERTEX_POSITION | Caps::SHADER_FLOAT16 | Caps::TEXTURE_EXTERNAL | Caps::SHADER_FLOAT16_IN_FLOAT32 | Caps::SHADER_BARYCENTRICS // No MESH_SHADER // No MESH_SHADER_POINT_TOPOLOGY | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING // No BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING | Caps::STORAGE_TEXTURE_BINDING_ARRAY_NON_UNIFORM_INDEXING | Caps::STORAGE_BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING | Caps::COOPERATIVE_MATRIX // No PER_VERTEX // No RAY_TRACING_PIPELINE // No DRAW_INDEX // No MEMORY_DECORATION_VOLATILE | Caps::MEMORY_DECORATION_COHERENT } #[test] fn test_error_size() { assert_eq!(size_of::(), 40); } ================================================ FILE: naga/src/back/msl/sampler.rs ================================================ use core::{num::NonZeroU32, ops::Range}; #[cfg(feature = "deserialize")] use serde::Deserialize; #[cfg(feature = "serialize")] use serde::Serialize; #[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] pub enum Coord { #[default] Normalized, Pixel, } impl Coord { pub const fn as_str(&self) -> &'static str { match *self { Self::Normalized => "normalized", Self::Pixel => "pixel", } } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] pub enum Address { Repeat, MirroredRepeat, #[default] ClampToEdge, ClampToZero, ClampToBorder, } impl Address { pub const fn as_str(&self) -> &'static str { match *self { Self::Repeat => "repeat", Self::MirroredRepeat => "mirrored_repeat", Self::ClampToEdge => "clamp_to_edge", Self::ClampToZero => "clamp_to_zero", Self::ClampToBorder => "clamp_to_border", } } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] pub enum BorderColor { #[default] TransparentBlack, OpaqueBlack, OpaqueWhite, } impl BorderColor { pub const fn as_str(&self) -> &'static str { match *self { Self::TransparentBlack => "transparent_black", Self::OpaqueBlack => "opaque_black", Self::OpaqueWhite => "opaque_white", } } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] pub enum Filter { #[default] Nearest, Linear, } impl Filter { pub const fn as_str(&self) -> &'static str { match *self { Self::Nearest => "nearest", Self::Linear => "linear", } } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] pub enum CompareFunc { #[default] Never, Less, LessEqual, Greater, GreaterEqual, Equal, NotEqual, Always, } impl CompareFunc { pub const fn as_str(&self) -> &'static str { match *self { Self::Never => "never", Self::Less => "less", Self::LessEqual => "less_equal", Self::Greater => "greater", Self::GreaterEqual => "greater_equal", Self::Equal => "equal", Self::NotEqual => "not_equal", Self::Always => "always", } } } #[derive(Clone, Debug, Default, PartialEq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] pub struct InlineSampler { pub coord: Coord, pub address: [Address; 3], pub border_color: BorderColor, pub mag_filter: Filter, pub min_filter: Filter, pub mip_filter: Option, pub lod_clamp: Option>, pub max_anisotropy: Option, pub compare_func: CompareFunc, } impl Eq for InlineSampler {} impl core::hash::Hash for InlineSampler { fn hash(&self, hasher: &mut H) { self.coord.hash(hasher); self.address.hash(hasher); self.border_color.hash(hasher); self.mag_filter.hash(hasher); self.min_filter.hash(hasher); self.mip_filter.hash(hasher); self.lod_clamp .as_ref() .map(|range| (range.start.to_bits(), range.end.to_bits())) .hash(hasher); self.max_anisotropy.hash(hasher); self.compare_func.hash(hasher); } } ================================================ FILE: naga/src/back/msl/writer.rs ================================================ use alloc::{ format, string::{String, ToString}, vec, vec::Vec, }; use core::{ cmp::Ordering, fmt::{Display, Error as FmtError, Formatter, Write}, iter, }; use num_traits::real::Real as _; use half::f16; use super::{sampler as sm, Error, LocationMode, Options, PipelineOptions, TranslationInfo}; use crate::{ arena::{Handle, HandleSet}, back::{self, get_entry_points, Baked}, common, proc::{ self, concrete_int_scalars, index::{self, BoundsCheck}, ExternalTextureNameKey, NameKey, TypeResolution, }, valid, FastHashMap, FastHashSet, }; #[cfg(test)] use core::ptr; /// Shorthand result used internally by the backend type BackendResult = Result<(), Error>; const NAMESPACE: &str = "metal"; // The name of the array member of the Metal struct types we generate to // represent Naga `Array` types. See the comments in `Writer::write_type_defs` // for details. const WRAPPED_ARRAY_FIELD: &str = "inner"; // This is a hack: we need to pass a pointer to an atomic, // but generally the backend isn't putting "&" in front of every pointer. // Some more general handling of pointers is needed to be implemented here. const ATOMIC_REFERENCE: &str = "&"; const RT_NAMESPACE: &str = "metal::raytracing"; const RAY_QUERY_TYPE: &str = "_RayQuery"; const RAY_QUERY_FIELD_INTERSECTOR: &str = "intersector"; const RAY_QUERY_FIELD_INTERSECTION: &str = "intersection"; const RAY_QUERY_MODERN_SUPPORT: bool = false; //TODO const RAY_QUERY_FIELD_READY: &str = "ready"; const RAY_QUERY_FUN_MAP_INTERSECTION: &str = "_map_intersection_type"; pub(crate) const ATOMIC_COMP_EXCH_FUNCTION: &str = "naga_atomic_compare_exchange_weak_explicit"; pub(crate) const MODF_FUNCTION: &str = "naga_modf"; pub(crate) const FREXP_FUNCTION: &str = "naga_frexp"; pub(crate) const ABS_FUNCTION: &str = "naga_abs"; pub(crate) const DIV_FUNCTION: &str = "naga_div"; pub(crate) const DOT_FUNCTION_PREFIX: &str = "naga_dot"; pub(crate) const MOD_FUNCTION: &str = "naga_mod"; pub(crate) const NEG_FUNCTION: &str = "naga_neg"; pub(crate) const F2I32_FUNCTION: &str = "naga_f2i32"; pub(crate) const F2U32_FUNCTION: &str = "naga_f2u32"; pub(crate) const F2I64_FUNCTION: &str = "naga_f2i64"; pub(crate) const F2U64_FUNCTION: &str = "naga_f2u64"; pub(crate) const IMAGE_LOAD_EXTERNAL_FUNCTION: &str = "nagaTextureLoadExternal"; pub(crate) const IMAGE_SIZE_EXTERNAL_FUNCTION: &str = "nagaTextureDimensionsExternal"; pub(crate) const IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION: &str = "nagaTextureSampleBaseClampToEdge"; /// For some reason, Metal does not let you have `metal::texture<..>*` as a buffer argument. /// However, if you put that texture inside a struct, everything is totally fine. This /// baffles me to no end. /// /// As such, we wrap all argument buffers in a struct that has a single generic `` field. /// This allows `NagaArgumentBufferWrapper>*` to work. The astute among /// you have noticed that this should be exactly the same to the compiler, and you're correct. pub(crate) const ARGUMENT_BUFFER_WRAPPER_STRUCT: &str = "NagaArgumentBufferWrapper"; /// Name of the struct that is declared to wrap the 3 textures and parameters /// buffer that [`crate::ImageClass::External`] variables are lowered to, /// allowing them to be conveniently passed to user-defined or wrapper /// functions. The struct is declared in [`Writer::write_type_defs`]. pub(crate) const EXTERNAL_TEXTURE_WRAPPER_STRUCT: &str = "NagaExternalTextureWrapper"; pub(crate) const COOPERATIVE_LOAD_FUNCTION: &str = "NagaCooperativeLoad"; pub(crate) const COOPERATIVE_MULTIPLY_ADD_FUNCTION: &str = "NagaCooperativeMultiplyAdd"; /// Write the Metal name for a Naga numeric type: scalar, vector, or matrix. /// /// The `sizes` slice determines whether this function writes a /// scalar, vector, or matrix type: /// /// - An empty slice produces a scalar type. /// - A one-element slice produces a vector type. /// - A two element slice `[ROWS COLUMNS]` produces a matrix of the given size. fn put_numeric_type( out: &mut impl Write, scalar: crate::Scalar, sizes: &[crate::VectorSize], ) -> Result<(), FmtError> { match (scalar, sizes) { (scalar, &[]) => { write!(out, "{}", scalar.to_msl_name()) } (scalar, &[rows]) => { write!( out, "{}::{}{}", NAMESPACE, scalar.to_msl_name(), common::vector_size_str(rows) ) } (scalar, &[rows, columns]) => { write!( out, "{}::{}{}x{}", NAMESPACE, scalar.to_msl_name(), common::vector_size_str(columns), common::vector_size_str(rows) ) } (_, _) => Ok(()), // not meaningful } } const fn scalar_is_int(scalar: crate::Scalar) -> bool { use crate::ScalarKind::*; match scalar.kind { Sint | Uint | AbstractInt | Bool => true, Float | AbstractFloat => false, } } /// Prefix for cached clamped level-of-detail values for `ImageLoad` expressions. const CLAMPED_LOD_LOAD_PREFIX: &str = "clamped_lod_e"; /// Prefix for reinterpreted expressions using `as_type(...)`. const REINTERPRET_PREFIX: &str = "reinterpreted_"; /// Wrapper for identifier names for clamped level-of-detail values /// /// Values of this type implement [`core::fmt::Display`], formatting as /// the name of the variable used to hold the cached clamped /// level-of-detail value for an `ImageLoad` expression. struct ClampedLod(Handle); impl Display for ClampedLod { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { self.0.write_prefixed(f, CLAMPED_LOD_LOAD_PREFIX) } } /// Wrapper for generating `struct _mslBufferSizes` member names for /// runtime-sized array lengths. /// /// On Metal, `wgpu_hal` passes the element counts for all runtime-sized arrays /// as an argument to the entry point. This argument's type in the MSL is /// `struct _mslBufferSizes`, a Naga-synthesized struct with a `uint` member for /// each global variable containing a runtime-sized array. /// /// If `global` is a [`Handle`] for a [`GlobalVariable`] that contains a /// runtime-sized array, then the value `ArraySize(global)` implements /// [`core::fmt::Display`], formatting as the name of the struct member carrying /// the number of elements in that runtime-sized array. /// /// [`GlobalVariable`]: crate::GlobalVariable struct ArraySizeMember(Handle); impl Display for ArraySizeMember { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { self.0.write_prefixed(f, "size") } } /// Wrapper for reinterpreted variables using `as_type(orig)`. /// /// Implements [`core::fmt::Display`], formatting as a name derived from /// `target_type` and the variable name of `orig`. #[derive(Clone, Copy)] struct Reinterpreted<'a> { target_type: &'a str, orig: Handle, } impl<'a> Reinterpreted<'a> { const fn new(target_type: &'a str, orig: Handle) -> Self { Self { target_type, orig } } } impl Display for Reinterpreted<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.write_str(REINTERPRET_PREFIX)?; f.write_str(self.target_type)?; self.orig.write_prefixed(f, "_e") } } struct TypeContext<'a> { handle: Handle, gctx: proc::GlobalCtx<'a>, names: &'a FastHashMap, access: crate::StorageAccess, first_time: bool, } impl TypeContext<'_> { fn scalar(&self) -> Option { let ty = &self.gctx.types[self.handle]; ty.inner.scalar() } fn vector_size(&self) -> Option { let ty = &self.gctx.types[self.handle]; match ty.inner { crate::TypeInner::Vector { size, .. } => Some(size), _ => None, } } } impl Display for TypeContext<'_> { fn fmt(&self, out: &mut Formatter<'_>) -> Result<(), FmtError> { let ty = &self.gctx.types[self.handle]; if ty.needs_alias() && !self.first_time { let name = &self.names[&NameKey::Type(self.handle)]; return write!(out, "{name}"); } match ty.inner { crate::TypeInner::Scalar(scalar) => put_numeric_type(out, scalar, &[]), crate::TypeInner::Atomic(scalar) => { write!(out, "{}::atomic_{}", NAMESPACE, scalar.to_msl_name()) } crate::TypeInner::Vector { size, scalar } => put_numeric_type(out, scalar, &[size]), crate::TypeInner::Matrix { columns, rows, scalar, } => put_numeric_type(out, scalar, &[rows, columns]), // Requires Metal-2.3 crate::TypeInner::CooperativeMatrix { columns, rows, scalar, role: _, } => { write!( out, "{NAMESPACE}::simdgroup_{}{}x{}", scalar.to_msl_name(), columns as u32, rows as u32, ) } crate::TypeInner::Pointer { base, space } => { let sub = Self { handle: base, first_time: false, ..*self }; let space_name = match space.to_msl_name() { Some(name) => name, None => return Ok(()), }; write!(out, "{space_name} {sub}&") } crate::TypeInner::ValuePointer { size, scalar, space, } => { match space.to_msl_name() { Some(name) => write!(out, "{name} ")?, None => return Ok(()), }; match size { Some(rows) => put_numeric_type(out, scalar, &[rows])?, None => put_numeric_type(out, scalar, &[])?, }; write!(out, "&") } crate::TypeInner::Array { base, .. } => { let sub = Self { handle: base, first_time: false, ..*self }; // Array lengths go at the end of the type definition, // so just print the element type here. write!(out, "{sub}") } crate::TypeInner::Struct { .. } => unreachable!(), crate::TypeInner::Image { dim, arrayed, class, } => { let dim_str = match dim { crate::ImageDimension::D1 => "1d", crate::ImageDimension::D2 => "2d", crate::ImageDimension::D3 => "3d", crate::ImageDimension::Cube => "cube", }; let (texture_str, msaa_str, scalar, access) = match class { crate::ImageClass::Sampled { kind, multi } => { let (msaa_str, access) = if multi { ("_ms", "read") } else { ("", "sample") }; let scalar = crate::Scalar { kind, width: 4 }; ("texture", msaa_str, scalar, access) } crate::ImageClass::Depth { multi } => { let (msaa_str, access) = if multi { ("_ms", "read") } else { ("", "sample") }; let scalar = crate::Scalar { kind: crate::ScalarKind::Float, width: 4, }; ("depth", msaa_str, scalar, access) } crate::ImageClass::Storage { format, .. } => { let access = if self .access .contains(crate::StorageAccess::LOAD | crate::StorageAccess::STORE) { "read_write" } else if self.access.contains(crate::StorageAccess::STORE) { "write" } else if self.access.contains(crate::StorageAccess::LOAD) { "read" } else { log::warn!( "Storage access for {:?} (name '{}'): {:?}", self.handle, ty.name.as_deref().unwrap_or_default(), self.access ); unreachable!("module is not valid"); }; ("texture", "", format.into(), access) } crate::ImageClass::External => { return write!(out, "{EXTERNAL_TEXTURE_WRAPPER_STRUCT}"); } }; let base_name = scalar.to_msl_name(); let array_str = if arrayed { "_array" } else { "" }; write!( out, "{NAMESPACE}::{texture_str}{dim_str}{msaa_str}{array_str}<{base_name}, {NAMESPACE}::access::{access}>", ) } crate::TypeInner::Sampler { comparison: _ } => { write!(out, "{NAMESPACE}::sampler") } crate::TypeInner::AccelerationStructure { vertex_return } => { if vertex_return { unimplemented!("metal does not support vertex ray hit return") } write!(out, "{RT_NAMESPACE}::instance_acceleration_structure") } crate::TypeInner::RayQuery { vertex_return } => { if vertex_return { unimplemented!("metal does not support vertex ray hit return") } write!(out, "{RAY_QUERY_TYPE}") } crate::TypeInner::BindingArray { base, .. } => { let base_tyname = Self { handle: base, first_time: false, ..*self }; write!( out, "constant {ARGUMENT_BUFFER_WRAPPER_STRUCT}<{base_tyname}>*" ) } } } } struct TypedGlobalVariable<'a> { module: &'a crate::Module, names: &'a FastHashMap, handle: Handle, usage: valid::GlobalUse, reference: bool, } impl TypedGlobalVariable<'_> { fn try_fmt(&self, out: &mut W) -> BackendResult { let var = &self.module.global_variables[self.handle]; let name = &self.names[&NameKey::GlobalVariable(self.handle)]; let storage_access = match var.space { crate::AddressSpace::Storage { access } => access, _ => match self.module.types[var.ty].inner { crate::TypeInner::Image { class: crate::ImageClass::Storage { access, .. }, .. } => access, crate::TypeInner::BindingArray { base, .. } => { match self.module.types[base].inner { crate::TypeInner::Image { class: crate::ImageClass::Storage { access, .. }, .. } => access, _ => crate::StorageAccess::default(), } } _ => crate::StorageAccess::default(), }, }; let ty_name = TypeContext { handle: var.ty, gctx: self.module.to_ctx(), names: self.names, access: storage_access, first_time: false, }; let (coherent, space, access, reference) = match var.space.to_msl_name() { Some(space) if self.reference => { let coherent = if var .memory_decorations .contains(crate::MemoryDecorations::COHERENT) { "coherent " } else { "" }; let access = if var.space.needs_access_qualifier() && !self.usage.intersects(valid::GlobalUse::WRITE) { "const" } else { "" }; (coherent, space, access, "&") } _ => ("", "", "", ""), }; Ok(write!( out, "{}{}{}{}{}{}{} {}", coherent, space, if space.is_empty() { "" } else { " " }, ty_name, if access.is_empty() { "" } else { " " }, access, reference, name, )?) } } #[derive(Eq, PartialEq, Hash)] enum WrappedFunction { UnaryOp { op: crate::UnaryOperator, ty: (Option, crate::Scalar), }, BinaryOp { op: crate::BinaryOperator, left_ty: (Option, crate::Scalar), right_ty: (Option, crate::Scalar), }, Math { fun: crate::MathFunction, arg_ty: (Option, crate::Scalar), }, Cast { src_scalar: crate::Scalar, vector_size: Option, dst_scalar: crate::Scalar, }, ImageLoad { class: crate::ImageClass, }, ImageSample { class: crate::ImageClass, clamp_to_edge: bool, }, ImageQuerySize { class: crate::ImageClass, }, CooperativeLoad { space_name: &'static str, columns: crate::CooperativeSize, rows: crate::CooperativeSize, scalar: crate::Scalar, }, CooperativeMultiplyAdd { space_name: &'static str, columns: crate::CooperativeSize, rows: crate::CooperativeSize, intermediate: crate::CooperativeSize, scalar: crate::Scalar, }, } pub struct Writer { out: W, names: FastHashMap, named_expressions: crate::NamedExpressions, /// Set of expressions that need to be baked to avoid unnecessary repetition in output need_bake_expressions: back::NeedBakeExpressions, namer: proc::Namer, wrapped_functions: FastHashSet, #[cfg(test)] put_expression_stack_pointers: FastHashSet<*const ()>, #[cfg(test)] put_block_stack_pointers: FastHashSet<*const ()>, /// Set of (struct type, struct field index) denoting which fields require /// padding inserted **before** them (i.e. between fields at index - 1 and index) struct_member_pads: FastHashSet<(Handle, u32)>, } impl crate::Scalar { pub(super) fn to_msl_name(self) -> &'static str { use crate::ScalarKind as Sk; match self { Self { kind: Sk::Float, width: 4, } => "float", Self { kind: Sk::Float, width: 2, } => "half", Self { kind: Sk::Sint, width: 4, } => "int", Self { kind: Sk::Uint, width: 4, } => "uint", Self { kind: Sk::Sint, width: 8, } => "long", Self { kind: Sk::Uint, width: 8, } => "ulong", Self { kind: Sk::Bool, width: _, } => "bool", Self { kind: Sk::AbstractInt | Sk::AbstractFloat, width: _, } => unreachable!("Found Abstract scalar kind"), _ => unreachable!("Unsupported scalar kind: {:?}", self), } } } const fn separate(need_separator: bool) -> &'static str { if need_separator { "," } else { "" } } fn should_pack_struct_member( members: &[crate::StructMember], span: u32, index: usize, module: &crate::Module, ) -> Option { let member = &members[index]; let ty_inner = &module.types[member.ty].inner; let last_offset = member.offset + ty_inner.size(module.to_ctx()); let next_offset = match members.get(index + 1) { Some(next) => next.offset, None => span, }; let is_tight = next_offset == last_offset; match *ty_inner { crate::TypeInner::Vector { size: crate::VectorSize::Tri, scalar: scalar @ crate::Scalar { width: 4 | 2, .. }, } if is_tight => Some(scalar), _ => None, } } fn needs_array_length(ty: Handle, arena: &crate::UniqueArena) -> bool { match arena[ty].inner { crate::TypeInner::Struct { ref members, .. } => { if let Some(member) = members.last() { if let crate::TypeInner::Array { size: crate::ArraySize::Dynamic, .. } = arena[member.ty].inner { return true; } } false } crate::TypeInner::Array { size: crate::ArraySize::Dynamic, .. } => true, _ => false, } } impl crate::AddressSpace { /// Returns true if global variables in this address space are /// passed in function arguments. These arguments need to be /// passed through any functions called from the entry point. const fn needs_pass_through(&self) -> bool { match *self { Self::Uniform | Self::Storage { .. } | Self::Private | Self::WorkGroup | Self::Immediate | Self::Handle | Self::TaskPayload => true, Self::Function => false, Self::RayPayload | Self::IncomingRayPayload => unreachable!(), } } /// Returns true if the address space may need a "const" qualifier. const fn needs_access_qualifier(&self) -> bool { match *self { //Note: we are ignoring the storage access here, and instead // rely on the actual use of a global by functions. This means we // may end up with "const" even if the binding is read-write, // and that should be OK. Self::Storage { .. } => true, Self::TaskPayload | Self::RayPayload | Self::IncomingRayPayload => unimplemented!(), // These should always be read-write. Self::Private | Self::WorkGroup => false, // These translate to `constant` address space, no need for qualifiers. Self::Uniform | Self::Immediate => false, // Not applicable. Self::Handle | Self::Function => false, } } const fn to_msl_name(self) -> Option<&'static str> { match self { Self::Handle => None, Self::Uniform | Self::Immediate => Some("constant"), Self::Storage { .. } => Some("device"), // note for `RayPayload`, this probably needs to be emulated as a // private variable, as metal has essentially an inout input // for where it is passed. Self::Private | Self::Function | Self::RayPayload => Some("thread"), Self::WorkGroup => Some("threadgroup"), Self::TaskPayload => Some("object_data"), Self::IncomingRayPayload => Some("ray_data"), } } } impl crate::Type { // Returns `true` if we need to emit an alias for this type. const fn needs_alias(&self) -> bool { use crate::TypeInner as Ti; match self.inner { // value types are concise enough, we only alias them if they are named Ti::Scalar(_) | Ti::Vector { .. } | Ti::Matrix { .. } | Ti::CooperativeMatrix { .. } | Ti::Atomic(_) | Ti::Pointer { .. } | Ti::ValuePointer { .. } => self.name.is_some(), // composite types are better to be aliased, regardless of the name Ti::Struct { .. } | Ti::Array { .. } => true, // handle types may be different, depending on the global var access, so we always inline them Ti::Image { .. } | Ti::Sampler { .. } | Ti::AccelerationStructure { .. } | Ti::RayQuery { .. } | Ti::BindingArray { .. } => false, } } } #[derive(Clone, Copy)] enum FunctionOrigin { Handle(Handle), EntryPoint(proc::EntryPointIndex), } trait NameKeyExt { fn local(origin: FunctionOrigin, local_handle: Handle) -> NameKey { match origin { FunctionOrigin::Handle(handle) => NameKey::FunctionLocal(handle, local_handle), FunctionOrigin::EntryPoint(idx) => NameKey::EntryPointLocal(idx, local_handle), } } /// Return the name key for a local variable used by ReadZeroSkipWrite bounds-check /// policy when it needs to produce a pointer-typed result for an OOB access. These /// are unique per accessed type, so the second argument is a type handle. See docs /// for [`crate::back::msl`]. fn oob_local_for_type(origin: FunctionOrigin, ty: Handle) -> NameKey { match origin { FunctionOrigin::Handle(handle) => NameKey::FunctionOobLocal(handle, ty), FunctionOrigin::EntryPoint(idx) => NameKey::EntryPointOobLocal(idx, ty), } } } impl NameKeyExt for NameKey {} /// A level of detail argument. /// /// When [`BoundsCheckPolicy::Restrict`] applies to an [`ImageLoad`] access, we /// save the clamped level of detail in a temporary variable whose name is based /// on the handle of the `ImageLoad` expression. But for other policies, we just /// use the expression directly. /// /// [`BoundsCheckPolicy::Restrict`]: index::BoundsCheckPolicy::Restrict /// [`ImageLoad`]: crate::Expression::ImageLoad #[derive(Clone, Copy)] enum LevelOfDetail { Direct(Handle), Restricted(Handle), } /// Values needed to select a particular texel for [`ImageLoad`] and [`ImageStore`]. /// /// When this is used in code paths unconcerned with the `Restrict` bounds check /// policy, the `LevelOfDetail` enum introduces an unneeded match, since `level` /// will always be either `None` or `Some(Direct(_))`. But this turns out not to /// be too awkward. If that changes, we can revisit. /// /// [`ImageLoad`]: crate::Expression::ImageLoad /// [`ImageStore`]: crate::Statement::ImageStore struct TexelAddress { coordinate: Handle, array_index: Option>, sample: Option>, level: Option, } struct ExpressionContext<'a> { function: &'a crate::Function, origin: FunctionOrigin, info: &'a valid::FunctionInfo, module: &'a crate::Module, mod_info: &'a valid::ModuleInfo, pipeline_options: &'a PipelineOptions, lang_version: (u8, u8), policies: index::BoundsCheckPolicies, /// The set of expressions used as indices in `ReadZeroSkipWrite`-policy /// accesses. These may need to be cached in temporary variables. See /// `index::find_checked_indexes` for details. guarded_indices: HandleSet, /// See [`Writer::gen_force_bounded_loop_statements`] for details. force_loop_bounding: bool, } impl<'a> ExpressionContext<'a> { fn resolve_type(&self, handle: Handle) -> &'a crate::TypeInner { self.info[handle].ty.inner_with(&self.module.types) } /// Return true if calls to `image`'s `read` and `write` methods should supply a level of detail. /// /// Only mipmapped images need to specify a level of detail. Since 1D /// textures cannot have mipmaps, MSL requires that the level argument to /// texture1d queries and accesses must be a constexpr 0. It's easiest /// just to omit the level entirely for 1D textures. fn image_needs_lod(&self, image: Handle) -> bool { let image_ty = self.resolve_type(image); if let crate::TypeInner::Image { dim, class, .. } = *image_ty { class.is_mipmapped() && dim != crate::ImageDimension::D1 } else { false } } fn choose_bounds_check_policy( &self, pointer: Handle, ) -> index::BoundsCheckPolicy { self.policies .choose_policy(pointer, &self.module.types, self.info) } /// See docs for [`proc::index::access_needs_check`]. fn access_needs_check( &self, base: Handle, index: index::GuardedIndex, ) -> Option { index::access_needs_check( base, index, self.module, &self.function.expressions, self.info, ) } /// See docs for [`proc::index::bounds_check_iter`]. fn bounds_check_iter( &self, chain: Handle, ) -> impl Iterator + '_ { index::bounds_check_iter(chain, self.module, self.function, self.info) } /// See docs for [`proc::index::oob_local_types`]. fn oob_local_types(&self) -> FastHashSet> { index::oob_local_types(self.module, self.function, self.info, self.policies) } fn get_packed_vec_kind(&self, expr_handle: Handle) -> Option { match self.function.expressions[expr_handle] { crate::Expression::AccessIndex { base, index } => { let ty = match *self.resolve_type(base) { crate::TypeInner::Pointer { base, .. } => &self.module.types[base].inner, ref ty => ty, }; match *ty { crate::TypeInner::Struct { ref members, span, .. } => should_pack_struct_member(members, span, index as usize, self.module), _ => None, } } _ => None, } } } struct StatementContext<'a> { expression: ExpressionContext<'a>, result_struct: Option<&'a str>, } impl Writer { /// Creates a new `Writer` instance. pub fn new(out: W) -> Self { Writer { out, names: FastHashMap::default(), named_expressions: Default::default(), need_bake_expressions: Default::default(), namer: proc::Namer::default(), wrapped_functions: FastHashSet::default(), #[cfg(test)] put_expression_stack_pointers: Default::default(), #[cfg(test)] put_block_stack_pointers: Default::default(), struct_member_pads: FastHashSet::default(), } } /// Finishes writing and returns the output. // See https://github.com/rust-lang/rust-clippy/issues/4979. pub fn finish(self) -> W { self.out } /// Generates statements to be inserted immediately before and at the very /// start of the body of each loop, to defeat MSL infinite loop reasoning. /// The 0th item of the returned tuple should be inserted immediately prior /// to the loop and the 1st item should be inserted at the very start of /// the loop body. /// /// # What is this trying to solve? /// /// In Metal Shading Language, an infinite loop has undefined behavior. /// (This rule is inherited from C++14.) This means that, if the MSL /// compiler determines that a given loop will never exit, it may assume /// that it is never reached. It may thus assume that any conditions /// sufficient to cause the loop to be reached must be false. Like many /// optimizing compilers, MSL uses this kind of analysis to establish limits /// on the range of values variables involved in those conditions might /// hold. /// /// For example, suppose the MSL compiler sees the code: /// /// ```ignore /// if (i >= 10) { /// while (true) { } /// } /// ``` /// /// It will recognize that the `while` loop will never terminate, conclude /// that it must be unreachable, and thus infer that, if this code is /// reached, then `i < 10` at that point. /// /// Now suppose that, at some point where `i` has the same value as above, /// the compiler sees the code: /// /// ```ignore /// if (i < 10) { /// a[i] = 1; /// } /// ``` /// /// Because the compiler is confident that `i < 10`, it will make the /// assignment to `a[i]` unconditional, rewriting this code as, simply: /// /// ```ignore /// a[i] = 1; /// ``` /// /// If that `if` condition was injected by Naga to implement a bounds check, /// the MSL compiler's optimizations could allow out-of-bounds array /// accesses to occur. /// /// Naga cannot feasibly anticipate whether the MSL compiler will determine /// that a loop is infinite, so an attacker could craft a Naga module /// containing an infinite loop protected by conditions that cause the Metal /// compiler to remove bounds checks that Naga injected elsewhere in the /// function. /// /// This rewrite could occur even if the conditional assignment appears /// *before* the `while` loop, as long as `i < 10` by the time the loop is /// reached. This would allow the attacker to save the results of /// unauthorized reads somewhere accessible before entering the infinite /// loop. But even worse, the MSL compiler has been observed to simply /// delete the infinite loop entirely, so that even code dominated by the /// loop becomes reachable. This would make the attack even more flexible, /// since shaders that would appear to never terminate would actually exit /// nicely, after having stolen data from elsewhere in the GPU address /// space. /// /// To avoid UB, Naga must persuade the MSL compiler that no loop Naga /// generates is infinite. One approach would be to add inline assembly to /// each loop that is annotated as potentially branching out of the loop, /// but which in fact generates no instructions. Unfortunately, inline /// assembly is not handled correctly by some Metal device drivers. /// /// A previously used approach was to add the following code to the bottom /// of every loop: /// /// ```ignore /// if (volatile bool unpredictable = false; unpredictable) /// break; /// ``` /// /// Although the `if` condition will always be false in any real execution, /// the `volatile` qualifier prevents the compiler from assuming this. Thus, /// it must assume that the `break` might be reached, and hence that the /// loop is not unbounded. This prevents the range analysis impact described /// above. Unfortunately this prevented the compiler from making important, /// and safe, optimizations such as loop unrolling and was observed to /// significantly hurt performance. /// /// Our current approach declares a counter before every loop and /// increments it every iteration, breaking after 2^64 iterations: /// /// ```ignore /// uint2 loop_bound = uint2(0); /// while (true) { /// if (metal::all(loop_bound == uint2(4294967295))) { break; } /// loop_bound += uint2(loop_bound.y == 4294967295, 1); /// } /// ``` /// /// This convinces the compiler that the loop is finite and therefore may /// execute, whilst at the same time allowing optimizations such as loop /// unrolling. Furthermore the 64-bit counter is large enough it seems /// implausible that it would affect the execution of any shader. /// /// This approach is also used by Chromium WebGPU's Dawn shader compiler: /// fn gen_force_bounded_loop_statements( &mut self, level: back::Level, context: &StatementContext, ) -> Option<(String, String)> { if !context.expression.force_loop_bounding { return None; } let loop_bound_name = self.namer.call("loop_bound"); // Count down from u32::MAX rather than up from 0 to avoid hang on // certain Intel drivers. See . let decl = format!("{level}uint2 {loop_bound_name} = uint2({}u);", u32::MAX); let level = level.next(); let break_and_inc = format!( "{level}if ({NAMESPACE}::all({loop_bound_name} == uint2(0u))) {{ break; }} {level}{loop_bound_name} -= uint2({loop_bound_name}.y == 0u, 1u);" ); Some((decl, break_and_inc)) } fn put_call_parameters( &mut self, parameters: impl Iterator>, context: &ExpressionContext, ) -> BackendResult { self.put_call_parameters_impl(parameters, context, |writer, context, expr| { writer.put_expression(expr, context, true) }) } fn put_call_parameters_impl( &mut self, parameters: impl Iterator>, ctx: &C, put_expression: E, ) -> BackendResult where E: Fn(&mut Self, &C, Handle) -> BackendResult, { write!(self.out, "(")?; for (i, handle) in parameters.enumerate() { if i != 0 { write!(self.out, ", ")?; } put_expression(self, ctx, handle)?; } write!(self.out, ")")?; Ok(()) } /// Writes the local variables of the given function, as well as any extra /// out-of-bounds locals that are needed. /// /// The names of the OOB locals are also added to `self.names` at the same /// time. fn put_locals(&mut self, context: &ExpressionContext) -> BackendResult { let oob_local_types = context.oob_local_types(); for &ty in oob_local_types.iter() { let name_key = NameKey::oob_local_for_type(context.origin, ty); self.names.insert(name_key, self.namer.call("oob")); } for (name_key, ty, init) in context .function .local_variables .iter() .map(|(local_handle, local)| { let name_key = NameKey::local(context.origin, local_handle); (name_key, local.ty, local.init) }) .chain(oob_local_types.iter().map(|&ty| { let name_key = NameKey::oob_local_for_type(context.origin, ty); (name_key, ty, None) })) { let ty_name = TypeContext { handle: ty, gctx: context.module.to_ctx(), names: &self.names, access: crate::StorageAccess::empty(), first_time: false, }; write!( self.out, "{}{} {}", back::INDENT, ty_name, self.names[&name_key] )?; match init { Some(value) => { write!(self.out, " = ")?; self.put_expression(value, context, true)?; } None => { write!(self.out, " = {{}}")?; } }; writeln!(self.out, ";")?; } Ok(()) } fn put_level_of_detail( &mut self, level: LevelOfDetail, context: &ExpressionContext, ) -> BackendResult { match level { LevelOfDetail::Direct(expr) => self.put_expression(expr, context, true)?, LevelOfDetail::Restricted(load) => write!(self.out, "{}", ClampedLod(load))?, } Ok(()) } fn put_image_query( &mut self, image: Handle, query: &str, level: Option, context: &ExpressionContext, ) -> BackendResult { self.put_expression(image, context, false)?; write!(self.out, ".get_{query}(")?; if let Some(level) = level { self.put_level_of_detail(level, context)?; } write!(self.out, ")")?; Ok(()) } fn put_image_size_query( &mut self, image: Handle, level: Option, kind: crate::ScalarKind, context: &ExpressionContext, ) -> BackendResult { if let crate::TypeInner::Image { class: crate::ImageClass::External, .. } = *context.resolve_type(image) { write!(self.out, "{IMAGE_SIZE_EXTERNAL_FUNCTION}(")?; self.put_expression(image, context, true)?; write!(self.out, ")")?; return Ok(()); } //Note: MSL only has separate width/height/depth queries, // so compose the result of them. let dim = match *context.resolve_type(image) { crate::TypeInner::Image { dim, .. } => dim, ref other => unreachable!("Unexpected type {:?}", other), }; let scalar = crate::Scalar { kind, width: 4 }; let coordinate_type = scalar.to_msl_name(); match dim { crate::ImageDimension::D1 => { // Since 1D textures never have mipmaps, MSL requires that the // `level` argument be a constexpr 0. It's simplest for us just // to pass `None` and omit the level entirely. if kind == crate::ScalarKind::Uint { // No need to construct a vector. No cast needed. self.put_image_query(image, "width", None, context)?; } else { // There's no definition for `int` in the `metal` namespace. write!(self.out, "int(")?; self.put_image_query(image, "width", None, context)?; write!(self.out, ")")?; } } crate::ImageDimension::D2 => { write!(self.out, "{NAMESPACE}::{coordinate_type}2(")?; self.put_image_query(image, "width", level, context)?; write!(self.out, ", ")?; self.put_image_query(image, "height", level, context)?; write!(self.out, ")")?; } crate::ImageDimension::D3 => { write!(self.out, "{NAMESPACE}::{coordinate_type}3(")?; self.put_image_query(image, "width", level, context)?; write!(self.out, ", ")?; self.put_image_query(image, "height", level, context)?; write!(self.out, ", ")?; self.put_image_query(image, "depth", level, context)?; write!(self.out, ")")?; } crate::ImageDimension::Cube => { write!(self.out, "{NAMESPACE}::{coordinate_type}2(")?; self.put_image_query(image, "width", level, context)?; write!(self.out, ")")?; } } Ok(()) } fn put_cast_to_uint_scalar_or_vector( &mut self, expr: Handle, context: &ExpressionContext, ) -> BackendResult { // coordinates in IR are int, but Metal expects uint match *context.resolve_type(expr) { crate::TypeInner::Scalar(_) => { put_numeric_type(&mut self.out, crate::Scalar::U32, &[])? } crate::TypeInner::Vector { size, .. } => { put_numeric_type(&mut self.out, crate::Scalar::U32, &[size])? } _ => { return Err(Error::GenericValidation( "Invalid type for image coordinate".into(), )) } }; write!(self.out, "(")?; self.put_expression(expr, context, true)?; write!(self.out, ")")?; Ok(()) } fn put_image_sample_level( &mut self, image: Handle, level: crate::SampleLevel, context: &ExpressionContext, ) -> BackendResult { let has_levels = context.image_needs_lod(image); match level { crate::SampleLevel::Auto => {} crate::SampleLevel::Zero => { //TODO: do we support Zero on `Sampled` image classes? } _ if !has_levels => { log::warn!("1D image can't be sampled with level {level:?}"); } crate::SampleLevel::Exact(h) => { write!(self.out, ", {NAMESPACE}::level(")?; self.put_expression(h, context, true)?; write!(self.out, ")")?; } crate::SampleLevel::Bias(h) => { write!(self.out, ", {NAMESPACE}::bias(")?; self.put_expression(h, context, true)?; write!(self.out, ")")?; } crate::SampleLevel::Gradient { x, y } => { write!(self.out, ", {NAMESPACE}::gradient2d(")?; self.put_expression(x, context, true)?; write!(self.out, ", ")?; self.put_expression(y, context, true)?; write!(self.out, ")")?; } } Ok(()) } fn put_image_coordinate_limits( &mut self, image: Handle, level: Option, context: &ExpressionContext, ) -> BackendResult { self.put_image_size_query(image, level, crate::ScalarKind::Uint, context)?; write!(self.out, " - 1")?; Ok(()) } /// General function for writing restricted image indexes. /// /// This is used to produce restricted mip levels, array indices, and sample /// indices for [`ImageLoad`] and [`ImageStore`] accesses under the /// [`Restrict`] bounds check policy. /// /// This function writes an expression of the form: /// /// ```ignore /// /// metal::min(uint(INDEX), IMAGE.LIMIT_METHOD() - 1) /// /// ``` /// /// [`ImageLoad`]: crate::Expression::ImageLoad /// [`ImageStore`]: crate::Statement::ImageStore /// [`Restrict`]: index::BoundsCheckPolicy::Restrict fn put_restricted_scalar_image_index( &mut self, image: Handle, index: Handle, limit_method: &str, context: &ExpressionContext, ) -> BackendResult { write!(self.out, "{NAMESPACE}::min(uint(")?; self.put_expression(index, context, true)?; write!(self.out, "), ")?; self.put_expression(image, context, false)?; write!(self.out, ".{limit_method}() - 1)")?; Ok(()) } fn put_restricted_texel_address( &mut self, image: Handle, address: &TexelAddress, context: &ExpressionContext, ) -> BackendResult { // Write the coordinate. write!(self.out, "{NAMESPACE}::min(")?; self.put_cast_to_uint_scalar_or_vector(address.coordinate, context)?; write!(self.out, ", ")?; self.put_image_coordinate_limits(image, address.level, context)?; write!(self.out, ")")?; // Write the array index, if present. if let Some(array_index) = address.array_index { write!(self.out, ", ")?; self.put_restricted_scalar_image_index(image, array_index, "get_array_size", context)?; } // Write the sample index, if present. if let Some(sample) = address.sample { write!(self.out, ", ")?; self.put_restricted_scalar_image_index(image, sample, "get_num_samples", context)?; } // The level of detail should be clamped and cached by // `put_cache_restricted_level`, so we don't need to clamp it here. if let Some(level) = address.level { write!(self.out, ", ")?; self.put_level_of_detail(level, context)?; } Ok(()) } /// Write an expression that is true if the given image access is in bounds. fn put_image_access_bounds_check( &mut self, image: Handle, address: &TexelAddress, context: &ExpressionContext, ) -> BackendResult { let mut conjunction = ""; // First, check the level of detail. Only if that is in bounds can we // use it to find the appropriate bounds for the coordinates. let level = if let Some(level) = address.level { write!(self.out, "uint(")?; self.put_level_of_detail(level, context)?; write!(self.out, ") < ")?; self.put_expression(image, context, true)?; write!(self.out, ".get_num_mip_levels()")?; conjunction = " && "; Some(level) } else { None }; // Check sample index, if present. if let Some(sample) = address.sample { write!(self.out, "uint(")?; self.put_expression(sample, context, true)?; write!(self.out, ") < ")?; self.put_expression(image, context, true)?; write!(self.out, ".get_num_samples()")?; conjunction = " && "; } // Check array index, if present. if let Some(array_index) = address.array_index { write!(self.out, "{conjunction}uint(")?; self.put_expression(array_index, context, true)?; write!(self.out, ") < ")?; self.put_expression(image, context, true)?; write!(self.out, ".get_array_size()")?; conjunction = " && "; } // Finally, check if the coordinates are within bounds. let coord_is_vector = match *context.resolve_type(address.coordinate) { crate::TypeInner::Vector { .. } => true, _ => false, }; write!(self.out, "{conjunction}")?; if coord_is_vector { write!(self.out, "{NAMESPACE}::all(")?; } self.put_cast_to_uint_scalar_or_vector(address.coordinate, context)?; write!(self.out, " < ")?; self.put_image_size_query(image, level, crate::ScalarKind::Uint, context)?; if coord_is_vector { write!(self.out, ")")?; } Ok(()) } fn put_image_load( &mut self, load: Handle, image: Handle, mut address: TexelAddress, context: &ExpressionContext, ) -> BackendResult { if let crate::TypeInner::Image { class: crate::ImageClass::External, .. } = *context.resolve_type(image) { write!(self.out, "{IMAGE_LOAD_EXTERNAL_FUNCTION}(")?; self.put_expression(image, context, true)?; write!(self.out, ", ")?; self.put_cast_to_uint_scalar_or_vector(address.coordinate, context)?; write!(self.out, ")")?; return Ok(()); } match context.policies.image_load { proc::BoundsCheckPolicy::Restrict => { // Use the cached restricted level of detail, if any. Omit the // level altogether for 1D textures. if address.level.is_some() { address.level = if context.image_needs_lod(image) { Some(LevelOfDetail::Restricted(load)) } else { None } } self.put_expression(image, context, false)?; write!(self.out, ".read(")?; self.put_restricted_texel_address(image, &address, context)?; write!(self.out, ")")?; } proc::BoundsCheckPolicy::ReadZeroSkipWrite => { write!(self.out, "(")?; self.put_image_access_bounds_check(image, &address, context)?; write!(self.out, " ? ")?; self.put_unchecked_image_load(image, &address, context)?; write!(self.out, ": DefaultConstructible())")?; } proc::BoundsCheckPolicy::Unchecked => { self.put_unchecked_image_load(image, &address, context)?; } } Ok(()) } fn put_unchecked_image_load( &mut self, image: Handle, address: &TexelAddress, context: &ExpressionContext, ) -> BackendResult { self.put_expression(image, context, false)?; write!(self.out, ".read(")?; // coordinates in IR are int, but Metal expects uint self.put_cast_to_uint_scalar_or_vector(address.coordinate, context)?; if let Some(expr) = address.array_index { write!(self.out, ", ")?; self.put_expression(expr, context, true)?; } if let Some(sample) = address.sample { write!(self.out, ", ")?; self.put_expression(sample, context, true)?; } if let Some(level) = address.level { if context.image_needs_lod(image) { write!(self.out, ", ")?; self.put_level_of_detail(level, context)?; } } write!(self.out, ")")?; Ok(()) } fn put_image_atomic( &mut self, level: back::Level, image: Handle, address: &TexelAddress, fun: crate::AtomicFunction, value: Handle, context: &StatementContext, ) -> BackendResult { write!(self.out, "{level}")?; self.put_expression(image, &context.expression, false)?; let op = if context.expression.resolve_type(value).scalar_width() == Some(8) { fun.to_msl_64_bit()? } else { fun.to_msl() }; write!(self.out, ".atomic_{op}(")?; // coordinates in IR are int, but Metal expects uint self.put_cast_to_uint_scalar_or_vector(address.coordinate, &context.expression)?; write!(self.out, ", ")?; self.put_expression(value, &context.expression, true)?; writeln!(self.out, ");")?; // Workaround for Apple Metal TBDR driver bug: fragment shader atomic // texture writes randomly drop unless followed by a standard texture // write. Insert a dead-code write behind an unprovable condition so // the compiler emits proper memory safety barriers. // See: https://projects.blender.org/blender/blender/commit/aa95220576706122d79c91c7f5c522e6c7416425 let value_ty = context.expression.resolve_type(value); let zero_value = match (value_ty.scalar_kind(), value_ty.scalar_width()) { (Some(crate::ScalarKind::Sint), _) => "int4(0)", (_, Some(8)) => "ulong4(0uL)", _ => "uint4(0u)", }; let coord_ty = context.expression.resolve_type(address.coordinate); let x = if matches!(coord_ty, crate::TypeInner::Scalar(_)) { "" } else { ".x" }; write!(self.out, "{level}if (")?; self.put_expression(address.coordinate, &context.expression, true)?; write!(self.out, "{x} == -99999) {{ ")?; self.put_expression(image, &context.expression, false)?; write!(self.out, ".write({zero_value}, ")?; self.put_cast_to_uint_scalar_or_vector(address.coordinate, &context.expression)?; if let Some(array_index) = address.array_index { write!(self.out, ", ")?; self.put_expression(array_index, &context.expression, true)?; } writeln!(self.out, "); }}")?; Ok(()) } fn put_image_store( &mut self, level: back::Level, image: Handle, address: &TexelAddress, value: Handle, context: &StatementContext, ) -> BackendResult { write!(self.out, "{level}")?; self.put_expression(image, &context.expression, false)?; write!(self.out, ".write(")?; self.put_expression(value, &context.expression, true)?; write!(self.out, ", ")?; // coordinates in IR are int, but Metal expects uint self.put_cast_to_uint_scalar_or_vector(address.coordinate, &context.expression)?; if let Some(expr) = address.array_index { write!(self.out, ", ")?; self.put_expression(expr, &context.expression, true)?; } writeln!(self.out, ");")?; Ok(()) } /// Write the maximum valid index of the dynamically sized array at the end of `handle`. /// /// The 'maximum valid index' is simply one less than the array's length. /// /// This emits an expression of the form `a / b`, so the caller must /// parenthesize its output if it will be applying operators of higher /// precedence. /// /// `handle` must be the handle of a global variable whose final member is a /// dynamically sized array. fn put_dynamic_array_max_index( &mut self, handle: Handle, context: &ExpressionContext, ) -> BackendResult { let global = &context.module.global_variables[handle]; let (offset, array_ty) = match context.module.types[global.ty].inner { crate::TypeInner::Struct { ref members, .. } => match members.last() { Some(&crate::StructMember { offset, ty, .. }) => (offset, ty), None => return Err(Error::GenericValidation("Struct has no members".into())), }, crate::TypeInner::Array { size: crate::ArraySize::Dynamic, .. } => (0, global.ty), ref ty => { return Err(Error::GenericValidation(format!( "Expected type with dynamic array, got {ty:?}" ))) } }; let (size, stride) = match context.module.types[array_ty].inner { crate::TypeInner::Array { base, stride, .. } => ( context.module.types[base] .inner .size(context.module.to_ctx()), stride, ), ref ty => { return Err(Error::GenericValidation(format!( "Expected array type, got {ty:?}" ))) } }; // When the stride length is larger than the size, the final element's stride of // bytes would have padding following the value. But the buffer size in // `buffer_sizes.sizeN` may not include this padding - it only needs to be large // enough to hold the actual values' bytes. // // So subtract off the size to get a byte size that falls at the start or within // the final element. Then divide by the stride size, to get one less than the // length, and then add one. This works even if the buffer size does include the // stride padding, since division rounds towards zero (MSL 2.4 §6.1). It will fail // if there are zero elements in the array, but the WebGPU `validating shader binding` // rules, together with draw-time validation when `minBindingSize` is zero, // prevent that. write!( self.out, "(_buffer_sizes.{member} - {offset} - {size}) / {stride}", member = ArraySizeMember(handle), offset = offset, size = size, stride = stride, )?; Ok(()) } /// Emit code for the arithmetic expression of the dot product. /// /// The argument `extractor` is a function that accepts a `Writer`, a vector, and /// an index. It writes out the expression for the vector component at that index. fn put_dot_product( &mut self, arg: T, arg1: T, size: usize, extractor: impl Fn(&mut Self, T, usize) -> BackendResult, ) -> BackendResult { // Write parentheses around the dot product expression to prevent operators // with different precedences from applying earlier. write!(self.out, "(")?; // Cycle through all the components of the vector for index in 0..size { // Write the addition to the previous product // This will print an extra '+' at the beginning but that is fine in msl write!(self.out, " + ")?; extractor(self, arg, index)?; write!(self.out, " * ")?; extractor(self, arg1, index)?; } write!(self.out, ")")?; Ok(()) } /// Emit code for the WGSL functions `pack4x{I, U}8[Clamp]`. fn put_pack4x8( &mut self, arg: Handle, context: &ExpressionContext<'_>, was_signed: bool, clamp_bounds: Option<(&str, &str)>, ) -> Result<(), Error> { let write_arg = |this: &mut Self| -> BackendResult { if let Some((min, max)) = clamp_bounds { // Clamping with scalar bounds works (component-wise) even for packed_[u]char4. write!(this.out, "{NAMESPACE}::clamp(")?; this.put_expression(arg, context, true)?; write!(this.out, ", {min}, {max})")?; } else { this.put_expression(arg, context, true)?; } Ok(()) }; if context.lang_version >= (2, 1) { let packed_type = if was_signed { "packed_char4" } else { "packed_uchar4" }; // Metal uses little endian byte order, which matches what WGSL expects here. write!(self.out, "as_type({packed_type}(")?; write_arg(self)?; write!(self.out, "))")?; } else { // MSL < 2.1 doesn't support `as_type` casting between packed chars and scalars. if was_signed { write!(self.out, "uint(")?; } write!(self.out, "(")?; write_arg(self)?; write!(self.out, "[0] & 0xFF) | ((")?; write_arg(self)?; write!(self.out, "[1] & 0xFF) << 8) | ((")?; write_arg(self)?; write!(self.out, "[2] & 0xFF) << 16) | ((")?; write_arg(self)?; write!(self.out, "[3] & 0xFF) << 24)")?; if was_signed { write!(self.out, ")")?; } } Ok(()) } /// Emit code for the isign expression. /// fn put_isign( &mut self, arg: Handle, context: &ExpressionContext, ) -> BackendResult { write!(self.out, "{NAMESPACE}::select({NAMESPACE}::select(")?; let scalar = context .resolve_type(arg) .scalar() .expect("put_isign should only be called for args which have an integer scalar type") .to_msl_name(); match context.resolve_type(arg) { &crate::TypeInner::Vector { size, .. } => { let size = common::vector_size_str(size); write!(self.out, "{scalar}{size}(-1), {scalar}{size}(1)")?; } _ => { write!(self.out, "{scalar}(-1), {scalar}(1)")?; } } write!(self.out, ", (")?; self.put_expression(arg, context, true)?; write!(self.out, " > 0)), {scalar}(0), (")?; self.put_expression(arg, context, true)?; write!(self.out, " == 0))")?; Ok(()) } fn put_const_expression( &mut self, expr_handle: Handle, module: &crate::Module, mod_info: &valid::ModuleInfo, arena: &crate::Arena, ) -> BackendResult { self.put_possibly_const_expression( expr_handle, arena, module, mod_info, &(module, mod_info), |&(_, mod_info), expr| &mod_info[expr], |writer, &(module, _), expr| writer.put_const_expression(expr, module, mod_info, arena), ) } fn put_literal(&mut self, literal: crate::Literal) -> BackendResult { match literal { crate::Literal::F64(_) => { return Err(Error::CapabilityNotSupported(valid::Capabilities::FLOAT64)) } crate::Literal::F16(value) => { if value.is_infinite() { let sign = if value.is_sign_negative() { "-" } else { "" }; write!(self.out, "{sign}INFINITY")?; } else if value.is_nan() { write!(self.out, "NAN")?; } else { let suffix = if value.fract() == f16::from_f32(0.0) { ".0h" } else { "h" }; write!(self.out, "{value}{suffix}")?; } } crate::Literal::F32(value) => { if value.is_infinite() { let sign = if value.is_sign_negative() { "-" } else { "" }; write!(self.out, "{sign}INFINITY")?; } else if value.is_nan() { write!(self.out, "NAN")?; } else { let suffix = if value.fract() == 0.0 { ".0" } else { "" }; write!(self.out, "{value}{suffix}")?; } } crate::Literal::U32(value) => { write!(self.out, "{value}u")?; } crate::Literal::I32(value) => { // `-2147483648` is parsed as unary negation of positive 2147483648. // 2147483648 is too large for int32_t meaning the expression gets // promoted to a int64_t which is not our intention. Avoid this by instead // using `-2147483647 - 1`. if value == i32::MIN { write!(self.out, "({} - 1)", value + 1)?; } else { write!(self.out, "{value}")?; } } crate::Literal::U64(value) => { write!(self.out, "{value}uL")?; } crate::Literal::I64(value) => { // `-9223372036854775808` is parsed as unary negation of positive // 9223372036854775808. 9223372036854775808 is too large for int64_t // causing Metal to emit a `-Wconstant-conversion` warning, and change the // value to `-9223372036854775808`. Which would then be negated, possibly // causing undefined behaviour. Avoid this by instead using // `-9223372036854775808L - 1L`. if value == i64::MIN { write!(self.out, "({}L - 1L)", value + 1)?; } else { write!(self.out, "{value}L")?; } } crate::Literal::Bool(value) => { write!(self.out, "{value}")?; } crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { return Err(Error::GenericValidation( "Unsupported abstract literal".into(), )); } } Ok(()) } #[allow(clippy::too_many_arguments)] fn put_possibly_const_expression( &mut self, expr_handle: Handle, expressions: &crate::Arena, module: &crate::Module, mod_info: &valid::ModuleInfo, ctx: &C, get_expr_ty: I, put_expression: E, ) -> BackendResult where I: Fn(&C, Handle) -> &TypeResolution, E: Fn(&mut Self, &C, Handle) -> BackendResult, { match expressions[expr_handle] { crate::Expression::Literal(literal) => { self.put_literal(literal)?; } crate::Expression::Constant(handle) => { let constant = &module.constants[handle]; if constant.name.is_some() { write!(self.out, "{}", self.names[&NameKey::Constant(handle)])?; } else { self.put_const_expression( constant.init, module, mod_info, &module.global_expressions, )?; } } crate::Expression::ZeroValue(ty) => { let ty_name = TypeContext { handle: ty, gctx: module.to_ctx(), names: &self.names, access: crate::StorageAccess::empty(), first_time: false, }; write!(self.out, "{ty_name} {{}}")?; } crate::Expression::Compose { ty, ref components } => { let ty_name = TypeContext { handle: ty, gctx: module.to_ctx(), names: &self.names, access: crate::StorageAccess::empty(), first_time: false, }; write!(self.out, "{ty_name}")?; match module.types[ty].inner { crate::TypeInner::Scalar(_) | crate::TypeInner::Vector { .. } | crate::TypeInner::Matrix { .. } => { self.put_call_parameters_impl( components.iter().copied(), ctx, put_expression, )?; } crate::TypeInner::Array { .. } => { // Naga Arrays are Metal arrays wrapped in structs, so // we need two levels of braces. write!(self.out, " {{{{")?; for (index, &component) in components.iter().enumerate() { if index != 0 { write!(self.out, ", ")?; } put_expression(self, ctx, component)?; } write!(self.out, "}}}}")?; } crate::TypeInner::Struct { .. } => { write!(self.out, " {{")?; for (index, &component) in components.iter().enumerate() { if index != 0 { write!(self.out, ", ")?; } // insert padding initialization, if needed if self.struct_member_pads.contains(&(ty, index as u32)) { write!(self.out, "{{}}, ")?; } put_expression(self, ctx, component)?; } write!(self.out, "}}")?; } _ => return Err(Error::UnsupportedCompose(ty)), } } crate::Expression::Splat { size, value } => { let scalar = match *get_expr_ty(ctx, value).inner_with(&module.types) { crate::TypeInner::Scalar(scalar) => scalar, ref ty => { return Err(Error::GenericValidation(format!( "Expected splat value type must be a scalar, got {ty:?}", ))) } }; put_numeric_type(&mut self.out, scalar, &[size])?; write!(self.out, "(")?; put_expression(self, ctx, value)?; write!(self.out, ")")?; } _ => { return Err(Error::Override); } } Ok(()) } /// Emit code for the expression `expr_handle`. /// /// The `is_scoped` argument is true if the surrounding operators have the /// precedence of the comma operator, or lower. So, for example: /// /// - Pass `true` for `is_scoped` when writing function arguments, an /// expression statement, an initializer expression, or anything already /// wrapped in parenthesis. /// /// - Pass `false` if it is an operand of a `?:` operator, a `[]`, or really /// almost anything else. fn put_expression( &mut self, expr_handle: Handle, context: &ExpressionContext, is_scoped: bool, ) -> BackendResult { // Add to the set in order to track the stack size. #[cfg(test)] self.put_expression_stack_pointers .insert(ptr::from_ref(&expr_handle).cast()); if let Some(name) = self.named_expressions.get(&expr_handle) { write!(self.out, "{name}")?; return Ok(()); } let expression = &context.function.expressions[expr_handle]; match *expression { crate::Expression::Literal(_) | crate::Expression::Constant(_) | crate::Expression::ZeroValue(_) | crate::Expression::Compose { .. } | crate::Expression::Splat { .. } => { self.put_possibly_const_expression( expr_handle, &context.function.expressions, context.module, context.mod_info, context, |context, expr: Handle| &context.info[expr].ty, |writer, context, expr| writer.put_expression(expr, context, true), )?; } crate::Expression::Override(_) => return Err(Error::Override), crate::Expression::Access { base, .. } | crate::Expression::AccessIndex { base, .. } => { // This is an acceptable place to generate a `ReadZeroSkipWrite` check. // Since `put_bounds_checks` and `put_access_chain` handle an entire // access chain at a time, recursing back through `put_expression` only // for index expressions and the base object, we will never see intermediate // `Access` or `AccessIndex` expressions here. let policy = context.choose_bounds_check_policy(base); if policy == index::BoundsCheckPolicy::ReadZeroSkipWrite && self.put_bounds_checks( expr_handle, context, back::Level(0), if is_scoped { "" } else { "(" }, )? { write!(self.out, " ? ")?; self.put_access_chain(expr_handle, policy, context)?; write!(self.out, " : ")?; if context.resolve_type(base).pointer_space().is_some() { // We can't just use `DefaultConstructible` if this is a pointer. // Instead, we create a dummy local variable to serve as pointer // target if the access is out of bounds. let result_ty = context.info[expr_handle] .ty .inner_with(&context.module.types) .pointer_base_type(); let result_ty_handle = match result_ty { Some(TypeResolution::Handle(handle)) => handle, Some(TypeResolution::Value(_)) => { // As long as the result of a pointer access expression is // passed to a function or stored in a let binding, the // type will be in the arena. If additional uses of // pointers become valid, this assumption might no longer // hold. Note that the LHS of a load or store doesn't // take this path -- there is dedicated code in `put_load` // and `put_store`. unreachable!( "Expected type {result_ty:?} of access through pointer type {base:?} to be in the arena", ); } None => { unreachable!( "Expected access through pointer type {base:?} to return a pointer, but got {result_ty:?}", ) } }; let name_key = NameKey::oob_local_for_type(context.origin, result_ty_handle); self.out.write_str(&self.names[&name_key])?; } else { write!(self.out, "DefaultConstructible()")?; } if !is_scoped { write!(self.out, ")")?; } } else { self.put_access_chain(expr_handle, policy, context)?; } } crate::Expression::Swizzle { size, vector, pattern, } => { self.put_wrapped_expression_for_packed_vec3_access( vector, context, false, &Self::put_expression, )?; write!(self.out, ".")?; for &sc in pattern[..size as usize].iter() { write!(self.out, "{}", back::COMPONENTS[sc as usize])?; } } crate::Expression::FunctionArgument(index) => { let name_key = match context.origin { FunctionOrigin::Handle(handle) => NameKey::FunctionArgument(handle, index), FunctionOrigin::EntryPoint(ep_index) => { NameKey::EntryPointArgument(ep_index, index) } }; let name = &self.names[&name_key]; write!(self.out, "{name}")?; } crate::Expression::GlobalVariable(handle) => { let name = &self.names[&NameKey::GlobalVariable(handle)]; write!(self.out, "{name}")?; } crate::Expression::LocalVariable(handle) => { let name_key = NameKey::local(context.origin, handle); let name = &self.names[&name_key]; write!(self.out, "{name}")?; } crate::Expression::Load { pointer } => self.put_load(pointer, context, is_scoped)?, crate::Expression::ImageSample { coordinate, image, sampler, clamp_to_edge: true, gather: None, array_index: None, offset: None, level: crate::SampleLevel::Zero, depth_ref: None, } => { write!(self.out, "{IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION}(")?; self.put_expression(image, context, true)?; write!(self.out, ", ")?; self.put_expression(sampler, context, true)?; write!(self.out, ", ")?; self.put_expression(coordinate, context, true)?; write!(self.out, ")")?; } crate::Expression::ImageSample { image, sampler, gather, coordinate, array_index, offset, level, depth_ref, clamp_to_edge, } => { if clamp_to_edge { return Err(Error::GenericValidation( "ImageSample::clamp_to_edge should have been validated out".to_string(), )); } let main_op = match gather { Some(_) => "gather", None => "sample", }; let comparison_op = match depth_ref { Some(_) => "_compare", None => "", }; self.put_expression(image, context, false)?; write!(self.out, ".{main_op}{comparison_op}(")?; self.put_expression(sampler, context, true)?; write!(self.out, ", ")?; self.put_expression(coordinate, context, true)?; if let Some(expr) = array_index { write!(self.out, ", ")?; self.put_expression(expr, context, true)?; } if let Some(dref) = depth_ref { write!(self.out, ", ")?; self.put_expression(dref, context, true)?; } self.put_image_sample_level(image, level, context)?; if let Some(offset) = offset { write!(self.out, ", ")?; self.put_expression(offset, context, true)?; } match gather { None | Some(crate::SwizzleComponent::X) => {} Some(component) => { let is_cube_map = match *context.resolve_type(image) { crate::TypeInner::Image { dim: crate::ImageDimension::Cube, .. } => true, _ => false, }; // Offset always comes before the gather, except // in cube maps where it's not applicable if offset.is_none() && !is_cube_map { write!(self.out, ", {NAMESPACE}::int2(0)")?; } let letter = back::COMPONENTS[component as usize]; write!(self.out, ", {NAMESPACE}::component::{letter}")?; } } write!(self.out, ")")?; } crate::Expression::ImageLoad { image, coordinate, array_index, sample, level, } => { let address = TexelAddress { coordinate, array_index, sample, level: level.map(LevelOfDetail::Direct), }; self.put_image_load(expr_handle, image, address, context)?; } //Note: for all the queries, the signed integers are expected, // so a conversion is needed. crate::Expression::ImageQuery { image, query } => match query { crate::ImageQuery::Size { level } => { self.put_image_size_query( image, level.map(LevelOfDetail::Direct), crate::ScalarKind::Uint, context, )?; } crate::ImageQuery::NumLevels => { self.put_expression(image, context, false)?; write!(self.out, ".get_num_mip_levels()")?; } crate::ImageQuery::NumLayers => { self.put_expression(image, context, false)?; write!(self.out, ".get_array_size()")?; } crate::ImageQuery::NumSamples => { self.put_expression(image, context, false)?; write!(self.out, ".get_num_samples()")?; } }, crate::Expression::Unary { op, expr } => { let op_str = match op { crate::UnaryOperator::Negate => { match context.resolve_type(expr).scalar_kind() { Some(crate::ScalarKind::Sint) => NEG_FUNCTION, _ => "-", } } crate::UnaryOperator::LogicalNot => "!", crate::UnaryOperator::BitwiseNot => "~", }; write!(self.out, "{op_str}(")?; self.put_expression(expr, context, false)?; write!(self.out, ")")?; } crate::Expression::Binary { op, left, right } => { let kind = context .resolve_type(left) .scalar_kind() .ok_or(Error::UnsupportedBinaryOp(op))?; if op == crate::BinaryOperator::Divide && (kind == crate::ScalarKind::Sint || kind == crate::ScalarKind::Uint) { write!(self.out, "{DIV_FUNCTION}(")?; self.put_expression(left, context, true)?; write!(self.out, ", ")?; self.put_expression(right, context, true)?; write!(self.out, ")")?; } else if op == crate::BinaryOperator::Modulo && (kind == crate::ScalarKind::Sint || kind == crate::ScalarKind::Uint) { write!(self.out, "{MOD_FUNCTION}(")?; self.put_expression(left, context, true)?; write!(self.out, ", ")?; self.put_expression(right, context, true)?; write!(self.out, ")")?; } else if op == crate::BinaryOperator::Modulo && kind == crate::ScalarKind::Float { // TODO: handle undefined behavior of BinaryOperator::Modulo // // float: // if right == 0 return ? see https://github.com/gpuweb/gpuweb/issues/2798 write!(self.out, "{NAMESPACE}::fmod(")?; self.put_expression(left, context, true)?; write!(self.out, ", ")?; self.put_expression(right, context, true)?; write!(self.out, ")")?; } else if (op == crate::BinaryOperator::Add || op == crate::BinaryOperator::Subtract || op == crate::BinaryOperator::Multiply) && kind == crate::ScalarKind::Sint { let to_unsigned = |ty: &crate::TypeInner| match *ty { crate::TypeInner::Scalar(scalar) => { Ok(crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Uint, ..scalar })) } crate::TypeInner::Vector { size, scalar } => Ok(crate::TypeInner::Vector { size, scalar: crate::Scalar { kind: crate::ScalarKind::Uint, ..scalar }, }), _ => Err(Error::UnsupportedBitCast(ty.clone())), }; // Avoid undefined behaviour due to overflowing signed // integer arithmetic. Cast the operands to unsigned prior // to performing the operation, then cast the result back // to signed. self.put_bitcasted_expression( context.resolve_type(expr_handle), context, &|writer, context, is_scoped| { writer.put_binop( op, left, right, context, is_scoped, &|writer, expr, context, _is_scoped| { writer.put_bitcasted_expression( &to_unsigned(context.resolve_type(expr))?, context, &|writer, context, is_scoped| { writer.put_expression(expr, context, is_scoped) }, ) }, ) }, )?; } else { self.put_binop(op, left, right, context, is_scoped, &Self::put_expression)?; } } crate::Expression::Select { condition, accept, reject, } => match *context.resolve_type(condition) { crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Bool, .. }) => { if !is_scoped { write!(self.out, "(")?; } self.put_expression(condition, context, false)?; write!(self.out, " ? ")?; self.put_expression(accept, context, false)?; write!(self.out, " : ")?; self.put_expression(reject, context, false)?; if !is_scoped { write!(self.out, ")")?; } } crate::TypeInner::Vector { scalar: crate::Scalar { kind: crate::ScalarKind::Bool, .. }, .. } => { write!(self.out, "{NAMESPACE}::select(")?; self.put_expression(reject, context, true)?; write!(self.out, ", ")?; self.put_expression(accept, context, true)?; write!(self.out, ", ")?; self.put_expression(condition, context, true)?; write!(self.out, ")")?; } ref ty => { return Err(Error::GenericValidation(format!( "Expected select condition to be a non-bool type, got {ty:?}", ))) } }, crate::Expression::Derivative { axis, expr, .. } => { use crate::DerivativeAxis as Axis; let op = match axis { Axis::X => "dfdx", Axis::Y => "dfdy", Axis::Width => "fwidth", }; write!(self.out, "{NAMESPACE}::{op}")?; self.put_call_parameters(iter::once(expr), context)?; } crate::Expression::Relational { fun, argument } => { let op = match fun { crate::RelationalFunction::Any => "any", crate::RelationalFunction::All => "all", crate::RelationalFunction::IsNan => "isnan", crate::RelationalFunction::IsInf => "isinf", }; write!(self.out, "{NAMESPACE}::{op}")?; self.put_call_parameters(iter::once(argument), context)?; } crate::Expression::Math { fun, arg, arg1, arg2, arg3, } => { use crate::MathFunction as Mf; let arg_type = context.resolve_type(arg); let scalar_argument = match arg_type { &crate::TypeInner::Scalar(_) => true, _ => false, }; let fun_name = match fun { // comparison Mf::Abs => "abs", Mf::Min => "min", Mf::Max => "max", Mf::Clamp => "clamp", Mf::Saturate => "saturate", // trigonometry Mf::Cos => "cos", Mf::Cosh => "cosh", Mf::Sin => "sin", Mf::Sinh => "sinh", Mf::Tan => "tan", Mf::Tanh => "tanh", Mf::Acos => "acos", Mf::Asin => "asin", Mf::Atan => "atan", Mf::Atan2 => "atan2", Mf::Asinh => "asinh", Mf::Acosh => "acosh", Mf::Atanh => "atanh", Mf::Radians => "", Mf::Degrees => "", // decomposition Mf::Ceil => "ceil", Mf::Floor => "floor", Mf::Round => "rint", Mf::Fract => "fract", Mf::Trunc => "trunc", Mf::Modf => MODF_FUNCTION, Mf::Frexp => FREXP_FUNCTION, Mf::Ldexp => "ldexp", // exponent Mf::Exp => "exp", Mf::Exp2 => "exp2", Mf::Log => "log", Mf::Log2 => "log2", Mf::Pow => "pow", // geometry Mf::Dot => match *context.resolve_type(arg) { crate::TypeInner::Vector { scalar: crate::Scalar { // Resolve float values to MSL's builtin dot function. kind: crate::ScalarKind::Float, .. }, .. } => "dot", crate::TypeInner::Vector { size, scalar: scalar @ crate::Scalar { kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, .. }, } => { // Integer vector dot: call our mangled helper `dot_{type}{N}(a, b)`. let fun_name = self.get_dot_wrapper_function_helper_name(scalar, size); write!(self.out, "{fun_name}(")?; self.put_expression(arg, context, true)?; write!(self.out, ", ")?; self.put_expression(arg1.unwrap(), context, true)?; write!(self.out, ")")?; return Ok(()); } _ => unreachable!( "Correct TypeInner for dot product should be already validated" ), }, fun @ (Mf::Dot4I8Packed | Mf::Dot4U8Packed) => { if context.lang_version >= (2, 1) { // Write potentially optimizable code using `packed_(u?)char4`. // The two function arguments were already reinterpreted as packed (signed // or unsigned) chars in `Self::put_block`. let packed_type = match fun { Mf::Dot4I8Packed => "packed_char4", Mf::Dot4U8Packed => "packed_uchar4", _ => unreachable!(), }; return self.put_dot_product( Reinterpreted::new(packed_type, arg), Reinterpreted::new(packed_type, arg1.unwrap()), 4, |writer, arg, index| { // MSL implicitly promotes these (signed or unsigned) chars to // `int` or `uint` in the multiplication, so no overflow can occur. write!(writer.out, "{arg}[{index}]")?; Ok(()) }, ); } else { // Fall back to a polyfill since MSL < 2.1 doesn't seem to support // bitcasting from uint to `packed_char4` or `packed_uchar4`. // See . let conversion = match fun { Mf::Dot4I8Packed => "int", Mf::Dot4U8Packed => "", _ => unreachable!(), }; return self.put_dot_product( arg, arg1.unwrap(), 4, |writer, arg, index| { write!(writer.out, "({conversion}(")?; writer.put_expression(arg, context, true)?; if index == 3 { write!(writer.out, ") >> 24)")?; } else { write!(writer.out, ") << {} >> 24)", (3 - index) * 8)?; } Ok(()) }, ); } } Mf::Outer => return Err(Error::UnsupportedCall(format!("{fun:?}"))), Mf::Cross => "cross", Mf::Distance => "distance", Mf::Length if scalar_argument => "abs", Mf::Length => "length", Mf::Normalize => "normalize", Mf::FaceForward => "faceforward", Mf::Reflect => "reflect", Mf::Refract => "refract", // computational Mf::Sign => match arg_type.scalar_kind() { Some(crate::ScalarKind::Sint) => { return self.put_isign(arg, context); } _ => "sign", }, Mf::Fma => "fma", Mf::Mix => "mix", Mf::Step => "step", Mf::SmoothStep => "smoothstep", Mf::Sqrt => "sqrt", Mf::InverseSqrt => "rsqrt", Mf::Inverse => return Err(Error::UnsupportedCall(format!("{fun:?}"))), Mf::Transpose => "transpose", Mf::Determinant => "determinant", Mf::QuantizeToF16 => "", // bits Mf::CountTrailingZeros => "ctz", Mf::CountLeadingZeros => "clz", Mf::CountOneBits => "popcount", Mf::ReverseBits => "reverse_bits", Mf::ExtractBits => "", Mf::InsertBits => "", Mf::FirstTrailingBit => "", Mf::FirstLeadingBit => "", // data packing Mf::Pack4x8snorm => "pack_float_to_snorm4x8", Mf::Pack4x8unorm => "pack_float_to_unorm4x8", Mf::Pack2x16snorm => "pack_float_to_snorm2x16", Mf::Pack2x16unorm => "pack_float_to_unorm2x16", Mf::Pack2x16float => "", Mf::Pack4xI8 => "", Mf::Pack4xU8 => "", Mf::Pack4xI8Clamp => "", Mf::Pack4xU8Clamp => "", // data unpacking Mf::Unpack4x8snorm => "unpack_snorm4x8_to_float", Mf::Unpack4x8unorm => "unpack_unorm4x8_to_float", Mf::Unpack2x16snorm => "unpack_snorm2x16_to_float", Mf::Unpack2x16unorm => "unpack_unorm2x16_to_float", Mf::Unpack2x16float => "", Mf::Unpack4xI8 => "", Mf::Unpack4xU8 => "", }; match fun { Mf::ReverseBits | Mf::ExtractBits | Mf::InsertBits => { // reverse_bits is listed as requiring MSL 2.1 but that // is a copy/paste error. Looking at previous snapshots // on web.archive.org it's present in MSL 1.2. // // https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/WhatsNewiniOS10tvOS10andOSX1012/WhatsNewiniOS10tvOS10andOSX1012.html // also talks about MSL 1.2 adding "New integer // functions to extract, insert, and reverse bits, as // described in Integer Functions." if context.lang_version < (1, 2) { return Err(Error::UnsupportedFunction(fun_name.to_string())); } } _ => {} } match fun { Mf::Abs if arg_type.scalar_kind() == Some(crate::ScalarKind::Sint) => { write!(self.out, "{ABS_FUNCTION}(")?; self.put_expression(arg, context, true)?; write!(self.out, ")")?; } Mf::Distance if scalar_argument => { write!(self.out, "{NAMESPACE}::abs(")?; self.put_expression(arg, context, false)?; write!(self.out, " - ")?; self.put_expression(arg1.unwrap(), context, false)?; write!(self.out, ")")?; } Mf::FirstTrailingBit => { let scalar = context.resolve_type(arg).scalar().unwrap(); let constant = scalar.width * 8 + 1; write!(self.out, "((({NAMESPACE}::ctz(")?; self.put_expression(arg, context, true)?; write!(self.out, ") + 1) % {constant}) - 1)")?; } Mf::FirstLeadingBit => { let inner = context.resolve_type(arg); let scalar = inner.scalar().unwrap(); let constant = scalar.width * 8 - 1; write!( self.out, "{NAMESPACE}::select({constant} - {NAMESPACE}::clz(" )?; if scalar.kind == crate::ScalarKind::Sint { write!(self.out, "{NAMESPACE}::select(")?; self.put_expression(arg, context, true)?; write!(self.out, ", ~")?; self.put_expression(arg, context, true)?; write!(self.out, ", ")?; self.put_expression(arg, context, true)?; write!(self.out, " < 0)")?; } else { self.put_expression(arg, context, true)?; } write!(self.out, "), ")?; // or metal will complain that select is ambiguous match *inner { crate::TypeInner::Vector { size, scalar } => { let size = common::vector_size_str(size); let name = scalar.to_msl_name(); write!(self.out, "{name}{size}")?; } crate::TypeInner::Scalar(scalar) => { let name = scalar.to_msl_name(); write!(self.out, "{name}")?; } _ => (), } write!(self.out, "(-1), ")?; self.put_expression(arg, context, true)?; write!(self.out, " == 0 || ")?; self.put_expression(arg, context, true)?; write!(self.out, " == -1)")?; } Mf::Unpack2x16float => { write!(self.out, "float2(as_type(")?; self.put_expression(arg, context, false)?; write!(self.out, "))")?; } Mf::Pack2x16float => { write!(self.out, "as_type(half2(")?; self.put_expression(arg, context, false)?; write!(self.out, "))")?; } Mf::ExtractBits => { // The behavior of ExtractBits is undefined when offset + count > bit_width. We need // to first sanitize the offset and count first. If we don't do this, Apple chips // will return out-of-spec values if the extracted range is not within the bit width. // // This encodes the exact formula specified by the wgsl spec, without temporary values: // https://gpuweb.github.io/gpuweb/wgsl/#extractBits-unsigned-builtin // // w = sizeof(x) * 8 // o = min(offset, w) // tmp = w - o // c = min(count, tmp) // // bitfieldExtract(x, o, c) // // extract_bits(e, min(offset, w), min(count, w - min(offset, w)))) let scalar_bits = context.resolve_type(arg).scalar_width().unwrap() * 8; write!(self.out, "{NAMESPACE}::extract_bits(")?; self.put_expression(arg, context, true)?; write!(self.out, ", {NAMESPACE}::min(")?; self.put_expression(arg1.unwrap(), context, true)?; write!(self.out, ", {scalar_bits}u), {NAMESPACE}::min(")?; self.put_expression(arg2.unwrap(), context, true)?; write!(self.out, ", {scalar_bits}u - {NAMESPACE}::min(")?; self.put_expression(arg1.unwrap(), context, true)?; write!(self.out, ", {scalar_bits}u)))")?; } Mf::InsertBits => { // The behavior of InsertBits has the same issue as ExtractBits. // // insertBits(e, newBits, min(offset, w), min(count, w - min(offset, w)))) let scalar_bits = context.resolve_type(arg).scalar_width().unwrap() * 8; write!(self.out, "{NAMESPACE}::insert_bits(")?; self.put_expression(arg, context, true)?; write!(self.out, ", ")?; self.put_expression(arg1.unwrap(), context, true)?; write!(self.out, ", {NAMESPACE}::min(")?; self.put_expression(arg2.unwrap(), context, true)?; write!(self.out, ", {scalar_bits}u), {NAMESPACE}::min(")?; self.put_expression(arg3.unwrap(), context, true)?; write!(self.out, ", {scalar_bits}u - {NAMESPACE}::min(")?; self.put_expression(arg2.unwrap(), context, true)?; write!(self.out, ", {scalar_bits}u)))")?; } Mf::Radians => { write!(self.out, "((")?; self.put_expression(arg, context, false)?; write!(self.out, ") * 0.017453292519943295474)")?; } Mf::Degrees => { write!(self.out, "((")?; self.put_expression(arg, context, false)?; write!(self.out, ") * 57.295779513082322865)")?; } Mf::Modf | Mf::Frexp => { write!(self.out, "{fun_name}")?; self.put_call_parameters(iter::once(arg), context)?; } Mf::Pack4xI8 => self.put_pack4x8(arg, context, true, None)?, Mf::Pack4xU8 => self.put_pack4x8(arg, context, false, None)?, Mf::Pack4xI8Clamp => { self.put_pack4x8(arg, context, true, Some(("-128", "127")))? } Mf::Pack4xU8Clamp => { self.put_pack4x8(arg, context, false, Some(("0", "255")))? } fun @ (Mf::Unpack4xI8 | Mf::Unpack4xU8) => { let sign_prefix = if matches!(fun, Mf::Unpack4xU8) { "u" } else { "" }; if context.lang_version >= (2, 1) { // Metal uses little endian byte order, which matches what WGSL expects here. write!( self.out, "{sign_prefix}int4(as_type(" )?; self.put_expression(arg, context, true)?; write!(self.out, "))")?; } else { // MSL < 2.1 doesn't support `as_type` casting between packed chars and scalars. write!(self.out, "({sign_prefix}int4(")?; self.put_expression(arg, context, true)?; write!(self.out, ", ")?; self.put_expression(arg, context, true)?; write!(self.out, " >> 8, ")?; self.put_expression(arg, context, true)?; write!(self.out, " >> 16, ")?; self.put_expression(arg, context, true)?; write!(self.out, " >> 24) << 24 >> 24)")?; } } Mf::QuantizeToF16 => { match *context.resolve_type(arg) { crate::TypeInner::Scalar { .. } => write!(self.out, "float(half(")?, crate::TypeInner::Vector { size, .. } => write!( self.out, "{NAMESPACE}::float{size}({NAMESPACE}::half{size}(", size = common::vector_size_str(size), )?, _ => unreachable!( "Correct TypeInner for QuantizeToF16 should be already validated" ), }; self.put_expression(arg, context, true)?; write!(self.out, "))")?; } _ => { write!(self.out, "{NAMESPACE}::{fun_name}")?; self.put_call_parameters( iter::once(arg).chain(arg1).chain(arg2).chain(arg3), context, )?; } } } crate::Expression::As { expr, kind, convert, } => match *context.resolve_type(expr) { crate::TypeInner::Scalar(src) | crate::TypeInner::Vector { scalar: src, .. } => { if src.kind == crate::ScalarKind::Float && (kind == crate::ScalarKind::Sint || kind == crate::ScalarKind::Uint) && convert.is_some() { // Use helper functions for float to int casts in order to avoid // undefined behaviour when value is out of range for the target // type. let fun_name = match (kind, convert) { (crate::ScalarKind::Sint, Some(4)) => F2I32_FUNCTION, (crate::ScalarKind::Uint, Some(4)) => F2U32_FUNCTION, (crate::ScalarKind::Sint, Some(8)) => F2I64_FUNCTION, (crate::ScalarKind::Uint, Some(8)) => F2U64_FUNCTION, _ => unreachable!(), }; write!(self.out, "{fun_name}(")?; self.put_expression(expr, context, true)?; write!(self.out, ")")?; } else { let target_scalar = crate::Scalar { kind, width: convert.unwrap_or(src.width), }; let op = match convert { Some(_) => "static_cast", None => "as_type", }; write!(self.out, "{op}<")?; match *context.resolve_type(expr) { crate::TypeInner::Vector { size, .. } => { put_numeric_type(&mut self.out, target_scalar, &[size])? } _ => put_numeric_type(&mut self.out, target_scalar, &[])?, }; write!(self.out, ">(")?; self.put_expression(expr, context, true)?; write!(self.out, ")")?; } } crate::TypeInner::Matrix { columns, rows, scalar, } => { let target_scalar = crate::Scalar { kind, width: convert.unwrap_or(scalar.width), }; put_numeric_type(&mut self.out, target_scalar, &[rows, columns])?; write!(self.out, "(")?; self.put_expression(expr, context, true)?; write!(self.out, ")")?; } ref ty => { return Err(Error::GenericValidation(format!( "Unsupported type for As: {ty:?}" ))) } }, // has to be a named expression crate::Expression::CallResult(_) | crate::Expression::AtomicResult { .. } | crate::Expression::WorkGroupUniformLoadResult { .. } | crate::Expression::SubgroupBallotResult | crate::Expression::SubgroupOperationResult { .. } | crate::Expression::RayQueryProceedResult => { unreachable!() } crate::Expression::ArrayLength(expr) => { // Find the global to which the array belongs. let global = match context.function.expressions[expr] { crate::Expression::AccessIndex { base, .. } => { match context.function.expressions[base] { crate::Expression::GlobalVariable(handle) => handle, ref ex => { return Err(Error::GenericValidation(format!( "Expected global variable in AccessIndex, got {ex:?}" ))) } } } crate::Expression::GlobalVariable(handle) => handle, ref ex => { return Err(Error::GenericValidation(format!( "Unexpected expression in ArrayLength, got {ex:?}" ))) } }; if !is_scoped { write!(self.out, "(")?; } write!(self.out, "1 + ")?; self.put_dynamic_array_max_index(global, context)?; if !is_scoped { write!(self.out, ")")?; } } crate::Expression::RayQueryVertexPositions { .. } => { unimplemented!() } crate::Expression::RayQueryGetIntersection { query, committed: _, } => { if context.lang_version < (2, 4) { return Err(Error::UnsupportedRayTracing); } let ty = context.module.special_types.ray_intersection.unwrap(); let type_name = &self.names[&NameKey::Type(ty)]; write!(self.out, "{type_name} {{{RAY_QUERY_FUN_MAP_INTERSECTION}(")?; self.put_expression(query, context, true)?; write!(self.out, ".{RAY_QUERY_FIELD_INTERSECTION}.type)")?; let fields = [ "distance", "user_instance_id", // req Metal 2.4 "instance_id", "", // SBT offset "geometry_id", "primitive_id", "triangle_barycentric_coord", "triangle_front_facing", "", // padding "object_to_world_transform", // req Metal 2.4 "world_to_object_transform", // req Metal 2.4 ]; for field in fields { write!(self.out, ", ")?; if field.is_empty() { write!(self.out, "{{}}")?; } else { self.put_expression(query, context, true)?; write!(self.out, ".{RAY_QUERY_FIELD_INTERSECTION}.{field}")?; } } write!(self.out, "}}")?; } crate::Expression::CooperativeLoad { ref data, .. } => { if context.lang_version < (2, 3) { return Err(Error::UnsupportedCooperativeMatrix); } write!(self.out, "{COOPERATIVE_LOAD_FUNCTION}(")?; write!(self.out, "&")?; self.put_access_chain(data.pointer, context.policies.index, context)?; write!(self.out, ", ")?; self.put_expression(data.stride, context, true)?; write!(self.out, ", {})", data.row_major)?; } crate::Expression::CooperativeMultiplyAdd { a, b, c } => { if context.lang_version < (2, 3) { return Err(Error::UnsupportedCooperativeMatrix); } write!(self.out, "{COOPERATIVE_MULTIPLY_ADD_FUNCTION}(")?; self.put_expression(a, context, true)?; write!(self.out, ", ")?; self.put_expression(b, context, true)?; write!(self.out, ", ")?; self.put_expression(c, context, true)?; write!(self.out, ")")?; } } Ok(()) } /// Emits code for a binary operation, using the provided callback to emit /// the left and right operands. fn put_binop( &mut self, op: crate::BinaryOperator, left: Handle, right: Handle, context: &ExpressionContext, is_scoped: bool, put_expression: &F, ) -> BackendResult where F: Fn(&mut Self, Handle, &ExpressionContext, bool) -> BackendResult, { let op_str = back::binary_operation_str(op); if !is_scoped { write!(self.out, "(")?; } // Cast packed vector if necessary // Packed vector - matrix multiplications are not supported in MSL if op == crate::BinaryOperator::Multiply && matches!( context.resolve_type(right), &crate::TypeInner::Matrix { .. } ) { self.put_wrapped_expression_for_packed_vec3_access( left, context, false, put_expression, )?; } else { put_expression(self, left, context, false)?; } write!(self.out, " {op_str} ")?; // See comment above if op == crate::BinaryOperator::Multiply && matches!(context.resolve_type(left), &crate::TypeInner::Matrix { .. }) { self.put_wrapped_expression_for_packed_vec3_access( right, context, false, put_expression, )?; } else { put_expression(self, right, context, false)?; } if !is_scoped { write!(self.out, ")")?; } Ok(()) } /// Used by expressions like Swizzle and Binary since they need packed_vec3's to be casted to a vec3 fn put_wrapped_expression_for_packed_vec3_access( &mut self, expr_handle: Handle, context: &ExpressionContext, is_scoped: bool, put_expression: &F, ) -> BackendResult where F: Fn(&mut Self, Handle, &ExpressionContext, bool) -> BackendResult, { if let Some(scalar) = context.get_packed_vec_kind(expr_handle) { write!(self.out, "{}::{}3(", NAMESPACE, scalar.to_msl_name())?; put_expression(self, expr_handle, context, is_scoped)?; write!(self.out, ")")?; } else { put_expression(self, expr_handle, context, is_scoped)?; } Ok(()) } /// Emits code for an expression using the provided callback, wrapping the /// result in a bitcast to the type `cast_to`. fn put_bitcasted_expression( &mut self, cast_to: &crate::TypeInner, context: &ExpressionContext, put_expression: &F, ) -> BackendResult where F: Fn(&mut Self, &ExpressionContext, bool) -> BackendResult, { write!(self.out, "as_type<")?; match *cast_to { crate::TypeInner::Scalar(scalar) => put_numeric_type(&mut self.out, scalar, &[])?, crate::TypeInner::Vector { size, scalar } => { put_numeric_type(&mut self.out, scalar, &[size])? } _ => return Err(Error::UnsupportedBitCast(cast_to.clone())), }; write!(self.out, ">(")?; put_expression(self, context, true)?; write!(self.out, ")")?; Ok(()) } /// Write a `GuardedIndex` as a Metal expression. fn put_index( &mut self, index: index::GuardedIndex, context: &ExpressionContext, is_scoped: bool, ) -> BackendResult { match index { index::GuardedIndex::Expression(expr) => { self.put_expression(expr, context, is_scoped)? } index::GuardedIndex::Known(value) => write!(self.out, "{value}")?, } Ok(()) } /// Emit an index bounds check condition for `chain`, if required. /// /// `chain` is a subtree of `Access` and `AccessIndex` expressions, /// operating either on a pointer to a value, or on a value directly. If we cannot /// statically determine that all indexing operations in `chain` are within /// bounds, then write a conditional expression to check them dynamically, /// and return true. All accesses in the chain are checked by the generated /// expression. /// /// This assumes that the [`BoundsCheckPolicy`] for `chain` is [`ReadZeroSkipWrite`]. /// /// The text written is of the form: /// /// ```ignore /// {level}{prefix}uint(i) < 4 && uint(j) < 10 /// ``` /// /// where `{level}` and `{prefix}` are the arguments to this function. For [`Store`] /// statements, presumably these arguments start an indented `if` statement; for /// [`Load`] expressions, the caller is probably building up a ternary `?:` /// expression. In either case, what is written is not a complete syntactic structure /// in its own right, and the caller will have to finish it off if we return `true`. /// /// If no expression is written, return false. /// /// [`BoundsCheckPolicy`]: index::BoundsCheckPolicy /// [`ReadZeroSkipWrite`]: index::BoundsCheckPolicy::ReadZeroSkipWrite /// [`Store`]: crate::Statement::Store /// [`Load`]: crate::Expression::Load fn put_bounds_checks( &mut self, chain: Handle, context: &ExpressionContext, level: back::Level, prefix: &'static str, ) -> Result { let mut check_written = false; // Iterate over the access chain, handling each required bounds check. for item in context.bounds_check_iter(chain) { let BoundsCheck { base, index, length, } = item; if check_written { write!(self.out, " && ")?; } else { write!(self.out, "{level}{prefix}")?; check_written = true; } // Check that the index falls within bounds. Do this with a single // comparison, by casting the index to `uint` first, so that negative // indices become large positive values. write!(self.out, "uint(")?; self.put_index(index, context, true)?; self.out.write_str(") < ")?; match length { index::IndexableLength::Known(value) => write!(self.out, "{value}")?, index::IndexableLength::Dynamic => { let global = context.function.originating_global(base).ok_or_else(|| { Error::GenericValidation("Could not find originating global".into()) })?; write!(self.out, "1 + ")?; self.put_dynamic_array_max_index(global, context)? } } } Ok(check_written) } /// Write the access chain `chain`. /// /// `chain` is a subtree of [`Access`] and [`AccessIndex`] expressions, /// operating either on a pointer to a value, or on a value directly. /// /// Generate bounds checks code only if `policy` is [`Restrict`]. The /// [`ReadZeroSkipWrite`] policy requires checks before any accesses take place, so /// that must be handled in the caller. /// /// Handle the entire chain, recursing back into `put_expression` only for index /// expressions and the base expression that originates the pointer or composite value /// being accessed. This allows `put_expression` to assume that any `Access` or /// `AccessIndex` expressions it sees are the top of a chain, so it can emit /// `ReadZeroSkipWrite` checks. /// /// [`Access`]: crate::Expression::Access /// [`AccessIndex`]: crate::Expression::AccessIndex /// [`Restrict`]: crate::proc::index::BoundsCheckPolicy::Restrict /// [`ReadZeroSkipWrite`]: crate::proc::index::BoundsCheckPolicy::ReadZeroSkipWrite fn put_access_chain( &mut self, chain: Handle, policy: index::BoundsCheckPolicy, context: &ExpressionContext, ) -> BackendResult { match context.function.expressions[chain] { crate::Expression::Access { base, index } => { let mut base_ty = context.resolve_type(base); // Look through any pointers to see what we're really indexing. if let crate::TypeInner::Pointer { base, space: _ } = *base_ty { base_ty = &context.module.types[base].inner; } self.put_subscripted_access_chain( base, base_ty, index::GuardedIndex::Expression(index), policy, context, )?; } crate::Expression::AccessIndex { base, index } => { let base_resolution = &context.info[base].ty; let mut base_ty = base_resolution.inner_with(&context.module.types); let mut base_ty_handle = base_resolution.handle(); // Look through any pointers to see what we're really indexing. if let crate::TypeInner::Pointer { base, space: _ } = *base_ty { base_ty = &context.module.types[base].inner; base_ty_handle = Some(base); } // Handle structs and anything else that can use `.x` syntax here, so // `put_subscripted_access_chain` won't have to handle the absurd case of // indexing a struct with an expression. match *base_ty { crate::TypeInner::Struct { .. } => { let base_ty = base_ty_handle.unwrap(); self.put_access_chain(base, policy, context)?; let name = &self.names[&NameKey::StructMember(base_ty, index)]; write!(self.out, ".{name}")?; } crate::TypeInner::ValuePointer { .. } | crate::TypeInner::Vector { .. } => { self.put_access_chain(base, policy, context)?; // Prior to Metal v2.1 component access for packed vectors wasn't available // however array indexing is if context.get_packed_vec_kind(base).is_some() { write!(self.out, "[{index}]")?; } else { write!(self.out, ".{}", back::COMPONENTS[index as usize])?; } } _ => { self.put_subscripted_access_chain( base, base_ty, index::GuardedIndex::Known(index), policy, context, )?; } } } _ => self.put_expression(chain, context, false)?, } Ok(()) } /// Write a `[]`-style access of `base` by `index`. /// /// If `policy` is [`Restrict`], then generate code as needed to force all index /// values within bounds. /// /// The `base_ty` argument must be the type we are actually indexing, like [`Array`] or /// [`Vector`]. In other words, it's `base`'s type with any surrounding [`Pointer`] /// removed. Our callers often already have this handy. /// /// This only emits `[]` expressions; it doesn't handle struct member accesses or /// referencing vector components by name. /// /// [`Restrict`]: crate::proc::index::BoundsCheckPolicy::Restrict /// [`Array`]: crate::TypeInner::Array /// [`Vector`]: crate::TypeInner::Vector /// [`Pointer`]: crate::TypeInner::Pointer fn put_subscripted_access_chain( &mut self, base: Handle, base_ty: &crate::TypeInner, index: index::GuardedIndex, policy: index::BoundsCheckPolicy, context: &ExpressionContext, ) -> BackendResult { let accessing_wrapped_array = match *base_ty { crate::TypeInner::Array { size: crate::ArraySize::Constant(_) | crate::ArraySize::Pending(_), .. } => true, _ => false, }; let accessing_wrapped_binding_array = matches!(*base_ty, crate::TypeInner::BindingArray { .. }); self.put_access_chain(base, policy, context)?; if accessing_wrapped_array { write!(self.out, ".{WRAPPED_ARRAY_FIELD}")?; } write!(self.out, "[")?; // Decide whether this index needs to be clamped to fall within range. let restriction_needed = if policy == index::BoundsCheckPolicy::Restrict { context.access_needs_check(base, index) } else { None }; if let Some(limit) = restriction_needed { write!(self.out, "{NAMESPACE}::min(unsigned(")?; self.put_index(index, context, true)?; write!(self.out, "), ")?; match limit { index::IndexableLength::Known(limit) => { write!(self.out, "{}u", limit - 1)?; } index::IndexableLength::Dynamic => { let global = context.function.originating_global(base).ok_or_else(|| { Error::GenericValidation("Could not find originating global".into()) })?; self.put_dynamic_array_max_index(global, context)?; } } write!(self.out, ")")?; } else { self.put_index(index, context, true)?; } write!(self.out, "]")?; if accessing_wrapped_binding_array { write!(self.out, ".{WRAPPED_ARRAY_FIELD}")?; } Ok(()) } fn put_load( &mut self, pointer: Handle, context: &ExpressionContext, is_scoped: bool, ) -> BackendResult { // Since access chains never cross between address spaces, we can just // check the index bounds check policy once at the top. let policy = context.choose_bounds_check_policy(pointer); if policy == index::BoundsCheckPolicy::ReadZeroSkipWrite && self.put_bounds_checks( pointer, context, back::Level(0), if is_scoped { "" } else { "(" }, )? { write!(self.out, " ? ")?; self.put_unchecked_load(pointer, policy, context)?; write!(self.out, " : DefaultConstructible()")?; if !is_scoped { write!(self.out, ")")?; } } else { self.put_unchecked_load(pointer, policy, context)?; } Ok(()) } fn put_unchecked_load( &mut self, pointer: Handle, policy: index::BoundsCheckPolicy, context: &ExpressionContext, ) -> BackendResult { let is_atomic_pointer = context .resolve_type(pointer) .is_atomic_pointer(&context.module.types); if is_atomic_pointer { write!( self.out, "{NAMESPACE}::atomic_load_explicit({ATOMIC_REFERENCE}" )?; self.put_access_chain(pointer, policy, context)?; write!(self.out, ", {NAMESPACE}::memory_order_relaxed)")?; } else { // We don't do any dereferencing with `*` here as pointer arguments to functions // are done by `&` references and not `*` pointers. These do not need to be // dereferenced. self.put_access_chain(pointer, policy, context)?; } Ok(()) } fn put_return_value( &mut self, level: back::Level, expr_handle: Handle, result_struct: Option<&str>, context: &ExpressionContext, ) -> BackendResult { match result_struct { Some(struct_name) => { let mut has_point_size = false; let result_ty = context.function.result.as_ref().unwrap().ty; match context.module.types[result_ty].inner { crate::TypeInner::Struct { ref members, .. } => { let tmp = "_tmp"; write!(self.out, "{level}const auto {tmp} = ")?; self.put_expression(expr_handle, context, true)?; writeln!(self.out, ";")?; write!(self.out, "{level}return {struct_name} {{")?; let mut is_first = true; for (index, member) in members.iter().enumerate() { if let Some(crate::Binding::BuiltIn(crate::BuiltIn::PointSize)) = member.binding { has_point_size = true; if !context.pipeline_options.allow_and_force_point_size { continue; } } let comma = if is_first { "" } else { "," }; is_first = false; let name = &self.names[&NameKey::StructMember(result_ty, index as u32)]; // HACK: we are forcefully deduplicating the expression here // to convert from a wrapped struct to a raw array, e.g. // `float gl_ClipDistance1 [[clip_distance]] [1];`. if let crate::TypeInner::Array { size: crate::ArraySize::Constant(size), .. } = context.module.types[member.ty].inner { write!(self.out, "{comma} {{")?; for j in 0..size.get() { if j != 0 { write!(self.out, ",")?; } write!(self.out, "{tmp}.{name}.{WRAPPED_ARRAY_FIELD}[{j}]")?; } write!(self.out, "}}")?; } else { write!(self.out, "{comma} {tmp}.{name}")?; } } } _ => { write!(self.out, "{level}return {struct_name} {{ ")?; self.put_expression(expr_handle, context, true)?; } } if let FunctionOrigin::EntryPoint(ep_index) = context.origin { let stage = context.module.entry_points[ep_index as usize].stage; if context.pipeline_options.allow_and_force_point_size && stage == crate::ShaderStage::Vertex && !has_point_size { // point size was injected and comes last write!(self.out, ", 1.0")?; } } write!(self.out, " }}")?; } None => { write!(self.out, "{level}return ")?; self.put_expression(expr_handle, context, true)?; } } writeln!(self.out, ";")?; Ok(()) } /// Helper method used to find which expressions of a given function require baking /// /// # Notes /// This function overwrites the contents of `self.need_bake_expressions` fn update_expressions_to_bake( &mut self, func: &crate::Function, info: &valid::FunctionInfo, context: &ExpressionContext, ) { use crate::Expression; self.need_bake_expressions.clear(); for (expr_handle, expr) in func.expressions.iter() { // Expressions whose reference count is above the // threshold should always be stored in temporaries. let expr_info = &info[expr_handle]; let min_ref_count = func.expressions[expr_handle].bake_ref_count(); if min_ref_count <= expr_info.ref_count { self.need_bake_expressions.insert(expr_handle); } else { match expr_info.ty { // force ray desc to be baked: it's used multiple times internally TypeResolution::Handle(h) if Some(h) == context.module.special_types.ray_desc => { self.need_bake_expressions.insert(expr_handle); } _ => {} } } if let Expression::Math { fun, arg, arg1, arg2, .. } = *expr { match fun { // WGSL's `dot` function works on any `vecN` type, but Metal's only // works on floating-point vectors, so we emit inline code for // integer vector `dot` calls. But that code uses each argument `N` // times, once for each component (see `put_dot_product`), so to // avoid duplicated evaluation, we must bake integer operands. // This applies both when using the polyfill (because of the duplicate // evaluation issue) and when we don't use the polyfill (because we // need them to be emitted before casting to packed chars -- see the // comment at the call to `put_casting_to_packed_chars`). crate::MathFunction::Dot4U8Packed | crate::MathFunction::Dot4I8Packed => { self.need_bake_expressions.insert(arg); self.need_bake_expressions.insert(arg1.unwrap()); } crate::MathFunction::FirstLeadingBit => { self.need_bake_expressions.insert(arg); } crate::MathFunction::Pack4xI8 | crate::MathFunction::Pack4xU8 | crate::MathFunction::Pack4xI8Clamp | crate::MathFunction::Pack4xU8Clamp | crate::MathFunction::Unpack4xI8 | crate::MathFunction::Unpack4xU8 => { // On MSL < 2.1, we emit a polyfill for these functions that uses the // argument multiple times. This is no longer necessary on MSL >= 2.1. if context.lang_version < (2, 1) { self.need_bake_expressions.insert(arg); } } crate::MathFunction::ExtractBits => { // Only argument 1 is re-used. self.need_bake_expressions.insert(arg1.unwrap()); } crate::MathFunction::InsertBits => { // Only argument 2 is re-used. self.need_bake_expressions.insert(arg2.unwrap()); } crate::MathFunction::Sign => { // WGSL's `sign` function works also on signed ints, but Metal's only // works on floating points, so we emit inline code for integer `sign` // calls. But that code uses each argument 2 times (see `put_isign`), // so to avoid duplicated evaluation, we must bake the argument. let inner = context.resolve_type(expr_handle); if inner.scalar_kind() == Some(crate::ScalarKind::Sint) { self.need_bake_expressions.insert(arg); } } _ => {} } } } } fn start_baking_expression( &mut self, handle: Handle, context: &ExpressionContext, name: &str, ) -> BackendResult { match context.info[handle].ty { TypeResolution::Handle(ty_handle) => { let ty_name = TypeContext { handle: ty_handle, gctx: context.module.to_ctx(), names: &self.names, access: crate::StorageAccess::empty(), first_time: false, }; write!(self.out, "{ty_name}")?; } TypeResolution::Value(crate::TypeInner::Scalar(scalar)) => { put_numeric_type(&mut self.out, scalar, &[])?; } TypeResolution::Value(crate::TypeInner::Vector { size, scalar }) => { put_numeric_type(&mut self.out, scalar, &[size])?; } TypeResolution::Value(crate::TypeInner::Matrix { columns, rows, scalar, }) => { put_numeric_type(&mut self.out, scalar, &[rows, columns])?; } TypeResolution::Value(crate::TypeInner::CooperativeMatrix { columns, rows, scalar, role: _, }) => { write!( self.out, "{}::simdgroup_{}{}x{}", NAMESPACE, scalar.to_msl_name(), columns as u32, rows as u32, )?; } TypeResolution::Value(ref other) => { log::warn!("Type {other:?} isn't a known local"); return Err(Error::FeatureNotImplemented("weird local type".to_string())); } } //TODO: figure out the naming scheme that wouldn't collide with user names. write!(self.out, " {name} = ")?; Ok(()) } /// Cache a clamped level of detail value, if necessary. /// /// [`ImageLoad`] accesses covered by [`BoundsCheckPolicy::Restrict`] use a /// properly clamped level of detail value both in the access itself, and /// for fetching the size of the requested MIP level, needed to clamp the /// coordinates. To avoid recomputing this clamped level of detail, we cache /// it in a temporary variable, as part of the [`Emit`] statement covering /// the [`ImageLoad`] expression. /// /// [`ImageLoad`]: crate::Expression::ImageLoad /// [`BoundsCheckPolicy::Restrict`]: index::BoundsCheckPolicy::Restrict /// [`Emit`]: crate::Statement::Emit fn put_cache_restricted_level( &mut self, load: Handle, image: Handle, mip_level: Option>, indent: back::Level, context: &StatementContext, ) -> BackendResult { // Does this image access actually require (or even permit) a // level-of-detail, and does the policy require us to restrict it? let level_of_detail = match mip_level { Some(level) => level, None => return Ok(()), }; if context.expression.policies.image_load != index::BoundsCheckPolicy::Restrict || !context.expression.image_needs_lod(image) { return Ok(()); } write!(self.out, "{}uint {} = ", indent, ClampedLod(load),)?; self.put_restricted_scalar_image_index( image, level_of_detail, "get_num_mip_levels", &context.expression, )?; writeln!(self.out, ";")?; Ok(()) } /// Convert the arguments of `Dot4{I, U}Packed` to `packed_(u?)char4`. /// /// Caches the results in temporary variables (whose names are derived from /// the original variable names). This caching avoids the need to redo the /// casting for each vector component when emitting the dot product. fn put_casting_to_packed_chars( &mut self, fun: crate::MathFunction, arg0: Handle, arg1: Handle, indent: back::Level, context: &StatementContext<'_>, ) -> Result<(), Error> { let packed_type = match fun { crate::MathFunction::Dot4I8Packed => "packed_char4", crate::MathFunction::Dot4U8Packed => "packed_uchar4", _ => unreachable!(), }; for arg in [arg0, arg1] { write!( self.out, "{indent}{packed_type} {0} = as_type<{packed_type}>(", Reinterpreted::new(packed_type, arg) )?; self.put_expression(arg, &context.expression, true)?; writeln!(self.out, ");")?; } Ok(()) } fn put_block( &mut self, level: back::Level, statements: &[crate::Statement], context: &StatementContext, ) -> BackendResult { // Add to the set in order to track the stack size. #[cfg(test)] self.put_block_stack_pointers .insert(ptr::from_ref(&level).cast()); for statement in statements { log::trace!("statement[{}] {:?}", level.0, statement); match *statement { crate::Statement::Emit(ref range) => { for handle in range.clone() { use crate::MathFunction as Mf; match context.expression.function.expressions[handle] { // `ImageLoad` expressions covered by the `Restrict` bounds check policy // may need to cache a clamped version of their level-of-detail argument. crate::Expression::ImageLoad { image, level: mip_level, .. } => { self.put_cache_restricted_level( handle, image, mip_level, level, context, )?; } // If we are going to write a `Dot4I8Packed` or `Dot4U8Packed` on Metal // 2.1+ then we introduce two intermediate variables that recast the two // arguments as packed (signed or unsigned) chars. The actual dot product // is implemented in `Self::put_expression`, and it uses both of these // intermediate variables multiple times. There's no danger that the // original arguments get modified between the definition of these // intermediate variables and the implementation of the actual dot // product since we require the inputs of `Dot4{I, U}Packed` to be baked. crate::Expression::Math { fun: fun @ (Mf::Dot4I8Packed | Mf::Dot4U8Packed), arg, arg1, .. } if context.expression.lang_version >= (2, 1) => { self.put_casting_to_packed_chars( fun, arg, arg1.unwrap(), level, context, )?; } _ => (), } let ptr_class = context.expression.resolve_type(handle).pointer_space(); let expr_name = if ptr_class.is_some() { None // don't bake pointer expressions (just yet) } else if let Some(name) = context.expression.function.named_expressions.get(&handle) { // The `crate::Function::named_expressions` table holds // expressions that should be saved in temporaries once they // are `Emit`ted. We only add them to `self.named_expressions` // when we reach the `Emit` that covers them, so that we don't // try to use their names before we've actually initialized // the temporary that holds them. // // Don't assume the names in `named_expressions` are unique, // or even valid. Use the `Namer`. Some(self.namer.call(name)) } else { // If this expression is an index that we're going to first compare // against a limit, and then actually use as an index, then we may // want to cache it in a temporary, to avoid evaluating it twice. let bake = if context.expression.guarded_indices.contains(handle) { true } else { self.need_bake_expressions.contains(&handle) }; if bake { Some(Baked(handle).to_string()) } else { None } }; if let Some(name) = expr_name { write!(self.out, "{level}")?; self.start_baking_expression(handle, &context.expression, &name)?; self.put_expression(handle, &context.expression, true)?; self.named_expressions.insert(handle, name); writeln!(self.out, ";")?; } } } crate::Statement::Block(ref block) => { if !block.is_empty() { writeln!(self.out, "{level}{{")?; self.put_block(level.next(), block, context)?; writeln!(self.out, "{level}}}")?; } } crate::Statement::If { condition, ref accept, ref reject, } => { write!(self.out, "{level}if (")?; self.put_expression(condition, &context.expression, true)?; writeln!(self.out, ") {{")?; self.put_block(level.next(), accept, context)?; if !reject.is_empty() { writeln!(self.out, "{level}}} else {{")?; self.put_block(level.next(), reject, context)?; } writeln!(self.out, "{level}}}")?; } crate::Statement::Switch { selector, ref cases, } => { write!(self.out, "{level}switch(")?; self.put_expression(selector, &context.expression, true)?; writeln!(self.out, ") {{")?; let lcase = level.next(); for case in cases.iter() { match case.value { crate::SwitchValue::I32(value) => { write!(self.out, "{lcase}case {value}:")?; } crate::SwitchValue::U32(value) => { write!(self.out, "{lcase}case {value}u:")?; } crate::SwitchValue::Default => { write!(self.out, "{lcase}default:")?; } } let write_block_braces = !(case.fall_through && case.body.is_empty()); if write_block_braces { writeln!(self.out, " {{")?; } else { writeln!(self.out)?; } self.put_block(lcase.next(), &case.body, context)?; if !case.fall_through && case.body.last().is_none_or(|s| !s.is_terminator()) { writeln!(self.out, "{}break;", lcase.next())?; } if write_block_braces { writeln!(self.out, "{lcase}}}")?; } } writeln!(self.out, "{level}}}")?; } crate::Statement::Loop { ref body, ref continuing, break_if, } => { let force_loop_bound_statements = self.gen_force_bounded_loop_statements(level, context); let gate_name = (!continuing.is_empty() || break_if.is_some()) .then(|| self.namer.call("loop_init")); if let Some((ref decl, _)) = force_loop_bound_statements { writeln!(self.out, "{decl}")?; } if let Some(ref gate_name) = gate_name { writeln!(self.out, "{level}bool {gate_name} = true;")?; } writeln!(self.out, "{level}while(true) {{",)?; if let Some((_, ref break_and_inc)) = force_loop_bound_statements { writeln!(self.out, "{break_and_inc}")?; } if let Some(ref gate_name) = gate_name { let lif = level.next(); let lcontinuing = lif.next(); writeln!(self.out, "{lif}if (!{gate_name}) {{")?; self.put_block(lcontinuing, continuing, context)?; if let Some(condition) = break_if { write!(self.out, "{lcontinuing}if (")?; self.put_expression(condition, &context.expression, true)?; writeln!(self.out, ") {{")?; writeln!(self.out, "{}break;", lcontinuing.next())?; writeln!(self.out, "{lcontinuing}}}")?; } writeln!(self.out, "{lif}}}")?; writeln!(self.out, "{lif}{gate_name} = false;")?; } self.put_block(level.next(), body, context)?; writeln!(self.out, "{level}}}")?; } crate::Statement::Break => { writeln!(self.out, "{level}break;")?; } crate::Statement::Continue => { writeln!(self.out, "{level}continue;")?; } crate::Statement::Return { value: Some(expr_handle), } => { self.put_return_value( level, expr_handle, context.result_struct, &context.expression, )?; } crate::Statement::Return { value: None } => { writeln!(self.out, "{level}return;")?; } crate::Statement::Kill => { writeln!(self.out, "{level}{NAMESPACE}::discard_fragment();")?; } crate::Statement::ControlBarrier(flags) | crate::Statement::MemoryBarrier(flags) => { self.write_barrier(flags, level)?; } crate::Statement::Store { pointer, value } => { self.put_store(pointer, value, level, context)? } crate::Statement::ImageStore { image, coordinate, array_index, value, } => { let address = TexelAddress { coordinate, array_index, sample: None, level: None, }; self.put_image_store(level, image, &address, value, context)? } crate::Statement::Call { function, ref arguments, result, } => { write!(self.out, "{level}")?; if let Some(expr) = result { let name = Baked(expr).to_string(); self.start_baking_expression(expr, &context.expression, &name)?; self.named_expressions.insert(expr, name); } let fun_name = &self.names[&NameKey::Function(function)]; write!(self.out, "{fun_name}(")?; // first, write down the actual arguments for (i, &handle) in arguments.iter().enumerate() { if i != 0 { write!(self.out, ", ")?; } self.put_expression(handle, &context.expression, true)?; } // follow-up with any global resources used let mut separate = !arguments.is_empty(); let fun_info = &context.expression.mod_info[function]; let mut needs_buffer_sizes = false; for (handle, var) in context.expression.module.global_variables.iter() { if fun_info[handle].is_empty() { continue; } if var.space.needs_pass_through() { let name = &self.names[&NameKey::GlobalVariable(handle)]; if separate { write!(self.out, ", ")?; } else { separate = true; } write!(self.out, "{name}")?; } needs_buffer_sizes |= needs_array_length(var.ty, &context.expression.module.types); } if needs_buffer_sizes { if separate { write!(self.out, ", ")?; } write!(self.out, "_buffer_sizes")?; } // done writeln!(self.out, ");")?; } crate::Statement::Atomic { pointer, ref fun, value, result, } => { let context = &context.expression; // This backend supports `SHADER_INT64_ATOMIC_MIN_MAX` but not // `SHADER_INT64_ATOMIC_ALL_OPS`, so we can assume that if `result` is // `Some`, we are not operating on a 64-bit value, and that if we are // operating on a 64-bit value, `result` is `None`. write!(self.out, "{level}")?; let fun_key = if let Some(result) = result { let res_name = Baked(result).to_string(); self.start_baking_expression(result, context, &res_name)?; self.named_expressions.insert(result, res_name); fun.to_msl() } else if context.resolve_type(value).scalar_width() == Some(8) { fun.to_msl_64_bit()? } else { fun.to_msl() }; // If the pointer we're passing to the atomic operation needs to be conditional // for `ReadZeroSkipWrite`, the condition needs to *surround* the atomic op, and // the pointer operand should be unchecked. let policy = context.choose_bounds_check_policy(pointer); let checked = policy == index::BoundsCheckPolicy::ReadZeroSkipWrite && self.put_bounds_checks(pointer, context, back::Level(0), "")?; // If requested and successfully put bounds checks, continue the ternary expression. if checked { write!(self.out, " ? ")?; } // Put the atomic function invocation. match *fun { crate::AtomicFunction::Exchange { compare: Some(cmp) } => { write!(self.out, "{ATOMIC_COMP_EXCH_FUNCTION}({ATOMIC_REFERENCE}")?; self.put_access_chain(pointer, policy, context)?; write!(self.out, ", ")?; self.put_expression(cmp, context, true)?; write!(self.out, ", ")?; self.put_expression(value, context, true)?; write!(self.out, ")")?; } _ => { write!( self.out, "{NAMESPACE}::atomic_{fun_key}_explicit({ATOMIC_REFERENCE}" )?; self.put_access_chain(pointer, policy, context)?; write!(self.out, ", ")?; self.put_expression(value, context, true)?; write!(self.out, ", {NAMESPACE}::memory_order_relaxed)")?; } } // Finish the ternary expression. if checked { write!(self.out, " : DefaultConstructible()")?; } // Done writeln!(self.out, ";")?; } crate::Statement::ImageAtomic { image, coordinate, array_index, fun, value, } => { let address = TexelAddress { coordinate, array_index, sample: None, level: None, }; self.put_image_atomic(level, image, &address, fun, value, context)? } crate::Statement::WorkGroupUniformLoad { pointer, result } => { self.write_barrier(crate::Barrier::WORK_GROUP, level)?; write!(self.out, "{level}")?; let name = self.namer.call(""); self.start_baking_expression(result, &context.expression, &name)?; self.put_load(pointer, &context.expression, true)?; self.named_expressions.insert(result, name); writeln!(self.out, ";")?; self.write_barrier(crate::Barrier::WORK_GROUP, level)?; } crate::Statement::RayQuery { query, ref fun } => { if context.expression.lang_version < (2, 4) { return Err(Error::UnsupportedRayTracing); } match *fun { crate::RayQueryFunction::Initialize { acceleration_structure, descriptor, } => { //TODO: how to deal with winding? write!(self.out, "{level}")?; self.put_expression(query, &context.expression, true)?; writeln!(self.out, ".{RAY_QUERY_FIELD_INTERSECTOR}.assume_geometry_type({RT_NAMESPACE}::geometry_type::triangle);")?; { let f_opaque = back::RayFlag::CULL_OPAQUE.bits(); let f_no_opaque = back::RayFlag::CULL_NO_OPAQUE.bits(); write!(self.out, "{level}")?; self.put_expression(query, &context.expression, true)?; write!( self.out, ".{RAY_QUERY_FIELD_INTERSECTOR}.set_opacity_cull_mode((" )?; self.put_expression(descriptor, &context.expression, true)?; write!(self.out, ".flags & {f_opaque}) != 0 ? {RT_NAMESPACE}::opacity_cull_mode::opaque : (")?; self.put_expression(descriptor, &context.expression, true)?; write!(self.out, ".flags & {f_no_opaque}) != 0 ? {RT_NAMESPACE}::opacity_cull_mode::non_opaque : ")?; writeln!(self.out, "{RT_NAMESPACE}::opacity_cull_mode::none);")?; } { let f_opaque = back::RayFlag::OPAQUE.bits(); let f_no_opaque = back::RayFlag::NO_OPAQUE.bits(); write!(self.out, "{level}")?; self.put_expression(query, &context.expression, true)?; write!(self.out, ".{RAY_QUERY_FIELD_INTERSECTOR}.force_opacity((")?; self.put_expression(descriptor, &context.expression, true)?; write!(self.out, ".flags & {f_opaque}) != 0 ? {RT_NAMESPACE}::forced_opacity::opaque : (")?; self.put_expression(descriptor, &context.expression, true)?; write!(self.out, ".flags & {f_no_opaque}) != 0 ? {RT_NAMESPACE}::forced_opacity::non_opaque : ")?; writeln!(self.out, "{RT_NAMESPACE}::forced_opacity::none);")?; } { let flag = back::RayFlag::TERMINATE_ON_FIRST_HIT.bits(); write!(self.out, "{level}")?; self.put_expression(query, &context.expression, true)?; write!( self.out, ".{RAY_QUERY_FIELD_INTERSECTOR}.accept_any_intersection((" )?; self.put_expression(descriptor, &context.expression, true)?; writeln!(self.out, ".flags & {flag}) != 0);")?; } write!(self.out, "{level}")?; self.put_expression(query, &context.expression, true)?; write!(self.out, ".{RAY_QUERY_FIELD_INTERSECTION} = ")?; self.put_expression(query, &context.expression, true)?; write!( self.out, ".{RAY_QUERY_FIELD_INTERSECTOR}.intersect({RT_NAMESPACE}::ray(" )?; self.put_expression(descriptor, &context.expression, true)?; write!(self.out, ".origin, ")?; self.put_expression(descriptor, &context.expression, true)?; write!(self.out, ".dir, ")?; self.put_expression(descriptor, &context.expression, true)?; write!(self.out, ".tmin, ")?; self.put_expression(descriptor, &context.expression, true)?; write!(self.out, ".tmax), ")?; self.put_expression(acceleration_structure, &context.expression, true)?; write!(self.out, ", ")?; self.put_expression(descriptor, &context.expression, true)?; write!(self.out, ".cull_mask);")?; write!(self.out, "{level}")?; self.put_expression(query, &context.expression, true)?; writeln!(self.out, ".{RAY_QUERY_FIELD_READY} = true;")?; } crate::RayQueryFunction::Proceed { result } => { write!(self.out, "{level}")?; let name = Baked(result).to_string(); self.start_baking_expression(result, &context.expression, &name)?; self.named_expressions.insert(result, name); self.put_expression(query, &context.expression, true)?; writeln!(self.out, ".{RAY_QUERY_FIELD_READY};")?; if RAY_QUERY_MODERN_SUPPORT { write!(self.out, "{level}")?; self.put_expression(query, &context.expression, true)?; writeln!(self.out, ".?.next();")?; } } crate::RayQueryFunction::GenerateIntersection { hit_t } => { if RAY_QUERY_MODERN_SUPPORT { write!(self.out, "{level}")?; self.put_expression(query, &context.expression, true)?; write!(self.out, ".?.commit_bounding_box_intersection(")?; self.put_expression(hit_t, &context.expression, true)?; writeln!(self.out, ");")?; } else { log::warn!("Ray Query GenerateIntersection is not yet supported"); } } crate::RayQueryFunction::ConfirmIntersection => { if RAY_QUERY_MODERN_SUPPORT { write!(self.out, "{level}")?; self.put_expression(query, &context.expression, true)?; writeln!(self.out, ".?.commit_triangle_intersection();")?; } else { log::warn!("Ray Query ConfirmIntersection is not yet supported"); } } crate::RayQueryFunction::Terminate => { if RAY_QUERY_MODERN_SUPPORT { write!(self.out, "{level}")?; self.put_expression(query, &context.expression, true)?; writeln!(self.out, ".?.abort();")?; } write!(self.out, "{level}")?; self.put_expression(query, &context.expression, true)?; writeln!(self.out, ".{RAY_QUERY_FIELD_READY} = false;")?; } } } crate::Statement::SubgroupBallot { result, predicate } => { write!(self.out, "{level}")?; let name = self.namer.call(""); self.start_baking_expression(result, &context.expression, &name)?; self.named_expressions.insert(result, name); write!( self.out, "{NAMESPACE}::uint4((uint64_t){NAMESPACE}::simd_ballot(" )?; if let Some(predicate) = predicate { self.put_expression(predicate, &context.expression, true)?; } else { write!(self.out, "true")?; } writeln!(self.out, "), 0, 0, 0);")?; } crate::Statement::SubgroupCollectiveOperation { op, collective_op, argument, result, } => { write!(self.out, "{level}")?; let name = self.namer.call(""); self.start_baking_expression(result, &context.expression, &name)?; self.named_expressions.insert(result, name); match (collective_op, op) { (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::All) => { write!(self.out, "{NAMESPACE}::simd_all(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Any) => { write!(self.out, "{NAMESPACE}::simd_any(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Add) => { write!(self.out, "{NAMESPACE}::simd_sum(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Mul) => { write!(self.out, "{NAMESPACE}::simd_product(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Max) => { write!(self.out, "{NAMESPACE}::simd_max(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Min) => { write!(self.out, "{NAMESPACE}::simd_min(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::And) => { write!(self.out, "{NAMESPACE}::simd_and(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Or) => { write!(self.out, "{NAMESPACE}::simd_or(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Xor) => { write!(self.out, "{NAMESPACE}::simd_xor(")? } ( crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Add, ) => write!(self.out, "{NAMESPACE}::simd_prefix_exclusive_sum(")?, ( crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Mul, ) => write!(self.out, "{NAMESPACE}::simd_prefix_exclusive_product(")?, ( crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Add, ) => write!(self.out, "{NAMESPACE}::simd_prefix_inclusive_sum(")?, ( crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Mul, ) => write!(self.out, "{NAMESPACE}::simd_prefix_inclusive_product(")?, _ => unimplemented!(), } self.put_expression(argument, &context.expression, true)?; writeln!(self.out, ");")?; } crate::Statement::SubgroupGather { mode, argument, result, } => { write!(self.out, "{level}")?; let name = self.namer.call(""); self.start_baking_expression(result, &context.expression, &name)?; self.named_expressions.insert(result, name); match mode { crate::GatherMode::BroadcastFirst => { write!(self.out, "{NAMESPACE}::simd_broadcast_first(")?; } crate::GatherMode::Broadcast(_) => { write!(self.out, "{NAMESPACE}::simd_broadcast(")?; } crate::GatherMode::Shuffle(_) => { write!(self.out, "{NAMESPACE}::simd_shuffle(")?; } crate::GatherMode::ShuffleDown(_) => { write!(self.out, "{NAMESPACE}::simd_shuffle_down(")?; } crate::GatherMode::ShuffleUp(_) => { write!(self.out, "{NAMESPACE}::simd_shuffle_up(")?; } crate::GatherMode::ShuffleXor(_) => { write!(self.out, "{NAMESPACE}::simd_shuffle_xor(")?; } crate::GatherMode::QuadBroadcast(_) => { write!(self.out, "{NAMESPACE}::quad_broadcast(")?; } crate::GatherMode::QuadSwap(_) => { write!(self.out, "{NAMESPACE}::quad_shuffle_xor(")?; } } self.put_expression(argument, &context.expression, true)?; match mode { crate::GatherMode::BroadcastFirst => {} crate::GatherMode::Broadcast(index) | crate::GatherMode::Shuffle(index) | crate::GatherMode::ShuffleDown(index) | crate::GatherMode::ShuffleUp(index) | crate::GatherMode::ShuffleXor(index) | crate::GatherMode::QuadBroadcast(index) => { write!(self.out, ", ")?; self.put_expression(index, &context.expression, true)?; } crate::GatherMode::QuadSwap(direction) => { write!(self.out, ", ")?; match direction { crate::Direction::X => { write!(self.out, "1u")?; } crate::Direction::Y => { write!(self.out, "2u")?; } crate::Direction::Diagonal => { write!(self.out, "3u")?; } } } } writeln!(self.out, ");")?; } crate::Statement::CooperativeStore { target, ref data } => { write!(self.out, "{level}simdgroup_store(")?; self.put_expression(target, &context.expression, true)?; write!(self.out, ", &")?; self.put_access_chain( data.pointer, context.expression.policies.index, &context.expression, )?; write!(self.out, ", ")?; self.put_expression(data.stride, &context.expression, true)?; if data.row_major { let matrix_origin = "0"; let transpose = true; write!(self.out, ", {matrix_origin}, {transpose}")?; } writeln!(self.out, ");")?; } crate::Statement::RayPipelineFunction(_) => unreachable!(), } } // un-emit expressions //TODO: take care of loop/continuing? for statement in statements { if let crate::Statement::Emit(ref range) = *statement { for handle in range.clone() { self.named_expressions.shift_remove(&handle); } } } Ok(()) } fn put_store( &mut self, pointer: Handle, value: Handle, level: back::Level, context: &StatementContext, ) -> BackendResult { let policy = context.expression.choose_bounds_check_policy(pointer); if policy == index::BoundsCheckPolicy::ReadZeroSkipWrite && self.put_bounds_checks(pointer, &context.expression, level, "if (")? { writeln!(self.out, ") {{")?; self.put_unchecked_store(pointer, value, policy, level.next(), context)?; writeln!(self.out, "{level}}}")?; } else { self.put_unchecked_store(pointer, value, policy, level, context)?; } Ok(()) } fn put_unchecked_store( &mut self, pointer: Handle, value: Handle, policy: index::BoundsCheckPolicy, level: back::Level, context: &StatementContext, ) -> BackendResult { let is_atomic_pointer = context .expression .resolve_type(pointer) .is_atomic_pointer(&context.expression.module.types); if is_atomic_pointer { write!( self.out, "{level}{NAMESPACE}::atomic_store_explicit({ATOMIC_REFERENCE}" )?; self.put_access_chain(pointer, policy, &context.expression)?; write!(self.out, ", ")?; self.put_expression(value, &context.expression, true)?; writeln!(self.out, ", {NAMESPACE}::memory_order_relaxed);")?; } else { write!(self.out, "{level}")?; self.put_access_chain(pointer, policy, &context.expression)?; write!(self.out, " = ")?; self.put_expression(value, &context.expression, true)?; writeln!(self.out, ";")?; } Ok(()) } pub fn write( &mut self, module: &crate::Module, info: &valid::ModuleInfo, options: &Options, pipeline_options: &PipelineOptions, ) -> Result { self.names.clear(); self.namer.reset( module, &super::keywords::RESERVED_SET, proc::KeywordSet::empty(), proc::CaseInsensitiveKeywordSet::empty(), &[CLAMPED_LOD_LOAD_PREFIX], &mut self.names, ); self.wrapped_functions.clear(); self.struct_member_pads.clear(); writeln!( self.out, "// language: metal{}.{}", options.lang_version.0, options.lang_version.1 )?; writeln!(self.out, "#include ")?; writeln!(self.out, "#include ")?; writeln!(self.out)?; // Work around Metal bug where `uint` is not available by default writeln!(self.out, "using {NAMESPACE}::uint;")?; let mut uses_ray_query = false; for (_, ty) in module.types.iter() { match ty.inner { crate::TypeInner::AccelerationStructure { .. } => { if options.lang_version < (2, 4) { return Err(Error::UnsupportedRayTracing); } } crate::TypeInner::RayQuery { .. } => { if options.lang_version < (2, 4) { return Err(Error::UnsupportedRayTracing); } uses_ray_query = true; } _ => (), } } if module.special_types.ray_desc.is_some() || module.special_types.ray_intersection.is_some() { if options.lang_version < (2, 4) { return Err(Error::UnsupportedRayTracing); } } if uses_ray_query { self.put_ray_query_type()?; } if options .bounds_check_policies .contains(index::BoundsCheckPolicy::ReadZeroSkipWrite) { self.put_default_constructible()?; } writeln!(self.out)?; { // Make a `Vec` of all the `GlobalVariable`s that contain // runtime-sized arrays. let globals: Vec> = module .global_variables .iter() .filter(|&(_, var)| needs_array_length(var.ty, &module.types)) .map(|(handle, _)| handle) .collect(); let mut buffer_indices = vec![]; for vbm in &pipeline_options.vertex_buffer_mappings { buffer_indices.push(vbm.id); } if !globals.is_empty() || !buffer_indices.is_empty() { writeln!(self.out, "struct _mslBufferSizes {{")?; for global in globals { writeln!( self.out, "{}uint {};", back::INDENT, ArraySizeMember(global) )?; } for idx in buffer_indices { writeln!(self.out, "{}uint buffer_size{};", back::INDENT, idx)?; } writeln!(self.out, "}};")?; writeln!(self.out)?; } }; self.write_type_defs(module)?; self.write_global_constants(module, info)?; self.write_functions(module, info, options, pipeline_options) } /// Write the definition for the `DefaultConstructible` class. /// /// The [`ReadZeroSkipWrite`] bounds check policy requires us to be able to /// produce 'zero' values for any type, including structs, arrays, and so /// on. We could do this by emitting default constructor applications, but /// that would entail printing the name of the type, which is more trouble /// than you'd think. Instead, we just construct this magic C++14 class that /// can be converted to any type that can be default constructed, using /// template parameter inference to detect which type is needed, so we don't /// have to figure out the name. /// /// [`ReadZeroSkipWrite`]: index::BoundsCheckPolicy::ReadZeroSkipWrite fn put_default_constructible(&mut self) -> BackendResult { let tab = back::INDENT; writeln!(self.out, "struct DefaultConstructible {{")?; writeln!(self.out, "{tab}template")?; writeln!(self.out, "{tab}operator T() && {{")?; writeln!(self.out, "{tab}{tab}return T {{}};")?; writeln!(self.out, "{tab}}}")?; writeln!(self.out, "}};")?; Ok(()) } fn put_ray_query_type(&mut self) -> BackendResult { let tab = back::INDENT; writeln!(self.out, "struct {RAY_QUERY_TYPE} {{")?; let full_type = format!("{RT_NAMESPACE}::intersector<{RT_NAMESPACE}::instancing, {RT_NAMESPACE}::triangle_data, {RT_NAMESPACE}::world_space_data>"); writeln!(self.out, "{tab}{full_type} {RAY_QUERY_FIELD_INTERSECTOR};")?; writeln!( self.out, "{tab}{full_type}::result_type {RAY_QUERY_FIELD_INTERSECTION};" )?; writeln!(self.out, "{tab}bool {RAY_QUERY_FIELD_READY} = false;")?; writeln!(self.out, "}};")?; writeln!(self.out, "constexpr {NAMESPACE}::uint {RAY_QUERY_FUN_MAP_INTERSECTION}(const {RT_NAMESPACE}::intersection_type ty) {{")?; let v_triangle = back::RayIntersectionType::Triangle as u32; let v_bbox = back::RayIntersectionType::BoundingBox as u32; writeln!( self.out, "{tab}return ty=={RT_NAMESPACE}::intersection_type::triangle ? {v_triangle} : " )?; writeln!( self.out, "{tab}{tab}ty=={RT_NAMESPACE}::intersection_type::bounding_box ? {v_bbox} : 0;" )?; writeln!(self.out, "}}")?; Ok(()) } fn write_type_defs(&mut self, module: &crate::Module) -> BackendResult { let mut generated_argument_buffer_wrapper = false; let mut generated_external_texture_wrapper = false; for (handle, ty) in module.types.iter() { match ty.inner { crate::TypeInner::BindingArray { .. } if !generated_argument_buffer_wrapper => { writeln!(self.out, "template ")?; writeln!(self.out, "struct {ARGUMENT_BUFFER_WRAPPER_STRUCT} {{")?; writeln!(self.out, "{}T {WRAPPED_ARRAY_FIELD};", back::INDENT)?; writeln!(self.out, "}};")?; generated_argument_buffer_wrapper = true; } crate::TypeInner::Image { class: crate::ImageClass::External, .. } if !generated_external_texture_wrapper => { let params_ty_name = &self.names [&NameKey::Type(module.special_types.external_texture_params.unwrap())]; writeln!(self.out, "struct {EXTERNAL_TEXTURE_WRAPPER_STRUCT} {{")?; writeln!( self.out, "{}{NAMESPACE}::texture2d plane0;", back::INDENT )?; writeln!( self.out, "{}{NAMESPACE}::texture2d plane1;", back::INDENT )?; writeln!( self.out, "{}{NAMESPACE}::texture2d plane2;", back::INDENT )?; writeln!(self.out, "{}{params_ty_name} params;", back::INDENT)?; writeln!(self.out, "}};")?; generated_external_texture_wrapper = true; } _ => {} } if !ty.needs_alias() { continue; } let name = &self.names[&NameKey::Type(handle)]; match ty.inner { // Naga IR can pass around arrays by value, but Metal, following // C++, performs an array-to-pointer conversion (C++ [conv.array]) // on expressions of array type, so assigning the array by value // isn't possible. However, Metal *does* assign structs by // value. So in our Metal output, we wrap all array types in // synthetic struct types: // // struct type1 { // float inner[10] // }; // // Then we carefully include `.inner` (`WRAPPED_ARRAY_FIELD`) in // any expression that actually wants access to the array. crate::TypeInner::Array { base, size, stride: _, } => { let base_name = TypeContext { handle: base, gctx: module.to_ctx(), names: &self.names, access: crate::StorageAccess::empty(), first_time: false, }; match size.resolve(module.to_ctx())? { proc::IndexableLength::Known(size) => { writeln!(self.out, "struct {name} {{")?; writeln!( self.out, "{}{} {}[{}];", back::INDENT, base_name, WRAPPED_ARRAY_FIELD, size )?; writeln!(self.out, "}};")?; } proc::IndexableLength::Dynamic => { writeln!(self.out, "typedef {base_name} {name}[1];")?; } } } crate::TypeInner::Struct { ref members, span, .. } => { writeln!(self.out, "struct {name} {{")?; let mut last_offset = 0; for (index, member) in members.iter().enumerate() { if member.offset > last_offset { self.struct_member_pads.insert((handle, index as u32)); let pad = member.offset - last_offset; writeln!(self.out, "{}char _pad{}[{}];", back::INDENT, index, pad)?; } let ty_inner = &module.types[member.ty].inner; last_offset = member.offset + ty_inner.size(module.to_ctx()); let member_name = &self.names[&NameKey::StructMember(handle, index as u32)]; // If the member should be packed (as is the case for a misaligned vec3) issue a packed vector match should_pack_struct_member(members, span, index, module) { Some(scalar) => { writeln!( self.out, "{}{}::packed_{}3 {};", back::INDENT, NAMESPACE, scalar.to_msl_name(), member_name )?; } None => { let base_name = TypeContext { handle: member.ty, gctx: module.to_ctx(), names: &self.names, access: crate::StorageAccess::empty(), first_time: false, }; writeln!( self.out, "{}{} {};", back::INDENT, base_name, member_name )?; // for 3-component vectors, add one component if let crate::TypeInner::Vector { size: crate::VectorSize::Tri, scalar, } = *ty_inner { last_offset += scalar.width as u32; } } } } if last_offset < span { let pad = span - last_offset; writeln!( self.out, "{}char _pad{}[{}];", back::INDENT, members.len(), pad )?; } writeln!(self.out, "}};")?; } _ => { let ty_name = TypeContext { handle, gctx: module.to_ctx(), names: &self.names, access: crate::StorageAccess::empty(), first_time: true, }; writeln!(self.out, "typedef {ty_name} {name};")?; } } } // Write functions to create special types. for (type_key, struct_ty) in module.special_types.predeclared_types.iter() { match type_key { &crate::PredeclaredType::ModfResult { size, scalar } | &crate::PredeclaredType::FrexpResult { size, scalar } => { let arg_type_name_owner; let arg_type_name = if let Some(size) = size { arg_type_name_owner = format!( "{NAMESPACE}::{}{}", if scalar.width == 8 { "double" } else { "float" }, size as u8 ); &arg_type_name_owner } else if scalar.width == 8 { "double" } else { "float" }; let other_type_name_owner; let (defined_func_name, called_func_name, other_type_name) = if matches!(type_key, &crate::PredeclaredType::ModfResult { .. }) { (MODF_FUNCTION, "modf", arg_type_name) } else { let other_type_name = if let Some(size) = size { other_type_name_owner = format!("int{}", size as u8); &other_type_name_owner } else { "int" }; (FREXP_FUNCTION, "frexp", other_type_name) }; let struct_name = &self.names[&NameKey::Type(*struct_ty)]; writeln!(self.out)?; writeln!( self.out, "{struct_name} {defined_func_name}({arg_type_name} arg) {{ {other_type_name} other; {arg_type_name} fract = {NAMESPACE}::{called_func_name}(arg, other); return {struct_name}{{ fract, other }}; }}" )?; } &crate::PredeclaredType::AtomicCompareExchangeWeakResult(scalar) => { let arg_type_name = scalar.to_msl_name(); let called_func_name = "atomic_compare_exchange_weak_explicit"; let defined_func_name = ATOMIC_COMP_EXCH_FUNCTION; let struct_name = &self.names[&NameKey::Type(*struct_ty)]; writeln!(self.out)?; for address_space_name in ["device", "threadgroup"] { writeln!( self.out, "\ template {struct_name} {defined_func_name}( {address_space_name} A *atomic_ptr, {arg_type_name} cmp, {arg_type_name} v ) {{ bool swapped = {NAMESPACE}::{called_func_name}( atomic_ptr, &cmp, v, metal::memory_order_relaxed, metal::memory_order_relaxed ); return {struct_name}{{cmp, swapped}}; }}" )?; } } } } Ok(()) } /// Writes all named constants fn write_global_constants( &mut self, module: &crate::Module, mod_info: &valid::ModuleInfo, ) -> BackendResult { let constants = module.constants.iter().filter(|&(_, c)| c.name.is_some()); for (handle, constant) in constants { let ty_name = TypeContext { handle: constant.ty, gctx: module.to_ctx(), names: &self.names, access: crate::StorageAccess::empty(), first_time: false, }; let name = &self.names[&NameKey::Constant(handle)]; write!(self.out, "constant {ty_name} {name} = ")?; self.put_const_expression(constant.init, module, mod_info, &module.global_expressions)?; writeln!(self.out, ";")?; } Ok(()) } fn put_inline_sampler_properties( &mut self, level: back::Level, sampler: &sm::InlineSampler, ) -> BackendResult { for (&letter, address) in ['s', 't', 'r'].iter().zip(sampler.address.iter()) { writeln!( self.out, "{}{}::{}_address::{},", level, NAMESPACE, letter, address.as_str(), )?; } writeln!( self.out, "{}{}::mag_filter::{},", level, NAMESPACE, sampler.mag_filter.as_str(), )?; writeln!( self.out, "{}{}::min_filter::{},", level, NAMESPACE, sampler.min_filter.as_str(), )?; if let Some(filter) = sampler.mip_filter { writeln!( self.out, "{}{}::mip_filter::{},", level, NAMESPACE, filter.as_str(), )?; } // avoid setting it on platforms that don't support it if sampler.border_color != sm::BorderColor::TransparentBlack { writeln!( self.out, "{}{}::border_color::{},", level, NAMESPACE, sampler.border_color.as_str(), )?; } //TODO: I'm not able to feed this in a way that MSL likes: //>error: use of undeclared identifier 'lod_clamp' //>error: no member named 'max_anisotropy' in namespace 'metal' if false { if let Some(ref lod) = sampler.lod_clamp { writeln!(self.out, "{}lod_clamp({},{}),", level, lod.start, lod.end,)?; } if let Some(aniso) = sampler.max_anisotropy { writeln!(self.out, "{}max_anisotropy({}),", level, aniso.get(),)?; } } if sampler.compare_func != sm::CompareFunc::Never { writeln!( self.out, "{}{}::compare_func::{},", level, NAMESPACE, sampler.compare_func.as_str(), )?; } writeln!( self.out, "{}{}::coord::{}", level, NAMESPACE, sampler.coord.as_str() )?; Ok(()) } fn write_unpacking_function( &mut self, format: back::msl::VertexFormat, ) -> Result<(String, u32, Option, crate::Scalar), Error> { use crate::{Scalar, VectorSize}; use back::msl::VertexFormat::*; match format { Uint8 => { let name = self.namer.call("unpackUint8"); writeln!(self.out, "uint {name}(metal::uchar b0) {{")?; writeln!(self.out, "{}return uint(b0);", back::INDENT)?; writeln!(self.out, "}}")?; Ok((name, 1, None, Scalar::U32)) } Uint8x2 => { let name = self.namer.call("unpackUint8x2"); writeln!( self.out, "metal::uint2 {name}(metal::uchar b0, \ metal::uchar b1) {{" )?; writeln!(self.out, "{}return metal::uint2(b0, b1);", back::INDENT)?; writeln!(self.out, "}}")?; Ok((name, 2, Some(VectorSize::Bi), Scalar::U32)) } Uint8x4 => { let name = self.namer.call("unpackUint8x4"); writeln!( self.out, "metal::uint4 {name}(metal::uchar b0, \ metal::uchar b1, \ metal::uchar b2, \ metal::uchar b3) {{" )?; writeln!( self.out, "{}return metal::uint4(b0, b1, b2, b3);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, Some(VectorSize::Quad), Scalar::U32)) } Sint8 => { let name = self.namer.call("unpackSint8"); writeln!(self.out, "int {name}(metal::uchar b0) {{")?; writeln!(self.out, "{}return int(as_type(b0));", back::INDENT)?; writeln!(self.out, "}}")?; Ok((name, 1, None, Scalar::I32)) } Sint8x2 => { let name = self.namer.call("unpackSint8x2"); writeln!( self.out, "metal::int2 {name}(metal::uchar b0, \ metal::uchar b1) {{" )?; writeln!( self.out, "{}return metal::int2(as_type(b0), \ as_type(b1));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 2, Some(VectorSize::Bi), Scalar::I32)) } Sint8x4 => { let name = self.namer.call("unpackSint8x4"); writeln!( self.out, "metal::int4 {name}(metal::uchar b0, \ metal::uchar b1, \ metal::uchar b2, \ metal::uchar b3) {{" )?; writeln!( self.out, "{}return metal::int4(as_type(b0), \ as_type(b1), \ as_type(b2), \ as_type(b3));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, Some(VectorSize::Quad), Scalar::I32)) } Unorm8 => { let name = self.namer.call("unpackUnorm8"); writeln!(self.out, "float {name}(metal::uchar b0) {{")?; writeln!( self.out, "{}return float(float(b0) / 255.0f);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 1, None, Scalar::F32)) } Unorm8x2 => { let name = self.namer.call("unpackUnorm8x2"); writeln!( self.out, "metal::float2 {name}(metal::uchar b0, \ metal::uchar b1) {{" )?; writeln!( self.out, "{}return metal::float2(float(b0) / 255.0f, \ float(b1) / 255.0f);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 2, Some(VectorSize::Bi), Scalar::F32)) } Unorm8x4 => { let name = self.namer.call("unpackUnorm8x4"); writeln!( self.out, "metal::float4 {name}(metal::uchar b0, \ metal::uchar b1, \ metal::uchar b2, \ metal::uchar b3) {{" )?; writeln!( self.out, "{}return metal::float4(float(b0) / 255.0f, \ float(b1) / 255.0f, \ float(b2) / 255.0f, \ float(b3) / 255.0f);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, Some(VectorSize::Quad), Scalar::F32)) } Snorm8 => { let name = self.namer.call("unpackSnorm8"); writeln!(self.out, "float {name}(metal::uchar b0) {{")?; writeln!( self.out, "{}return float(metal::max(-1.0f, as_type(b0) / 127.0f));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 1, None, Scalar::F32)) } Snorm8x2 => { let name = self.namer.call("unpackSnorm8x2"); writeln!( self.out, "metal::float2 {name}(metal::uchar b0, \ metal::uchar b1) {{" )?; writeln!( self.out, "{}return metal::float2(metal::max(-1.0f, as_type(b0) / 127.0f), \ metal::max(-1.0f, as_type(b1) / 127.0f));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 2, Some(VectorSize::Bi), Scalar::F32)) } Snorm8x4 => { let name = self.namer.call("unpackSnorm8x4"); writeln!( self.out, "metal::float4 {name}(metal::uchar b0, \ metal::uchar b1, \ metal::uchar b2, \ metal::uchar b3) {{" )?; writeln!( self.out, "{}return metal::float4(metal::max(-1.0f, as_type(b0) / 127.0f), \ metal::max(-1.0f, as_type(b1) / 127.0f), \ metal::max(-1.0f, as_type(b2) / 127.0f), \ metal::max(-1.0f, as_type(b3) / 127.0f));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, Some(VectorSize::Quad), Scalar::F32)) } Uint16 => { let name = self.namer.call("unpackUint16"); writeln!( self.out, "metal::uint {name}(metal::uint b0, \ metal::uint b1) {{" )?; writeln!( self.out, "{}return metal::uint(b1 << 8 | b0);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 2, None, Scalar::U32)) } Uint16x2 => { let name = self.namer.call("unpackUint16x2"); writeln!( self.out, "metal::uint2 {name}(metal::uint b0, \ metal::uint b1, \ metal::uint b2, \ metal::uint b3) {{" )?; writeln!( self.out, "{}return metal::uint2(b1 << 8 | b0, \ b3 << 8 | b2);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, Some(VectorSize::Bi), Scalar::U32)) } Uint16x4 => { let name = self.namer.call("unpackUint16x4"); writeln!( self.out, "metal::uint4 {name}(metal::uint b0, \ metal::uint b1, \ metal::uint b2, \ metal::uint b3, \ metal::uint b4, \ metal::uint b5, \ metal::uint b6, \ metal::uint b7) {{" )?; writeln!( self.out, "{}return metal::uint4(b1 << 8 | b0, \ b3 << 8 | b2, \ b5 << 8 | b4, \ b7 << 8 | b6);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 8, Some(VectorSize::Quad), Scalar::U32)) } Sint16 => { let name = self.namer.call("unpackSint16"); writeln!( self.out, "int {name}(metal::ushort b0, \ metal::ushort b1) {{" )?; writeln!( self.out, "{}return int(as_type(metal::ushort(b1 << 8 | b0)));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 2, None, Scalar::I32)) } Sint16x2 => { let name = self.namer.call("unpackSint16x2"); writeln!( self.out, "metal::int2 {name}(metal::ushort b0, \ metal::ushort b1, \ metal::ushort b2, \ metal::ushort b3) {{" )?; writeln!( self.out, "{}return metal::int2(as_type(metal::ushort(b1 << 8 | b0)), \ as_type(metal::ushort(b3 << 8 | b2)));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, Some(VectorSize::Bi), Scalar::I32)) } Sint16x4 => { let name = self.namer.call("unpackSint16x4"); writeln!( self.out, "metal::int4 {name}(metal::ushort b0, \ metal::ushort b1, \ metal::ushort b2, \ metal::ushort b3, \ metal::ushort b4, \ metal::ushort b5, \ metal::ushort b6, \ metal::ushort b7) {{" )?; writeln!( self.out, "{}return metal::int4(as_type(metal::ushort(b1 << 8 | b0)), \ as_type(metal::ushort(b3 << 8 | b2)), \ as_type(metal::ushort(b5 << 8 | b4)), \ as_type(metal::ushort(b7 << 8 | b6)));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 8, Some(VectorSize::Quad), Scalar::I32)) } Unorm16 => { let name = self.namer.call("unpackUnorm16"); writeln!( self.out, "float {name}(metal::ushort b0, \ metal::ushort b1) {{" )?; writeln!( self.out, "{}return float(float(b1 << 8 | b0) / 65535.0f);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 2, None, Scalar::F32)) } Unorm16x2 => { let name = self.namer.call("unpackUnorm16x2"); writeln!( self.out, "metal::float2 {name}(metal::ushort b0, \ metal::ushort b1, \ metal::ushort b2, \ metal::ushort b3) {{" )?; writeln!( self.out, "{}return metal::float2(float(b1 << 8 | b0) / 65535.0f, \ float(b3 << 8 | b2) / 65535.0f);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, Some(VectorSize::Bi), Scalar::F32)) } Unorm16x4 => { let name = self.namer.call("unpackUnorm16x4"); writeln!( self.out, "metal::float4 {name}(metal::ushort b0, \ metal::ushort b1, \ metal::ushort b2, \ metal::ushort b3, \ metal::ushort b4, \ metal::ushort b5, \ metal::ushort b6, \ metal::ushort b7) {{" )?; writeln!( self.out, "{}return metal::float4(float(b1 << 8 | b0) / 65535.0f, \ float(b3 << 8 | b2) / 65535.0f, \ float(b5 << 8 | b4) / 65535.0f, \ float(b7 << 8 | b6) / 65535.0f);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 8, Some(VectorSize::Quad), Scalar::F32)) } Snorm16 => { let name = self.namer.call("unpackSnorm16"); writeln!( self.out, "float {name}(metal::ushort b0, \ metal::ushort b1) {{" )?; writeln!( self.out, "{}return metal::unpack_snorm2x16_to_float(b1 << 8 | b0).x;", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 2, None, Scalar::F32)) } Snorm16x2 => { let name = self.namer.call("unpackSnorm16x2"); writeln!( self.out, "metal::float2 {name}(uint b0, \ uint b1, \ uint b2, \ uint b3) {{" )?; writeln!( self.out, "{}return metal::unpack_snorm2x16_to_float(b3 << 24 | b2 << 16 | b1 << 8 | b0);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, Some(VectorSize::Bi), Scalar::F32)) } Snorm16x4 => { let name = self.namer.call("unpackSnorm16x4"); writeln!( self.out, "metal::float4 {name}(uint b0, \ uint b1, \ uint b2, \ uint b3, \ uint b4, \ uint b5, \ uint b6, \ uint b7) {{" )?; writeln!( self.out, "{}return metal::float4(metal::unpack_snorm2x16_to_float(b3 << 24 | b2 << 16 | b1 << 8 | b0), \ metal::unpack_snorm2x16_to_float(b7 << 24 | b6 << 16 | b5 << 8 | b4));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 8, Some(VectorSize::Quad), Scalar::F32)) } Float16 => { let name = self.namer.call("unpackFloat16"); writeln!( self.out, "float {name}(metal::ushort b0, \ metal::ushort b1) {{" )?; writeln!( self.out, "{}return float(as_type(metal::ushort(b1 << 8 | b0)));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 2, None, Scalar::F32)) } Float16x2 => { let name = self.namer.call("unpackFloat16x2"); writeln!( self.out, "metal::float2 {name}(metal::ushort b0, \ metal::ushort b1, \ metal::ushort b2, \ metal::ushort b3) {{" )?; writeln!( self.out, "{}return metal::float2(as_type(metal::ushort(b1 << 8 | b0)), \ as_type(metal::ushort(b3 << 8 | b2)));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, Some(VectorSize::Bi), Scalar::F32)) } Float16x4 => { let name = self.namer.call("unpackFloat16x4"); writeln!( self.out, "metal::float4 {name}(metal::ushort b0, \ metal::ushort b1, \ metal::ushort b2, \ metal::ushort b3, \ metal::ushort b4, \ metal::ushort b5, \ metal::ushort b6, \ metal::ushort b7) {{" )?; writeln!( self.out, "{}return metal::float4(as_type(metal::ushort(b1 << 8 | b0)), \ as_type(metal::ushort(b3 << 8 | b2)), \ as_type(metal::ushort(b5 << 8 | b4)), \ as_type(metal::ushort(b7 << 8 | b6)));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 8, Some(VectorSize::Quad), Scalar::F32)) } Float32 => { let name = self.namer.call("unpackFloat32"); writeln!( self.out, "float {name}(uint b0, \ uint b1, \ uint b2, \ uint b3) {{" )?; writeln!( self.out, "{}return as_type(b3 << 24 | b2 << 16 | b1 << 8 | b0);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, None, Scalar::F32)) } Float32x2 => { let name = self.namer.call("unpackFloat32x2"); writeln!( self.out, "metal::float2 {name}(uint b0, \ uint b1, \ uint b2, \ uint b3, \ uint b4, \ uint b5, \ uint b6, \ uint b7) {{" )?; writeln!( self.out, "{}return metal::float2(as_type(b3 << 24 | b2 << 16 | b1 << 8 | b0), \ as_type(b7 << 24 | b6 << 16 | b5 << 8 | b4));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 8, Some(VectorSize::Bi), Scalar::F32)) } Float32x3 => { let name = self.namer.call("unpackFloat32x3"); writeln!( self.out, "metal::float3 {name}(uint b0, \ uint b1, \ uint b2, \ uint b3, \ uint b4, \ uint b5, \ uint b6, \ uint b7, \ uint b8, \ uint b9, \ uint b10, \ uint b11) {{" )?; writeln!( self.out, "{}return metal::float3(as_type(b3 << 24 | b2 << 16 | b1 << 8 | b0), \ as_type(b7 << 24 | b6 << 16 | b5 << 8 | b4), \ as_type(b11 << 24 | b10 << 16 | b9 << 8 | b8));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 12, Some(VectorSize::Tri), Scalar::F32)) } Float32x4 => { let name = self.namer.call("unpackFloat32x4"); writeln!( self.out, "metal::float4 {name}(uint b0, \ uint b1, \ uint b2, \ uint b3, \ uint b4, \ uint b5, \ uint b6, \ uint b7, \ uint b8, \ uint b9, \ uint b10, \ uint b11, \ uint b12, \ uint b13, \ uint b14, \ uint b15) {{" )?; writeln!( self.out, "{}return metal::float4(as_type(b3 << 24 | b2 << 16 | b1 << 8 | b0), \ as_type(b7 << 24 | b6 << 16 | b5 << 8 | b4), \ as_type(b11 << 24 | b10 << 16 | b9 << 8 | b8), \ as_type(b15 << 24 | b14 << 16 | b13 << 8 | b12));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 16, Some(VectorSize::Quad), Scalar::F32)) } Uint32 => { let name = self.namer.call("unpackUint32"); writeln!( self.out, "uint {name}(uint b0, \ uint b1, \ uint b2, \ uint b3) {{" )?; writeln!( self.out, "{}return (b3 << 24 | b2 << 16 | b1 << 8 | b0);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, None, Scalar::U32)) } Uint32x2 => { let name = self.namer.call("unpackUint32x2"); writeln!( self.out, "uint2 {name}(uint b0, \ uint b1, \ uint b2, \ uint b3, \ uint b4, \ uint b5, \ uint b6, \ uint b7) {{" )?; writeln!( self.out, "{}return uint2((b3 << 24 | b2 << 16 | b1 << 8 | b0), \ (b7 << 24 | b6 << 16 | b5 << 8 | b4));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 8, Some(VectorSize::Bi), Scalar::U32)) } Uint32x3 => { let name = self.namer.call("unpackUint32x3"); writeln!( self.out, "uint3 {name}(uint b0, \ uint b1, \ uint b2, \ uint b3, \ uint b4, \ uint b5, \ uint b6, \ uint b7, \ uint b8, \ uint b9, \ uint b10, \ uint b11) {{" )?; writeln!( self.out, "{}return uint3((b3 << 24 | b2 << 16 | b1 << 8 | b0), \ (b7 << 24 | b6 << 16 | b5 << 8 | b4), \ (b11 << 24 | b10 << 16 | b9 << 8 | b8));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 12, Some(VectorSize::Tri), Scalar::U32)) } Uint32x4 => { let name = self.namer.call("unpackUint32x4"); writeln!( self.out, "{NAMESPACE}::uint4 {name}(uint b0, \ uint b1, \ uint b2, \ uint b3, \ uint b4, \ uint b5, \ uint b6, \ uint b7, \ uint b8, \ uint b9, \ uint b10, \ uint b11, \ uint b12, \ uint b13, \ uint b14, \ uint b15) {{" )?; writeln!( self.out, "{}return {NAMESPACE}::uint4((b3 << 24 | b2 << 16 | b1 << 8 | b0), \ (b7 << 24 | b6 << 16 | b5 << 8 | b4), \ (b11 << 24 | b10 << 16 | b9 << 8 | b8), \ (b15 << 24 | b14 << 16 | b13 << 8 | b12));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 16, Some(VectorSize::Quad), Scalar::U32)) } Sint32 => { let name = self.namer.call("unpackSint32"); writeln!( self.out, "int {name}(uint b0, \ uint b1, \ uint b2, \ uint b3) {{" )?; writeln!( self.out, "{}return as_type(b3 << 24 | b2 << 16 | b1 << 8 | b0);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, None, Scalar::I32)) } Sint32x2 => { let name = self.namer.call("unpackSint32x2"); writeln!( self.out, "metal::int2 {name}(uint b0, \ uint b1, \ uint b2, \ uint b3, \ uint b4, \ uint b5, \ uint b6, \ uint b7) {{" )?; writeln!( self.out, "{}return metal::int2(as_type(b3 << 24 | b2 << 16 | b1 << 8 | b0), \ as_type(b7 << 24 | b6 << 16 | b5 << 8 | b4));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 8, Some(VectorSize::Bi), Scalar::I32)) } Sint32x3 => { let name = self.namer.call("unpackSint32x3"); writeln!( self.out, "metal::int3 {name}(uint b0, \ uint b1, \ uint b2, \ uint b3, \ uint b4, \ uint b5, \ uint b6, \ uint b7, \ uint b8, \ uint b9, \ uint b10, \ uint b11) {{" )?; writeln!( self.out, "{}return metal::int3(as_type(b3 << 24 | b2 << 16 | b1 << 8 | b0), \ as_type(b7 << 24 | b6 << 16 | b5 << 8 | b4), \ as_type(b11 << 24 | b10 << 16 | b9 << 8 | b8));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 12, Some(VectorSize::Tri), Scalar::I32)) } Sint32x4 => { let name = self.namer.call("unpackSint32x4"); writeln!( self.out, "metal::int4 {name}(uint b0, \ uint b1, \ uint b2, \ uint b3, \ uint b4, \ uint b5, \ uint b6, \ uint b7, \ uint b8, \ uint b9, \ uint b10, \ uint b11, \ uint b12, \ uint b13, \ uint b14, \ uint b15) {{" )?; writeln!( self.out, "{}return metal::int4(as_type(b3 << 24 | b2 << 16 | b1 << 8 | b0), \ as_type(b7 << 24 | b6 << 16 | b5 << 8 | b4), \ as_type(b11 << 24 | b10 << 16 | b9 << 8 | b8), \ as_type(b15 << 24 | b14 << 16 | b13 << 8 | b12));", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 16, Some(VectorSize::Quad), Scalar::I32)) } Unorm10_10_10_2 => { let name = self.namer.call("unpackUnorm10_10_10_2"); writeln!( self.out, "metal::float4 {name}(uint b0, \ uint b1, \ uint b2, \ uint b3) {{" )?; writeln!( self.out, // The following is correct for RGBA packing, but our format seems to // match ABGR, which can be fed into the Metal builtin function // unpack_unorm10a2_to_float. /* "{}uint v = (b3 << 24 | b2 << 16 | b1 << 8 | b0); \ uint r = (v & 0xFFC00000) >> 22; \ uint g = (v & 0x003FF000) >> 12; \ uint b = (v & 0x00000FFC) >> 2; \ uint a = (v & 0x00000003); \ return metal::float4(float(r) / 1023.0f, float(g) / 1023.0f, float(b) / 1023.0f, float(a) / 3.0f);", */ "{}return metal::unpack_unorm10a2_to_float(b3 << 24 | b2 << 16 | b1 << 8 | b0);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, Some(VectorSize::Quad), Scalar::F32)) } Unorm8x4Bgra => { let name = self.namer.call("unpackUnorm8x4Bgra"); writeln!( self.out, "metal::float4 {name}(metal::uchar b0, \ metal::uchar b1, \ metal::uchar b2, \ metal::uchar b3) {{" )?; writeln!( self.out, "{}return metal::float4(float(b2) / 255.0f, \ float(b1) / 255.0f, \ float(b0) / 255.0f, \ float(b3) / 255.0f);", back::INDENT )?; writeln!(self.out, "}}")?; Ok((name, 4, Some(VectorSize::Quad), Scalar::F32)) } } } fn write_wrapped_unary_op( &mut self, module: &crate::Module, func_ctx: &back::FunctionCtx, op: crate::UnaryOperator, operand: Handle, ) -> BackendResult { let operand_ty = func_ctx.resolve_type(operand, &module.types); match op { // Negating the TYPE_MIN of a two's complement signed integer // type causes overflow, which is undefined behaviour in MSL. To // avoid this we bitcast the value to unsigned and negate it, // then bitcast back to signed. // This adheres to the WGSL spec in that the negative of the // type's minimum value should equal to the minimum value. crate::UnaryOperator::Negate if operand_ty.scalar_kind() == Some(crate::ScalarKind::Sint) => { let Some((vector_size, scalar)) = operand_ty.vector_size_and_scalar() else { return Ok(()); }; let wrapped = WrappedFunction::UnaryOp { op, ty: (vector_size, scalar), }; if !self.wrapped_functions.insert(wrapped) { return Ok(()); } let unsigned_scalar = crate::Scalar { kind: crate::ScalarKind::Uint, ..scalar }; let mut type_name = String::new(); let mut unsigned_type_name = String::new(); match vector_size { None => { put_numeric_type(&mut type_name, scalar, &[])?; put_numeric_type(&mut unsigned_type_name, unsigned_scalar, &[])? } Some(size) => { put_numeric_type(&mut type_name, scalar, &[size])?; put_numeric_type(&mut unsigned_type_name, unsigned_scalar, &[size])?; } }; writeln!(self.out, "{type_name} {NEG_FUNCTION}({type_name} val) {{")?; let level = back::Level(1); writeln!( self.out, "{level}return as_type<{type_name}>(-as_type<{unsigned_type_name}>(val));" )?; writeln!(self.out, "}}")?; writeln!(self.out)?; } _ => {} } Ok(()) } fn write_wrapped_binary_op( &mut self, module: &crate::Module, func_ctx: &back::FunctionCtx, expr: Handle, op: crate::BinaryOperator, left: Handle, right: Handle, ) -> BackendResult { let expr_ty = func_ctx.resolve_type(expr, &module.types); let left_ty = func_ctx.resolve_type(left, &module.types); let right_ty = func_ctx.resolve_type(right, &module.types); match (op, expr_ty.scalar_kind()) { // Signed integer division of TYPE_MIN / -1, or signed or // unsigned division by zero, gives an unspecified value in MSL. // We override the divisor to 1 in these cases. // This adheres to the WGSL spec in that: // * TYPE_MIN / -1 == TYPE_MIN // * x / 0 == x ( crate::BinaryOperator::Divide, Some(crate::ScalarKind::Sint | crate::ScalarKind::Uint), ) => { let Some(left_wrapped_ty) = left_ty.vector_size_and_scalar() else { return Ok(()); }; let Some(right_wrapped_ty) = right_ty.vector_size_and_scalar() else { return Ok(()); }; let wrapped = WrappedFunction::BinaryOp { op, left_ty: left_wrapped_ty, right_ty: right_wrapped_ty, }; if !self.wrapped_functions.insert(wrapped) { return Ok(()); } let Some((vector_size, scalar)) = expr_ty.vector_size_and_scalar() else { return Ok(()); }; let mut type_name = String::new(); match vector_size { None => put_numeric_type(&mut type_name, scalar, &[])?, Some(size) => put_numeric_type(&mut type_name, scalar, &[size])?, }; writeln!( self.out, "{type_name} {DIV_FUNCTION}({type_name} lhs, {type_name} rhs) {{" )?; let level = back::Level(1); match scalar.kind { crate::ScalarKind::Sint => { let min_val = match scalar.width { 4 => crate::Literal::I32(i32::MIN), 8 => crate::Literal::I64(i64::MIN), _ => { return Err(Error::GenericValidation(format!( "Unexpected width for scalar {scalar:?}" ))); } }; write!( self.out, "{level}return lhs / metal::select(rhs, 1, (lhs == " )?; self.put_literal(min_val)?; writeln!(self.out, " & rhs == -1) | (rhs == 0));")? } crate::ScalarKind::Uint => writeln!( self.out, "{level}return lhs / metal::select(rhs, 1u, rhs == 0u);" )?, _ => unreachable!(), } writeln!(self.out, "}}")?; writeln!(self.out)?; } // Integer modulo where one or both operands are negative, or the // divisor is zero, is undefined behaviour in MSL. To avoid this // we use the following equation: // // dividend - (dividend / divisor) * divisor // // overriding the divisor to 1 if either it is 0, or it is -1 // and the dividend is TYPE_MIN. // // This adheres to the WGSL spec in that: // * TYPE_MIN % -1 == 0 // * x % 0 == 0 ( crate::BinaryOperator::Modulo, Some(crate::ScalarKind::Sint | crate::ScalarKind::Uint), ) => { let Some(left_wrapped_ty) = left_ty.vector_size_and_scalar() else { return Ok(()); }; let Some((right_vector_size, right_scalar)) = right_ty.vector_size_and_scalar() else { return Ok(()); }; let wrapped = WrappedFunction::BinaryOp { op, left_ty: left_wrapped_ty, right_ty: (right_vector_size, right_scalar), }; if !self.wrapped_functions.insert(wrapped) { return Ok(()); } let Some((vector_size, scalar)) = expr_ty.vector_size_and_scalar() else { return Ok(()); }; let mut type_name = String::new(); match vector_size { None => put_numeric_type(&mut type_name, scalar, &[])?, Some(size) => put_numeric_type(&mut type_name, scalar, &[size])?, }; let mut rhs_type_name = String::new(); match right_vector_size { None => put_numeric_type(&mut rhs_type_name, right_scalar, &[])?, Some(size) => put_numeric_type(&mut rhs_type_name, right_scalar, &[size])?, }; writeln!( self.out, "{type_name} {MOD_FUNCTION}({type_name} lhs, {type_name} rhs) {{" )?; let level = back::Level(1); match scalar.kind { crate::ScalarKind::Sint => { let min_val = match scalar.width { 4 => crate::Literal::I32(i32::MIN), 8 => crate::Literal::I64(i64::MIN), _ => { return Err(Error::GenericValidation(format!( "Unexpected width for scalar {scalar:?}" ))); } }; write!( self.out, "{level}{rhs_type_name} divisor = metal::select(rhs, 1, (lhs == " )?; self.put_literal(min_val)?; writeln!(self.out, " & rhs == -1) | (rhs == 0));")?; writeln!(self.out, "{level}return lhs - (lhs / divisor) * divisor;")? } crate::ScalarKind::Uint => writeln!( self.out, "{level}return lhs % metal::select(rhs, 1u, rhs == 0u);" )?, _ => unreachable!(), } writeln!(self.out, "}}")?; writeln!(self.out)?; } _ => {} } Ok(()) } /// Build the mangled helper name for integer vector dot products. /// /// `scalar` must be a concrete integer scalar type. /// /// Result format: `{DOT_FUNCTION_PREFIX}_{type}{N}` (e.g., `naga_dot_int3`). fn get_dot_wrapper_function_helper_name( &self, scalar: crate::Scalar, size: crate::VectorSize, ) -> String { // Check for consistency with [`super::keywords::RESERVED_SET`] debug_assert!(concrete_int_scalars().any(|s| s == scalar)); let type_name = scalar.to_msl_name(); let size_suffix = common::vector_size_str(size); format!("{DOT_FUNCTION_PREFIX}_{type_name}{size_suffix}") } #[allow(clippy::too_many_arguments)] fn write_wrapped_math_function( &mut self, module: &crate::Module, func_ctx: &back::FunctionCtx, fun: crate::MathFunction, arg: Handle, _arg1: Option>, _arg2: Option>, _arg3: Option>, ) -> BackendResult { let arg_ty = func_ctx.resolve_type(arg, &module.types); match fun { // Taking the absolute value of the TYPE_MIN of a two's // complement signed integer type causes overflow, which is // undefined behaviour in MSL. To avoid this, when the value is // negative we bitcast the value to unsigned and negate it, then // bitcast back to signed. // This adheres to the WGSL spec in that the absolute of the // type's minimum value should equal to the minimum value. crate::MathFunction::Abs if arg_ty.scalar_kind() == Some(crate::ScalarKind::Sint) => { let Some((vector_size, scalar)) = arg_ty.vector_size_and_scalar() else { return Ok(()); }; let wrapped = WrappedFunction::Math { fun, arg_ty: (vector_size, scalar), }; if !self.wrapped_functions.insert(wrapped) { return Ok(()); } let unsigned_scalar = crate::Scalar { kind: crate::ScalarKind::Uint, ..scalar }; let mut type_name = String::new(); let mut unsigned_type_name = String::new(); match vector_size { None => { put_numeric_type(&mut type_name, scalar, &[])?; put_numeric_type(&mut unsigned_type_name, unsigned_scalar, &[])? } Some(size) => { put_numeric_type(&mut type_name, scalar, &[size])?; put_numeric_type(&mut unsigned_type_name, unsigned_scalar, &[size])?; } }; writeln!(self.out, "{type_name} {ABS_FUNCTION}({type_name} val) {{")?; let level = back::Level(1); writeln!(self.out, "{level}return metal::select(as_type<{type_name}>(-as_type<{unsigned_type_name}>(val)), val, val >= 0);")?; writeln!(self.out, "}}")?; writeln!(self.out)?; } crate::MathFunction::Dot => match *arg_ty { crate::TypeInner::Vector { size, scalar } if matches!( scalar.kind, crate::ScalarKind::Sint | crate::ScalarKind::Uint ) => { // De-duplicate per (fun, arg type) like other wrapped math functions let wrapped = WrappedFunction::Math { fun, arg_ty: (Some(size), scalar), }; if !self.wrapped_functions.insert(wrapped) { return Ok(()); } let mut vec_ty = String::new(); put_numeric_type(&mut vec_ty, scalar, &[size])?; let mut ret_ty = String::new(); put_numeric_type(&mut ret_ty, scalar, &[])?; let fun_name = self.get_dot_wrapper_function_helper_name(scalar, size); // Emit function signature and body using put_dot_product for the expression writeln!(self.out, "{ret_ty} {fun_name}({vec_ty} a, {vec_ty} b) {{")?; let level = back::Level(1); write!(self.out, "{level}return ")?; self.put_dot_product("a", "b", size as usize, |writer, name, index| { write!(writer.out, "{name}.{}", back::COMPONENTS[index])?; Ok(()) })?; writeln!(self.out, ";")?; writeln!(self.out, "}}")?; writeln!(self.out)?; } _ => {} }, _ => {} } Ok(()) } fn write_wrapped_cast( &mut self, module: &crate::Module, func_ctx: &back::FunctionCtx, expr: Handle, kind: crate::ScalarKind, convert: Option, ) -> BackendResult { // Avoid undefined behaviour when casting from a float to integer // when the value is out of range for the target type. Additionally // ensure we clamp to the correct value as per the WGSL spec. // // https://www.w3.org/TR/WGSL/#floating-point-conversion: // * If X is exactly representable in the target type T, then the // result is that value. // * Otherwise, the result is the value in T closest to // truncate(X) and also exactly representable in the original // floating point type. let src_ty = func_ctx.resolve_type(expr, &module.types); let Some(width) = convert else { return Ok(()); }; let Some((vector_size, src_scalar)) = src_ty.vector_size_and_scalar() else { return Ok(()); }; let dst_scalar = crate::Scalar { kind, width }; if src_scalar.kind != crate::ScalarKind::Float || (dst_scalar.kind != crate::ScalarKind::Sint && dst_scalar.kind != crate::ScalarKind::Uint) { return Ok(()); } let wrapped = WrappedFunction::Cast { src_scalar, vector_size, dst_scalar, }; if !self.wrapped_functions.insert(wrapped) { return Ok(()); } let (min, max) = proc::min_max_float_representable_by(src_scalar, dst_scalar); let mut src_type_name = String::new(); match vector_size { None => put_numeric_type(&mut src_type_name, src_scalar, &[])?, Some(size) => put_numeric_type(&mut src_type_name, src_scalar, &[size])?, }; let mut dst_type_name = String::new(); match vector_size { None => put_numeric_type(&mut dst_type_name, dst_scalar, &[])?, Some(size) => put_numeric_type(&mut dst_type_name, dst_scalar, &[size])?, }; let fun_name = match dst_scalar { crate::Scalar::I32 => F2I32_FUNCTION, crate::Scalar::U32 => F2U32_FUNCTION, crate::Scalar::I64 => F2I64_FUNCTION, crate::Scalar::U64 => F2U64_FUNCTION, _ => unreachable!(), }; writeln!( self.out, "{dst_type_name} {fun_name}({src_type_name} value) {{" )?; let level = back::Level(1); write!( self.out, "{level}return static_cast<{dst_type_name}>({NAMESPACE}::clamp(value, " )?; self.put_literal(min)?; write!(self.out, ", ")?; self.put_literal(max)?; writeln!(self.out, "));")?; writeln!(self.out, "}}")?; writeln!(self.out)?; Ok(()) } /// Helper function used by [`Self::write_wrapped_image_load`] and /// [`Self::write_wrapped_image_sample`] to write the shared YUV to RGB /// conversion code for external textures. Expects the preceding code to /// declare the Y component as a `float` variable of name `y`, the UV /// components as a `float2` variable of name `uv`, and the external /// texture params as a variable of name `params`. The emitted code will /// return the result. fn write_convert_yuv_to_rgb_and_return( &mut self, level: back::Level, y: &str, uv: &str, params: &str, ) -> BackendResult { let l1 = level; let l2 = l1.next(); // Convert from YUV to non-linear RGB in the source color space. writeln!( self.out, "{l1}float3 srcGammaRgb = ({params}.yuv_conversion_matrix * float4({y}, {uv}, 1.0)).rgb;" )?; // Apply the inverse of the source transfer function to convert to // linear RGB in the source color space. writeln!(self.out, "{l1}float3 srcLinearRgb = {NAMESPACE}::select(")?; writeln!(self.out, "{l2}{NAMESPACE}::pow((srcGammaRgb + {params}.src_tf.a - 1.0) / {params}.src_tf.a, {params}.src_tf.g),")?; writeln!(self.out, "{l2}srcGammaRgb / {params}.src_tf.k,")?; writeln!( self.out, "{l2}srcGammaRgb < {params}.src_tf.k * {params}.src_tf.b);" )?; // Multiply by the gamut conversion matrix to convert to linear RGB in // the destination color space. writeln!( self.out, "{l1}float3 dstLinearRgb = {params}.gamut_conversion_matrix * srcLinearRgb;" )?; // Finally, apply the dest transfer function to convert to non-linear // RGB in the destination color space, and return the result. writeln!(self.out, "{l1}float3 dstGammaRgb = {NAMESPACE}::select(")?; writeln!(self.out, "{l2}{params}.dst_tf.a * {NAMESPACE}::pow(dstLinearRgb, 1.0 / {params}.dst_tf.g) - ({params}.dst_tf.a - 1),")?; writeln!(self.out, "{l2}{params}.dst_tf.k * dstLinearRgb,")?; writeln!(self.out, "{l2}dstLinearRgb < {params}.dst_tf.b);")?; writeln!(self.out, "{l1}return float4(dstGammaRgb, 1.0);")?; Ok(()) } #[allow(clippy::too_many_arguments)] fn write_wrapped_image_load( &mut self, module: &crate::Module, func_ctx: &back::FunctionCtx, image: Handle, _coordinate: Handle, _array_index: Option>, _sample: Option>, _level: Option>, ) -> BackendResult { // We currently only need to wrap image loads for external textures let class = match *func_ctx.resolve_type(image, &module.types) { crate::TypeInner::Image { class, .. } => class, _ => unreachable!(), }; if class != crate::ImageClass::External { return Ok(()); } let wrapped = WrappedFunction::ImageLoad { class }; if !self.wrapped_functions.insert(wrapped) { return Ok(()); } writeln!(self.out, "float4 {IMAGE_LOAD_EXTERNAL_FUNCTION}({EXTERNAL_TEXTURE_WRAPPER_STRUCT} tex, uint2 coords) {{")?; let l1 = back::Level(1); let l2 = l1.next(); let l3 = l2.next(); writeln!( self.out, "{l1}uint2 plane0_size = uint2(tex.plane0.get_width(), tex.plane0.get_height());" )?; // Clamp coords to provided size of external texture to prevent OOB // read. If params.size is zero then clamp to the actual size of the // texture. writeln!( self.out, "{l1}uint2 cropped_size = {NAMESPACE}::any(tex.params.size != 0) ? tex.params.size : plane0_size;" )?; writeln!( self.out, "{l1}coords = {NAMESPACE}::min(coords, cropped_size - 1);" )?; // Apply load transformation writeln!(self.out, "{l1}uint2 plane0_coords = uint2({NAMESPACE}::round(tex.params.load_transform * float3(float2(coords), 1.0)));")?; writeln!(self.out, "{l1}if (tex.params.num_planes == 1u) {{")?; // For single plane, simply read from plane0 writeln!(self.out, "{l2}return tex.plane0.read(plane0_coords);")?; writeln!(self.out, "{l1}}} else {{")?; // Chroma planes may be subsampled so we must scale the coords accordingly. writeln!( self.out, "{l2}uint2 plane1_size = uint2(tex.plane1.get_width(), tex.plane1.get_height());" )?; writeln!(self.out, "{l2}uint2 plane1_coords = uint2({NAMESPACE}::floor(float2(plane0_coords) * float2(plane1_size) / float2(plane0_size)));")?; // For multi-plane, read the Y value from plane 0 writeln!(self.out, "{l2}float y = tex.plane0.read(plane0_coords).x;")?; writeln!(self.out, "{l2}float2 uv;")?; writeln!(self.out, "{l2}if (tex.params.num_planes == 2u) {{")?; // For 2 planes, read UV from interleaved plane 1 writeln!(self.out, "{l3}uv = tex.plane1.read(plane1_coords).xy;")?; writeln!(self.out, "{l2}}} else {{")?; // For 3 planes, read U and V from planes 1 and 2 respectively writeln!( self.out, "{l2}uint2 plane2_size = uint2(tex.plane2.get_width(), tex.plane2.get_height());" )?; writeln!(self.out, "{l2}uint2 plane2_coords = uint2({NAMESPACE}::floor(float2(plane0_coords) * float2(plane2_size) / float2(plane0_size)));")?; writeln!( self.out, "{l3}uv = float2(tex.plane1.read(plane1_coords).x, tex.plane2.read(plane2_coords).x);" )?; writeln!(self.out, "{l2}}}")?; self.write_convert_yuv_to_rgb_and_return(l2, "y", "uv", "tex.params")?; writeln!(self.out, "{l1}}}")?; writeln!(self.out, "}}")?; writeln!(self.out)?; Ok(()) } #[allow(clippy::too_many_arguments)] fn write_wrapped_image_sample( &mut self, module: &crate::Module, func_ctx: &back::FunctionCtx, image: Handle, _sampler: Handle, _gather: Option, _coordinate: Handle, _array_index: Option>, _offset: Option>, _level: crate::SampleLevel, _depth_ref: Option>, clamp_to_edge: bool, ) -> BackendResult { // We currently only need to wrap textureSampleBaseClampToEdge, for // both sampled and external textures. if !clamp_to_edge { return Ok(()); } let class = match *func_ctx.resolve_type(image, &module.types) { crate::TypeInner::Image { class, .. } => class, _ => unreachable!(), }; let wrapped = WrappedFunction::ImageSample { class, clamp_to_edge: true, }; if !self.wrapped_functions.insert(wrapped) { return Ok(()); } match class { crate::ImageClass::External => { writeln!(self.out, "float4 {IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION}({EXTERNAL_TEXTURE_WRAPPER_STRUCT} tex, {NAMESPACE}::sampler samp, float2 coords) {{")?; let l1 = back::Level(1); let l2 = l1.next(); let l3 = l2.next(); writeln!(self.out, "{l1}uint2 plane0_size = uint2(tex.plane0.get_width(), tex.plane0.get_height());")?; writeln!( self.out, "{l1}coords = tex.params.sample_transform * float3(coords, 1.0);" )?; // Calculate the sample bounds. The purported size of the texture // (params.size) is irrelevant here as we are dealing with normalized // coordinates. Usually we would clamp to (0,0)..(1,1). However, we must // apply the sample transformation to that, also bearing in mind that it // may contain a flip on either axis. We calculate and adjust for the // half-texel separately for each plane as it depends on the actual // texture size which may vary between planes. writeln!( self.out, "{l1}float2 bounds_min = tex.params.sample_transform * float3(0.0, 0.0, 1.0);" )?; writeln!( self.out, "{l1}float2 bounds_max = tex.params.sample_transform * float3(1.0, 1.0, 1.0);" )?; writeln!(self.out, "{l1}float4 bounds = float4({NAMESPACE}::min(bounds_min, bounds_max), {NAMESPACE}::max(bounds_min, bounds_max));")?; writeln!( self.out, "{l1}float2 plane0_half_texel = float2(0.5, 0.5) / float2(plane0_size);" )?; writeln!( self.out, "{l1}float2 plane0_coords = {NAMESPACE}::clamp(coords, bounds.xy + plane0_half_texel, bounds.zw - plane0_half_texel);" )?; writeln!(self.out, "{l1}if (tex.params.num_planes == 1u) {{")?; // For single plane, simply sample from plane0 writeln!( self.out, "{l2}return tex.plane0.sample(samp, plane0_coords, {NAMESPACE}::level(0.0f));" )?; writeln!(self.out, "{l1}}} else {{")?; writeln!(self.out, "{l2}uint2 plane1_size = uint2(tex.plane1.get_width(), tex.plane1.get_height());")?; writeln!( self.out, "{l2}float2 plane1_half_texel = float2(0.5, 0.5) / float2(plane1_size);" )?; writeln!( self.out, "{l2}float2 plane1_coords = {NAMESPACE}::clamp(coords, bounds.xy + plane1_half_texel, bounds.zw - plane1_half_texel);" )?; // For multi-plane, sample the Y value from plane 0 writeln!( self.out, "{l2}float y = tex.plane0.sample(samp, plane0_coords, {NAMESPACE}::level(0.0f)).r;" )?; writeln!(self.out, "{l2}float2 uv = float2(0.0, 0.0);")?; writeln!(self.out, "{l2}if (tex.params.num_planes == 2u) {{")?; // For 2 planes, sample UV from interleaved plane 1 writeln!( self.out, "{l3}uv = tex.plane1.sample(samp, plane1_coords, {NAMESPACE}::level(0.0f)).xy;" )?; writeln!(self.out, "{l2}}} else {{")?; // For 3 planes, sample U and V from planes 1 and 2 respectively writeln!(self.out, "{l3}uint2 plane2_size = uint2(tex.plane2.get_width(), tex.plane2.get_height());")?; writeln!( self.out, "{l3}float2 plane2_half_texel = float2(0.5, 0.5) / float2(plane2_size);" )?; writeln!( self.out, "{l3}float2 plane2_coords = {NAMESPACE}::clamp(coords, bounds.xy + plane2_half_texel, bounds.zw - plane1_half_texel);" )?; writeln!(self.out, "{l3}uv.x = tex.plane1.sample(samp, plane1_coords, {NAMESPACE}::level(0.0f)).x;")?; writeln!(self.out, "{l3}uv.y = tex.plane2.sample(samp, plane2_coords, {NAMESPACE}::level(0.0f)).x;")?; writeln!(self.out, "{l2}}}")?; self.write_convert_yuv_to_rgb_and_return(l2, "y", "uv", "tex.params")?; writeln!(self.out, "{l1}}}")?; writeln!(self.out, "}}")?; writeln!(self.out)?; } _ => { writeln!(self.out, "{NAMESPACE}::float4 {IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION}({NAMESPACE}::texture2d tex, {NAMESPACE}::sampler samp, {NAMESPACE}::float2 coords) {{")?; let l1 = back::Level(1); writeln!(self.out, "{l1}{NAMESPACE}::float2 half_texel = 0.5 / {NAMESPACE}::float2(tex.get_width(0u), tex.get_height(0u));")?; writeln!( self.out, "{l1}return tex.sample(samp, {NAMESPACE}::clamp(coords, half_texel, 1.0 - half_texel), {NAMESPACE}::level(0.0));" )?; writeln!(self.out, "}}")?; writeln!(self.out)?; } } Ok(()) } fn write_wrapped_image_query( &mut self, module: &crate::Module, func_ctx: &back::FunctionCtx, image: Handle, query: crate::ImageQuery, ) -> BackendResult { // We currently only need to wrap size image queries for external textures if !matches!(query, crate::ImageQuery::Size { .. }) { return Ok(()); } let class = match *func_ctx.resolve_type(image, &module.types) { crate::TypeInner::Image { class, .. } => class, _ => unreachable!(), }; if class != crate::ImageClass::External { return Ok(()); } let wrapped = WrappedFunction::ImageQuerySize { class }; if !self.wrapped_functions.insert(wrapped) { return Ok(()); } writeln!( self.out, "uint2 {IMAGE_SIZE_EXTERNAL_FUNCTION}({EXTERNAL_TEXTURE_WRAPPER_STRUCT} tex) {{" )?; let l1 = back::Level(1); let l2 = l1.next(); writeln!( self.out, "{l1}if ({NAMESPACE}::any(tex.params.size != uint2(0u))) {{" )?; writeln!(self.out, "{l2}return tex.params.size;")?; writeln!(self.out, "{l1}}} else {{")?; // params.size == (0, 0) indicates to query and return plane 0's actual size writeln!( self.out, "{l2}return uint2(tex.plane0.get_width(), tex.plane0.get_height());" )?; writeln!(self.out, "{l1}}}")?; writeln!(self.out, "}}")?; writeln!(self.out)?; Ok(()) } fn write_wrapped_cooperative_load( &mut self, module: &crate::Module, func_ctx: &back::FunctionCtx, columns: crate::CooperativeSize, rows: crate::CooperativeSize, pointer: Handle, ) -> BackendResult { let ptr_ty = func_ctx.resolve_type(pointer, &module.types); let space = ptr_ty.pointer_space().unwrap(); let space_name = space.to_msl_name().unwrap_or_default(); let scalar = ptr_ty .pointer_base_type() .unwrap() .inner_with(&module.types) .scalar() .unwrap(); let wrapped = WrappedFunction::CooperativeLoad { space_name, columns, rows, scalar, }; if !self.wrapped_functions.insert(wrapped) { return Ok(()); } let scalar_name = scalar.to_msl_name(); writeln!( self.out, "{NAMESPACE}::simdgroup_{scalar_name}{}x{} {COOPERATIVE_LOAD_FUNCTION}(const {space_name} {scalar_name}* ptr, int stride, bool is_row_major) {{", columns as u32, rows as u32, )?; let l1 = back::Level(1); writeln!( self.out, "{l1}{NAMESPACE}::simdgroup_{scalar_name}{}x{} m;", columns as u32, rows as u32 )?; let matrix_origin = "0"; writeln!( self.out, "{l1}simdgroup_load(m, ptr, stride, {matrix_origin}, is_row_major);" )?; writeln!(self.out, "{l1}return m;")?; writeln!(self.out, "}}")?; writeln!(self.out)?; Ok(()) } fn write_wrapped_cooperative_multiply_add( &mut self, module: &crate::Module, func_ctx: &back::FunctionCtx, space: crate::AddressSpace, a: Handle, b: Handle, ) -> BackendResult { let space_name = space.to_msl_name().unwrap_or_default(); let (a_c, a_r, scalar) = match *func_ctx.resolve_type(a, &module.types) { crate::TypeInner::CooperativeMatrix { columns, rows, scalar, .. } => (columns, rows, scalar), _ => unreachable!(), }; let (b_c, b_r) = match *func_ctx.resolve_type(b, &module.types) { crate::TypeInner::CooperativeMatrix { columns, rows, .. } => (columns, rows), _ => unreachable!(), }; let wrapped = WrappedFunction::CooperativeMultiplyAdd { space_name, columns: b_c, rows: a_r, intermediate: a_c, scalar, }; if !self.wrapped_functions.insert(wrapped) { return Ok(()); } let scalar_name = scalar.to_msl_name(); writeln!( self.out, "{NAMESPACE}::simdgroup_{scalar_name}{}x{} {COOPERATIVE_MULTIPLY_ADD_FUNCTION}(const {space_name} {NAMESPACE}::simdgroup_{scalar_name}{}x{}& a, const {space_name} {NAMESPACE}::simdgroup_{scalar_name}{}x{}& b, const {space_name} {NAMESPACE}::simdgroup_{scalar_name}{}x{}& c) {{", b_c as u32, a_r as u32, a_c as u32, a_r as u32, b_c as u32, b_r as u32, b_c as u32, a_r as u32, )?; let l1 = back::Level(1); writeln!( self.out, "{l1}{NAMESPACE}::simdgroup_{scalar_name}{}x{} d;", b_c as u32, a_r as u32 )?; writeln!(self.out, "{l1}simdgroup_multiply_accumulate(d,a,b,c);")?; writeln!(self.out, "{l1}return d;")?; writeln!(self.out, "}}")?; writeln!(self.out)?; Ok(()) } pub(super) fn write_wrapped_functions( &mut self, module: &crate::Module, func_ctx: &back::FunctionCtx, ) -> BackendResult { for (expr_handle, expr) in func_ctx.expressions.iter() { match *expr { crate::Expression::Unary { op, expr: operand } => { self.write_wrapped_unary_op(module, func_ctx, op, operand)?; } crate::Expression::Binary { op, left, right } => { self.write_wrapped_binary_op(module, func_ctx, expr_handle, op, left, right)?; } crate::Expression::Math { fun, arg, arg1, arg2, arg3, } => { self.write_wrapped_math_function(module, func_ctx, fun, arg, arg1, arg2, arg3)?; } crate::Expression::As { expr, kind, convert, } => { self.write_wrapped_cast(module, func_ctx, expr, kind, convert)?; } crate::Expression::ImageLoad { image, coordinate, array_index, sample, level, } => { self.write_wrapped_image_load( module, func_ctx, image, coordinate, array_index, sample, level, )?; } crate::Expression::ImageSample { image, sampler, gather, coordinate, array_index, offset, level, depth_ref, clamp_to_edge, } => { self.write_wrapped_image_sample( module, func_ctx, image, sampler, gather, coordinate, array_index, offset, level, depth_ref, clamp_to_edge, )?; } crate::Expression::ImageQuery { image, query } => { self.write_wrapped_image_query(module, func_ctx, image, query)?; } crate::Expression::CooperativeLoad { columns, rows, role: _, ref data, } => { self.write_wrapped_cooperative_load( module, func_ctx, columns, rows, data.pointer, )?; } crate::Expression::CooperativeMultiplyAdd { a, b, c: _ } => { let space = crate::AddressSpace::Private; self.write_wrapped_cooperative_multiply_add(module, func_ctx, space, a, b)?; } _ => {} } } Ok(()) } // Returns the array of mapped entry point names. fn write_functions( &mut self, module: &crate::Module, mod_info: &valid::ModuleInfo, options: &Options, pipeline_options: &PipelineOptions, ) -> Result { use back::msl::VertexFormat; // Define structs to hold resolved/generated data for vertex buffers and // their attributes. struct AttributeMappingResolved { ty_name: String, dimension: Option, scalar: crate::Scalar, name: String, } let mut am_resolved = FastHashMap::::default(); struct VertexBufferMappingResolved<'a> { id: u32, stride: u32, step_mode: back::msl::VertexBufferStepMode, ty_name: String, param_name: String, elem_name: String, attributes: &'a Vec, } let mut vbm_resolved = Vec::::new(); // Define a struct to hold a named reference to a byte-unpacking function. struct UnpackingFunction { name: String, byte_count: u32, dimension: Option, scalar: crate::Scalar, } let mut unpacking_functions = FastHashMap::::default(); // Check if we are attempting vertex pulling. If we are, generate some // names we'll need, and iterate the vertex buffer mappings to output // all the conversion functions we'll need to unpack the attribute data. // We can re-use these names for all entry points that need them, since // those entry points also use self.namer. let mut needs_vertex_id = false; let v_id = self.namer.call("v_id"); let mut needs_instance_id = false; let i_id = self.namer.call("i_id"); if pipeline_options.vertex_pulling_transform { for vbm in &pipeline_options.vertex_buffer_mappings { let buffer_id = vbm.id; let buffer_stride = vbm.stride; assert!( buffer_stride > 0, "Vertex pulling requires a non-zero buffer stride." ); match vbm.step_mode { back::msl::VertexBufferStepMode::Constant => {} back::msl::VertexBufferStepMode::ByVertex => { needs_vertex_id = true; } back::msl::VertexBufferStepMode::ByInstance => { needs_instance_id = true; } } let buffer_ty = self.namer.call(format!("vb_{buffer_id}_type").as_str()); let buffer_param = self.namer.call(format!("vb_{buffer_id}_in").as_str()); let buffer_elem = self.namer.call(format!("vb_{buffer_id}_elem").as_str()); vbm_resolved.push(VertexBufferMappingResolved { id: buffer_id, stride: buffer_stride, step_mode: vbm.step_mode, ty_name: buffer_ty, param_name: buffer_param, elem_name: buffer_elem, attributes: &vbm.attributes, }); // Iterate the attributes and generate needed unpacking functions. for attribute in &vbm.attributes { if unpacking_functions.contains_key(&attribute.format) { continue; } let (name, byte_count, dimension, scalar) = match self.write_unpacking_function(attribute.format) { Ok((name, byte_count, dimension, scalar)) => { (name, byte_count, dimension, scalar) } _ => { continue; } }; unpacking_functions.insert( attribute.format, UnpackingFunction { name, byte_count, dimension, scalar, }, ); } } } let mut pass_through_globals = Vec::new(); for (fun_handle, fun) in module.functions.iter() { log::trace!( "function {:?}, handle {:?}", fun.name.as_deref().unwrap_or("(anonymous)"), fun_handle ); let ctx = back::FunctionCtx { ty: back::FunctionType::Function(fun_handle), info: &mod_info[fun_handle], expressions: &fun.expressions, named_expressions: &fun.named_expressions, }; writeln!(self.out)?; self.write_wrapped_functions(module, &ctx)?; let fun_info = &mod_info[fun_handle]; pass_through_globals.clear(); let mut needs_buffer_sizes = false; for (handle, var) in module.global_variables.iter() { if !fun_info[handle].is_empty() { if var.space.needs_pass_through() { pass_through_globals.push(handle); } needs_buffer_sizes |= needs_array_length(var.ty, &module.types); } } let fun_name = &self.names[&NameKey::Function(fun_handle)]; match fun.result { Some(ref result) => { let ty_name = TypeContext { handle: result.ty, gctx: module.to_ctx(), names: &self.names, access: crate::StorageAccess::empty(), first_time: false, }; write!(self.out, "{ty_name}")?; } None => { write!(self.out, "void")?; } } writeln!(self.out, " {fun_name}(")?; for (index, arg) in fun.arguments.iter().enumerate() { let name = &self.names[&NameKey::FunctionArgument(fun_handle, index as u32)]; let param_type_name = TypeContext { handle: arg.ty, gctx: module.to_ctx(), names: &self.names, access: crate::StorageAccess::empty(), first_time: false, }; let separator = separate( !pass_through_globals.is_empty() || index + 1 != fun.arguments.len() || needs_buffer_sizes, ); writeln!( self.out, "{}{} {}{}", back::INDENT, param_type_name, name, separator )?; } for (index, &handle) in pass_through_globals.iter().enumerate() { let tyvar = TypedGlobalVariable { module, names: &self.names, handle, usage: fun_info[handle], reference: true, }; let separator = separate(index + 1 != pass_through_globals.len() || needs_buffer_sizes); write!(self.out, "{}", back::INDENT)?; tyvar.try_fmt(&mut self.out)?; writeln!(self.out, "{separator}")?; } if needs_buffer_sizes { writeln!( self.out, "{}constant _mslBufferSizes& _buffer_sizes", back::INDENT )?; } writeln!(self.out, ") {{")?; let guarded_indices = index::find_checked_indexes(module, fun, fun_info, options.bounds_check_policies); let context = StatementContext { expression: ExpressionContext { function: fun, origin: FunctionOrigin::Handle(fun_handle), info: fun_info, lang_version: options.lang_version, policies: options.bounds_check_policies, guarded_indices, module, mod_info, pipeline_options, force_loop_bounding: options.force_loop_bounding, }, result_struct: None, }; self.put_locals(&context.expression)?; self.update_expressions_to_bake(fun, fun_info, &context.expression); self.put_block(back::Level(1), &fun.body, &context)?; writeln!(self.out, "}}")?; self.named_expressions.clear(); } let ep_range = get_entry_points(module, pipeline_options.entry_point.as_ref()) .map_err(|(stage, name)| Error::EntryPointNotFound(stage, name))?; let mut info = TranslationInfo { entry_point_names: Vec::with_capacity(ep_range.len()), }; for ep_index in ep_range { let ep = &module.entry_points[ep_index]; let fun = &ep.function; let fun_info = mod_info.get_entry_point(ep_index); let mut ep_error = None; // For vertex_id and instance_id arguments, presume that we'll // use our generated names, but switch to the name of an // existing @builtin param, if we find one. let mut v_existing_id = None; let mut i_existing_id = None; log::trace!( "entry point {:?}, index {:?}", fun.name.as_deref().unwrap_or("(anonymous)"), ep_index ); let ctx = back::FunctionCtx { ty: back::FunctionType::EntryPoint(ep_index as u16), info: fun_info, expressions: &fun.expressions, named_expressions: &fun.named_expressions, }; self.write_wrapped_functions(module, &ctx)?; let (em_str, in_mode, out_mode, can_vertex_pull) = match ep.stage { crate::ShaderStage::Vertex => ( "vertex", LocationMode::VertexInput, LocationMode::VertexOutput, true, ), crate::ShaderStage::Fragment => ( "fragment", LocationMode::FragmentInput, LocationMode::FragmentOutput, false, ), crate::ShaderStage::Compute => ( "kernel", LocationMode::Uniform, LocationMode::Uniform, false, ), crate::ShaderStage::Task | crate::ShaderStage::Mesh => unimplemented!(), crate::ShaderStage::RayGeneration | crate::ShaderStage::AnyHit | crate::ShaderStage::ClosestHit | crate::ShaderStage::Miss => unimplemented!(), }; // Should this entry point be modified to do vertex pulling? let do_vertex_pulling = can_vertex_pull && pipeline_options.vertex_pulling_transform && !pipeline_options.vertex_buffer_mappings.is_empty(); // Is any global variable used by this entry point dynamically sized? let needs_buffer_sizes = do_vertex_pulling || module .global_variables .iter() .filter(|&(handle, _)| !fun_info[handle].is_empty()) .any(|(_, var)| needs_array_length(var.ty, &module.types)); // skip this entry point if any global bindings are missing, // or their types are incompatible. if !options.fake_missing_bindings { for (var_handle, var) in module.global_variables.iter() { if fun_info[var_handle].is_empty() { continue; } match var.space { crate::AddressSpace::Uniform | crate::AddressSpace::Storage { .. } | crate::AddressSpace::Handle => { let br = match var.binding { Some(ref br) => br, None => { let var_name = var.name.clone().unwrap_or_default(); ep_error = Some(super::EntryPointError::MissingBinding(var_name)); break; } }; let target = options.get_resource_binding_target(ep, br); let good = match target { Some(target) => { // We intentionally don't dereference binding_arrays here, // so that binding arrays fall to the buffer location. match module.types[var.ty].inner { crate::TypeInner::Image { class: crate::ImageClass::External, .. } => target.external_texture.is_some(), crate::TypeInner::Image { .. } => target.texture.is_some(), crate::TypeInner::Sampler { .. } => { target.sampler.is_some() } _ => target.buffer.is_some(), } } None => false, }; if !good { ep_error = Some(super::EntryPointError::MissingBindTarget(*br)); break; } } crate::AddressSpace::Immediate => { if let Err(e) = options.resolve_immediates(ep) { ep_error = Some(e); break; } } crate::AddressSpace::TaskPayload => { unimplemented!() } crate::AddressSpace::Function | crate::AddressSpace::Private | crate::AddressSpace::WorkGroup => {} crate::AddressSpace::RayPayload | crate::AddressSpace::IncomingRayPayload => unimplemented!(), } } if needs_buffer_sizes { if let Err(err) = options.resolve_sizes_buffer(ep) { ep_error = Some(err); } } } if let Some(err) = ep_error { info.entry_point_names.push(Err(err)); continue; } let fun_name = &self.names[&NameKey::EntryPoint(ep_index as _)]; info.entry_point_names.push(Ok(fun_name.clone())); writeln!(self.out)?; // Since `Namer.reset` wasn't expecting struct members to be // suddenly injected into another namespace like this, // `self.names` doesn't keep them distinct from other variables. // Generate fresh names for these arguments, and remember the // mapping. let mut flattened_member_names = FastHashMap::default(); // Varyings' members get their own namespace let mut varyings_namer = proc::Namer::default(); // List all the Naga `EntryPoint`'s `Function`'s arguments, // flattening structs into their members. In Metal, we will pass // each of these values to the entry point as a separate argument— // except for the varyings, handled next. let mut flattened_arguments = Vec::new(); for (arg_index, arg) in fun.arguments.iter().enumerate() { match module.types[arg.ty].inner { crate::TypeInner::Struct { ref members, .. } => { for (member_index, member) in members.iter().enumerate() { let member_index = member_index as u32; flattened_arguments.push(( NameKey::StructMember(arg.ty, member_index), member.ty, member.binding.as_ref(), )); let name_key = NameKey::StructMember(arg.ty, member_index); let name = match member.binding { Some(crate::Binding::Location { .. }) => { if do_vertex_pulling { self.namer.call(&self.names[&name_key]) } else { varyings_namer.call(&self.names[&name_key]) } } _ => self.namer.call(&self.names[&name_key]), }; flattened_member_names.insert(name_key, name); } } _ => flattened_arguments.push(( NameKey::EntryPointArgument(ep_index as _, arg_index as u32), arg.ty, arg.binding.as_ref(), )), } } // Identify the varyings among the argument values, and maybe emit // a struct type named `Input` to hold them. If we are doing // vertex pulling, we instead update our attribute mapping to // note the types, names, and zero values of the attributes. let stage_in_name = self.namer.call(&format!("{fun_name}Input")); let varyings_member_name = self.namer.call("varyings"); let mut has_varyings = false; if !flattened_arguments.is_empty() { if !do_vertex_pulling { writeln!(self.out, "struct {stage_in_name} {{")?; } for &(ref name_key, ty, binding) in flattened_arguments.iter() { let Some(binding) = binding else { continue; }; let name = match *name_key { NameKey::StructMember(..) => &flattened_member_names[name_key], _ => &self.names[name_key], }; let ty_name = TypeContext { handle: ty, gctx: module.to_ctx(), names: &self.names, access: crate::StorageAccess::empty(), first_time: false, }; let resolved = options.resolve_local_binding(binding, in_mode)?; let location = match *binding { crate::Binding::Location { location, .. } => Some(location), crate::Binding::BuiltIn(crate::BuiltIn::Barycentric { .. }) => None, crate::Binding::BuiltIn(_) => continue, }; if do_vertex_pulling { let Some(location) = location else { continue; }; // Update our attribute mapping. am_resolved.insert( location, AttributeMappingResolved { ty_name: ty_name.to_string(), dimension: ty_name.vector_size(), scalar: ty_name.scalar().unwrap(), name: name.to_string(), }, ); } else { has_varyings = true; write!(self.out, "{}{} {}", back::INDENT, ty_name, name)?; resolved.try_fmt(&mut self.out)?; writeln!(self.out, ";")?; } } if !do_vertex_pulling { writeln!(self.out, "}};")?; } } // Define a struct type named for the return value, if any, named // `Output`. let stage_out_name = self.namer.call(&format!("{fun_name}Output")); let result_member_name = self.namer.call("member"); let result_type_name = match fun.result { Some(ref result) => { let mut result_members = Vec::new(); if let crate::TypeInner::Struct { ref members, .. } = module.types[result.ty].inner { for (member_index, member) in members.iter().enumerate() { result_members.push(( &self.names[&NameKey::StructMember(result.ty, member_index as u32)], member.ty, member.binding.as_ref(), )); } } else { result_members.push(( &result_member_name, result.ty, result.binding.as_ref(), )); } writeln!(self.out, "struct {stage_out_name} {{")?; let mut has_point_size = false; for (name, ty, binding) in result_members { let ty_name = TypeContext { handle: ty, gctx: module.to_ctx(), names: &self.names, access: crate::StorageAccess::empty(), first_time: true, }; let binding = binding.ok_or_else(|| { Error::GenericValidation("Expected binding, got None".into()) })?; if let crate::Binding::BuiltIn(crate::BuiltIn::PointSize) = *binding { has_point_size = true; if !pipeline_options.allow_and_force_point_size { continue; } } let array_len = match module.types[ty].inner { crate::TypeInner::Array { size: crate::ArraySize::Constant(size), .. } => Some(size), _ => None, }; let resolved = options.resolve_local_binding(binding, out_mode)?; write!(self.out, "{}{} {}", back::INDENT, ty_name, name)?; if let Some(array_len) = array_len { write!(self.out, " [{array_len}]")?; } resolved.try_fmt(&mut self.out)?; writeln!(self.out, ";")?; } if pipeline_options.allow_and_force_point_size && ep.stage == crate::ShaderStage::Vertex && !has_point_size { // inject the point size output last writeln!( self.out, "{}float _point_size [[point_size]];", back::INDENT )?; } writeln!(self.out, "}};")?; &stage_out_name } None => "void", }; // If we're doing a vertex pulling transform, define the buffer // structure types. if do_vertex_pulling { for vbm in &vbm_resolved { let buffer_stride = vbm.stride; let buffer_ty = &vbm.ty_name; // Define a structure of bytes of the appropriate size. // When we access the attributes, we'll be unpacking these // bytes at some offset. writeln!( self.out, "struct {buffer_ty} {{ metal::uchar data[{buffer_stride}]; }};" )?; } } // Write the entry point function's name, and begin its argument list. writeln!(self.out, "{em_str} {result_type_name} {fun_name}(")?; let mut is_first_argument = true; let mut separator = || { if is_first_argument { is_first_argument = false; ' ' } else { ',' } }; // If we have produced a struct holding the `EntryPoint`'s // `Function`'s arguments' varyings, pass that struct first. if has_varyings { writeln!( self.out, "{} {stage_in_name} {varyings_member_name} [[stage_in]]", separator() )?; } let mut local_invocation_id = None; // Then pass the remaining arguments not included in the varyings // struct. for &(ref name_key, ty, binding) in flattened_arguments.iter() { let binding = match binding { Some(&crate::Binding::BuiltIn(crate::BuiltIn::Barycentric { .. })) => continue, Some(binding @ &crate::Binding::BuiltIn { .. }) => binding, _ => continue, }; let name = match *name_key { NameKey::StructMember(..) => &flattened_member_names[name_key], _ => &self.names[name_key], }; if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationId) { local_invocation_id = Some(name_key); } let ty_name = TypeContext { handle: ty, gctx: module.to_ctx(), names: &self.names, access: crate::StorageAccess::empty(), first_time: false, }; match *binding { crate::Binding::BuiltIn(crate::BuiltIn::VertexIndex) => { v_existing_id = Some(name.clone()); } crate::Binding::BuiltIn(crate::BuiltIn::InstanceIndex) => { i_existing_id = Some(name.clone()); } _ => {} }; let resolved = options.resolve_local_binding(binding, in_mode)?; write!(self.out, "{} {ty_name} {name}", separator())?; resolved.try_fmt(&mut self.out)?; writeln!(self.out)?; } let need_workgroup_variables_initialization = self.need_workgroup_variables_initialization(options, ep, module, fun_info); if need_workgroup_variables_initialization && local_invocation_id.is_none() { writeln!( self.out, "{} {NAMESPACE}::uint3 __local_invocation_id [[thread_position_in_threadgroup]]", separator() )?; } // Those global variables used by this entry point and its callees // get passed as arguments. `Private` globals are an exception, they // don't outlive this invocation, so we declare them below as locals // within the entry point. for (handle, var) in module.global_variables.iter() { let usage = fun_info[handle]; if usage.is_empty() || var.space == crate::AddressSpace::Private { continue; } if options.lang_version < (1, 2) { match var.space { // This restriction is not documented in the MSL spec // but validation will fail if it is not upheld. // // We infer the required version from the "Function // Buffer Read-Writes" section of [what's new], where // the feature sets listed correspond with the ones // supporting MSL 1.2. // // [what's new]: https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/WhatsNewiniOS10tvOS10andOSX1012/WhatsNewiniOS10tvOS10andOSX1012.html crate::AddressSpace::Storage { access } if access.contains(crate::StorageAccess::STORE) && ep.stage == crate::ShaderStage::Fragment => { return Err(Error::UnsupportedWriteableStorageBuffer) } crate::AddressSpace::Handle => { match module.types[var.ty].inner { crate::TypeInner::Image { class: crate::ImageClass::Storage { access, .. }, .. } => { // This restriction is not documented in the MSL spec // but validation will fail if it is not upheld. // // We infer the required version from the "Function // Texture Read-Writes" section of [what's new], where // the feature sets listed correspond with the ones // supporting MSL 1.2. // // [what's new]: https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/WhatsNewiniOS10tvOS10andOSX1012/WhatsNewiniOS10tvOS10andOSX1012.html if access.contains(crate::StorageAccess::STORE) && (ep.stage == crate::ShaderStage::Vertex || ep.stage == crate::ShaderStage::Fragment) { return Err(Error::UnsupportedWriteableStorageTexture( ep.stage, )); } if access.contains( crate::StorageAccess::LOAD | crate::StorageAccess::STORE, ) { return Err(Error::UnsupportedRWStorageTexture); } } _ => {} } } _ => {} } } // Check min MSL version for binding arrays match var.space { crate::AddressSpace::Handle => match module.types[var.ty].inner { crate::TypeInner::BindingArray { base, .. } => { match module.types[base].inner { crate::TypeInner::Sampler { .. } => { if options.lang_version < (2, 0) { return Err(Error::UnsupportedArrayOf( "samplers".to_string(), )); } } crate::TypeInner::Image { class, .. } => match class { crate::ImageClass::Sampled { .. } | crate::ImageClass::Depth { .. } | crate::ImageClass::Storage { access: crate::StorageAccess::LOAD, .. } => { // Array of textures since: // - iOS: Metal 1.2 (check depends on https://github.com/gfx-rs/naga/issues/2164) // - macOS: Metal 2 if options.lang_version < (2, 0) { return Err(Error::UnsupportedArrayOf( "textures".to_string(), )); } } crate::ImageClass::Storage { access: crate::StorageAccess::STORE, .. } => { // Array of write-only textures since: // - iOS: Metal 2.2 (check depends on https://github.com/gfx-rs/naga/issues/2164) // - macOS: Metal 2 if options.lang_version < (2, 0) { return Err(Error::UnsupportedArrayOf( "write-only textures".to_string(), )); } } crate::ImageClass::Storage { .. } => { if options.lang_version < (3, 0) { return Err(Error::UnsupportedArrayOf( "read-write textures".to_string(), )); } } crate::ImageClass::External => { return Err(Error::UnsupportedArrayOf( "external textures".to_string(), )); } }, _ => { return Err(Error::UnsupportedArrayOfType(base)); } } } _ => {} }, _ => {} } // the resolves have already been checked for `!fake_missing_bindings` case let resolved = match var.space { crate::AddressSpace::Immediate => options.resolve_immediates(ep).ok(), crate::AddressSpace::WorkGroup => None, _ => options .resolve_resource_binding(ep, var.binding.as_ref().unwrap()) .ok(), }; if let Some(ref resolved) = resolved { // Inline samplers are be defined in the EP body if resolved.as_inline_sampler(options).is_some() { continue; } } match module.types[var.ty].inner { crate::TypeInner::Image { class: crate::ImageClass::External, .. } => { // External texture global variables get lowered to 3 textures // and a constant buffer. We must emit a separate argument for // each of these. let target = match resolved { Some(back::msl::ResolvedBinding::Resource(target)) => { target.external_texture } _ => None, }; for i in 0..3 { write!(self.out, "{} ", separator())?; let plane_name = &self.names[&NameKey::ExternalTextureGlobalVariable( handle, ExternalTextureNameKey::Plane(i), )]; write!( self.out, "{NAMESPACE}::texture2d {plane_name}" )?; if let Some(ref target) = target { write!(self.out, " [[texture({})]]", target.planes[i])?; } writeln!(self.out)?; } let params_ty_name = &self.names [&NameKey::Type(module.special_types.external_texture_params.unwrap())]; let params_name = &self.names[&NameKey::ExternalTextureGlobalVariable( handle, ExternalTextureNameKey::Params, )]; write!(self.out, "{} ", separator())?; write!(self.out, "constant {params_ty_name}& {params_name}")?; if let Some(ref target) = target { write!(self.out, " [[buffer({})]]", target.params)?; } } _ => { let tyvar = TypedGlobalVariable { module, names: &self.names, handle, usage, reference: true, }; write!(self.out, "{} ", separator())?; tyvar.try_fmt(&mut self.out)?; if let Some(resolved) = resolved { resolved.try_fmt(&mut self.out)?; } if let Some(value) = var.init { write!(self.out, " = ")?; self.put_const_expression( value, module, mod_info, &module.global_expressions, )?; } } } writeln!(self.out)?; } if do_vertex_pulling { if needs_vertex_id && v_existing_id.is_none() { // Write the [[vertex_id]] argument. writeln!(self.out, "{} uint {v_id} [[vertex_id]]", separator())?; } if needs_instance_id && i_existing_id.is_none() { writeln!(self.out, "{} uint {i_id} [[instance_id]]", separator())?; } // Iterate vbm_resolved, output one argument for every vertex buffer, // using the names we generated earlier. for vbm in &vbm_resolved { let id = &vbm.id; let ty_name = &vbm.ty_name; let param_name = &vbm.param_name; writeln!( self.out, "{} const device {ty_name}* {param_name} [[buffer({id})]]", separator() )?; } } // If this entry uses any variable-length arrays, their sizes are // passed as a final struct-typed argument. if needs_buffer_sizes { // this is checked earlier let resolved = options.resolve_sizes_buffer(ep).unwrap(); write!( self.out, "{} constant _mslBufferSizes& _buffer_sizes", separator() )?; resolved.try_fmt(&mut self.out)?; writeln!(self.out)?; } // end of the entry point argument list writeln!(self.out, ") {{")?; // Starting the function body. if do_vertex_pulling { // Provide zero values for all the attributes, which we will overwrite with // real data from the vertex attribute buffers, if the indices are in-bounds. for vbm in &vbm_resolved { for attribute in vbm.attributes { let location = attribute.shader_location; let am_option = am_resolved.get(&location); if am_option.is_none() { // This bound attribute isn't used in this entry point, so // don't bother zero-initializing it. continue; } let am = am_option.unwrap(); let attribute_ty_name = &am.ty_name; let attribute_name = &am.name; writeln!( self.out, "{}{attribute_ty_name} {attribute_name} = {{}};", back::Level(1) )?; } // Output a bounds check block that will set real values for the // attributes, if the bounds are satisfied. write!(self.out, "{}if (", back::Level(1))?; let idx = &vbm.id; let stride = &vbm.stride; let index_name = match vbm.step_mode { back::msl::VertexBufferStepMode::Constant => "0", back::msl::VertexBufferStepMode::ByVertex => { if let Some(ref name) = v_existing_id { name } else { &v_id } } back::msl::VertexBufferStepMode::ByInstance => { if let Some(ref name) = i_existing_id { name } else { &i_id } } }; write!( self.out, "{index_name} < (_buffer_sizes.buffer_size{idx} / {stride})" )?; writeln!(self.out, ") {{")?; // Pull the bytes out of the vertex buffer. let ty_name = &vbm.ty_name; let elem_name = &vbm.elem_name; let param_name = &vbm.param_name; writeln!( self.out, "{}const {ty_name} {elem_name} = {param_name}[{index_name}];", back::Level(2), )?; // Now set real values for each of the attributes, by unpacking the data // from the buffer elements. for attribute in vbm.attributes { let location = attribute.shader_location; let Some(am) = am_resolved.get(&location) else { // This bound attribute isn't used in this entry point, so // don't bother extracting the data. Too bad we emitted the // unpacking function earlier -- it might not get used. continue; }; let attribute_name = &am.name; let attribute_ty_name = &am.ty_name; let offset = attribute.offset; let func = unpacking_functions .get(&attribute.format) .expect("Should have generated this unpacking function earlier."); let func_name = &func.name; // Check dimensionality of the attribute compared to the unpacking // function. If attribute dimension > unpack dimension, we have to // pad out the unpack value from a vec4(0, 0, 0, 1) of matching // scalar type. Otherwise, if attribute dimension is < unpack // dimension, then we need to explicitly truncate the result. let needs_padding_or_truncation = am.dimension.cmp(&func.dimension); // We need an extra type conversion if the shader type does not // match the type returned from the unpacking function. let needs_conversion = am.scalar != func.scalar; if needs_padding_or_truncation != Ordering::Equal { // Emit a comment flagging that a conversion is happening, // since the actual logic can be at the end of a long line. writeln!( self.out, "{}// {attribute_ty_name} <- {:?}", back::Level(2), attribute.format )?; } write!(self.out, "{}{attribute_name} = ", back::Level(2),)?; if needs_padding_or_truncation == Ordering::Greater { // Needs padding: emit constructor call for wider type write!(self.out, "{attribute_ty_name}(")?; } // Emit call to unpacking function if needs_conversion { put_numeric_type(&mut self.out, am.scalar, func.dimension.as_slice())?; write!(self.out, "(")?; } write!(self.out, "{func_name}({elem_name}.data[{offset}]")?; for i in (offset + 1)..(offset + func.byte_count) { write!(self.out, ", {elem_name}.data[{i}]")?; } write!(self.out, ")")?; if needs_conversion { write!(self.out, ")")?; } match needs_padding_or_truncation { Ordering::Greater => { // Padding let ty_is_int = scalar_is_int(am.scalar); let zero_value = if ty_is_int { "0" } else { "0.0" }; let one_value = if ty_is_int { "1" } else { "1.0" }; for i in func.dimension.map_or(1, u8::from) ..am.dimension.map_or(1, u8::from) { write!( self.out, ", {}", if i == 3 { one_value } else { zero_value } )?; } } Ordering::Less => { // Truncate to the first `am.dimension` components write!( self.out, ".{}", &"xyzw"[0..usize::from(am.dimension.map_or(1, u8::from))] )?; } Ordering::Equal => {} } if needs_padding_or_truncation == Ordering::Greater { write!(self.out, ")")?; } writeln!(self.out, ";")?; } // End the bounds check / attribute setting block. writeln!(self.out, "{}}}", back::Level(1))?; } } if need_workgroup_variables_initialization { self.write_workgroup_variables_initialization( module, mod_info, fun_info, local_invocation_id, )?; } // Metal doesn't support private mutable variables outside of functions, // so we put them here, just like the locals. for (handle, var) in module.global_variables.iter() { let usage = fun_info[handle]; if usage.is_empty() { continue; } if var.space == crate::AddressSpace::Private { let tyvar = TypedGlobalVariable { module, names: &self.names, handle, usage, reference: false, }; write!(self.out, "{}", back::INDENT)?; tyvar.try_fmt(&mut self.out)?; match var.init { Some(value) => { write!(self.out, " = ")?; self.put_const_expression( value, module, mod_info, &module.global_expressions, )?; writeln!(self.out, ";")?; } None => { writeln!(self.out, " = {{}};")?; } }; } else if let Some(ref binding) = var.binding { let resolved = options.resolve_resource_binding(ep, binding).unwrap(); if let Some(sampler) = resolved.as_inline_sampler(options) { // write an inline sampler let name = &self.names[&NameKey::GlobalVariable(handle)]; writeln!( self.out, "{}constexpr {}::sampler {}(", back::INDENT, NAMESPACE, name )?; self.put_inline_sampler_properties(back::Level(2), sampler)?; writeln!(self.out, "{});", back::INDENT)?; } else if let crate::TypeInner::Image { class: crate::ImageClass::External, .. } = module.types[var.ty].inner { // Wrap the individual arguments for each external texture global // in a struct which can be easily passed around. let wrapper_name = &self.names[&NameKey::GlobalVariable(handle)]; let l1 = back::Level(1); let l2 = l1.next(); writeln!( self.out, "{l1}const {EXTERNAL_TEXTURE_WRAPPER_STRUCT} {wrapper_name} {{" )?; for i in 0..3 { let plane_name = &self.names[&NameKey::ExternalTextureGlobalVariable( handle, ExternalTextureNameKey::Plane(i), )]; writeln!(self.out, "{l2}.plane{i} = {plane_name},")?; } let params_name = &self.names[&NameKey::ExternalTextureGlobalVariable( handle, ExternalTextureNameKey::Params, )]; writeln!(self.out, "{l2}.params = {params_name},")?; writeln!(self.out, "{l1}}};")?; } } } // Now take the arguments that we gathered into structs, and the // structs that we flattened into arguments, and emit local // variables with initializers that put everything back the way the // body code expects. // // If we had to generate fresh names for struct members passed as // arguments, be sure to use those names when rebuilding the struct. // // "Each day, I change some zeros to ones, and some ones to zeros. // The rest, I leave alone." for (arg_index, arg) in fun.arguments.iter().enumerate() { let arg_name = &self.names[&NameKey::EntryPointArgument(ep_index as _, arg_index as u32)]; match module.types[arg.ty].inner { crate::TypeInner::Struct { ref members, .. } => { let struct_name = &self.names[&NameKey::Type(arg.ty)]; write!( self.out, "{}const {} {} = {{ ", back::INDENT, struct_name, arg_name )?; for (member_index, member) in members.iter().enumerate() { let key = NameKey::StructMember(arg.ty, member_index as u32); let name = &flattened_member_names[&key]; if member_index != 0 { write!(self.out, ", ")?; } // insert padding initialization, if needed if self .struct_member_pads .contains(&(arg.ty, member_index as u32)) { write!(self.out, "{{}}, ")?; } if let Some(crate::Binding::Location { .. }) = member.binding { if has_varyings { write!(self.out, "{varyings_member_name}.")?; } } write!(self.out, "{name}")?; } writeln!(self.out, " }};")?; } _ => match arg.binding { Some(crate::Binding::Location { .. }) | Some(crate::Binding::BuiltIn(crate::BuiltIn::Barycentric { .. })) => { if has_varyings { writeln!( self.out, "{}const auto {} = {}.{};", back::INDENT, arg_name, varyings_member_name, arg_name )?; } } _ => {} }, } } let guarded_indices = index::find_checked_indexes(module, fun, fun_info, options.bounds_check_policies); let context = StatementContext { expression: ExpressionContext { function: fun, origin: FunctionOrigin::EntryPoint(ep_index as _), info: fun_info, lang_version: options.lang_version, policies: options.bounds_check_policies, guarded_indices, module, mod_info, pipeline_options, force_loop_bounding: options.force_loop_bounding, }, result_struct: Some(&stage_out_name), }; // Finally, declare all the local variables that we need //TODO: we can postpone this till the relevant expressions are emitted self.put_locals(&context.expression)?; self.update_expressions_to_bake(fun, fun_info, &context.expression); self.put_block(back::Level(1), &fun.body, &context)?; writeln!(self.out, "}}")?; if ep_index + 1 != module.entry_points.len() { writeln!(self.out)?; } self.named_expressions.clear(); } Ok(info) } fn write_barrier(&mut self, flags: crate::Barrier, level: back::Level) -> BackendResult { // Note: OR-ring bitflags requires `__HAVE_MEMFLAG_OPERATORS__`, // so we try to avoid it here. if flags.is_empty() { writeln!( self.out, "{level}{NAMESPACE}::threadgroup_barrier({NAMESPACE}::mem_flags::mem_none);", )?; } if flags.contains(crate::Barrier::STORAGE) { writeln!( self.out, "{level}{NAMESPACE}::threadgroup_barrier({NAMESPACE}::mem_flags::mem_device);", )?; } if flags.contains(crate::Barrier::WORK_GROUP) { writeln!( self.out, "{level}{NAMESPACE}::threadgroup_barrier({NAMESPACE}::mem_flags::mem_threadgroup);", )?; } if flags.contains(crate::Barrier::SUB_GROUP) { writeln!( self.out, "{level}{NAMESPACE}::simdgroup_barrier({NAMESPACE}::mem_flags::mem_threadgroup);", )?; } if flags.contains(crate::Barrier::TEXTURE) { writeln!( self.out, "{level}{NAMESPACE}::threadgroup_barrier({NAMESPACE}::mem_flags::mem_texture);", )?; } Ok(()) } } /// Initializing workgroup variables is more tricky for Metal because we have to deal /// with atomics at the type-level (which don't have a copy constructor). mod workgroup_mem_init { use crate::EntryPoint; use super::*; enum Access { GlobalVariable(Handle), StructMember(Handle, u32), Array(usize), } impl Access { fn write( &self, writer: &mut W, names: &FastHashMap, ) -> Result<(), core::fmt::Error> { match *self { Access::GlobalVariable(handle) => { write!(writer, "{}", &names[&NameKey::GlobalVariable(handle)]) } Access::StructMember(handle, index) => { write!(writer, ".{}", &names[&NameKey::StructMember(handle, index)]) } Access::Array(depth) => write!(writer, ".{WRAPPED_ARRAY_FIELD}[__i{depth}]"), } } } struct AccessStack { stack: Vec, array_depth: usize, } impl AccessStack { const fn new() -> Self { Self { stack: Vec::new(), array_depth: 0, } } fn enter_array(&mut self, cb: impl FnOnce(&mut Self, usize) -> R) -> R { let array_depth = self.array_depth; self.stack.push(Access::Array(array_depth)); self.array_depth += 1; let res = cb(self, array_depth); self.stack.pop(); self.array_depth -= 1; res } fn enter(&mut self, new: Access, cb: impl FnOnce(&mut Self) -> R) -> R { self.stack.push(new); let res = cb(self); self.stack.pop(); res } fn write( &self, writer: &mut W, names: &FastHashMap, ) -> Result<(), core::fmt::Error> { for next in self.stack.iter() { next.write(writer, names)?; } Ok(()) } } impl Writer { pub(super) fn need_workgroup_variables_initialization( &mut self, options: &Options, ep: &EntryPoint, module: &crate::Module, fun_info: &valid::FunctionInfo, ) -> bool { options.zero_initialize_workgroup_memory && ep.stage.compute_like() && module.global_variables.iter().any(|(handle, var)| { !fun_info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup }) } pub(super) fn write_workgroup_variables_initialization( &mut self, module: &crate::Module, module_info: &valid::ModuleInfo, fun_info: &valid::FunctionInfo, local_invocation_id: Option<&NameKey>, ) -> BackendResult { let level = back::Level(1); writeln!( self.out, "{}if ({}::all({} == {}::uint3(0u))) {{", level, NAMESPACE, local_invocation_id .map(|name_key| self.names[name_key].as_str()) .unwrap_or("__local_invocation_id"), NAMESPACE, )?; let mut access_stack = AccessStack::new(); let vars = module.global_variables.iter().filter(|&(handle, var)| { !fun_info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup }); for (handle, var) in vars { access_stack.enter(Access::GlobalVariable(handle), |access_stack| { self.write_workgroup_variable_initialization( module, module_info, var.ty, access_stack, level.next(), ) })?; } writeln!(self.out, "{level}}}")?; self.write_barrier(crate::Barrier::WORK_GROUP, level) } fn write_workgroup_variable_initialization( &mut self, module: &crate::Module, module_info: &valid::ModuleInfo, ty: Handle, access_stack: &mut AccessStack, level: back::Level, ) -> BackendResult { if module_info[ty].contains(valid::TypeFlags::CONSTRUCTIBLE) { write!(self.out, "{level}")?; access_stack.write(&mut self.out, &self.names)?; writeln!(self.out, " = {{}};")?; } else { match module.types[ty].inner { crate::TypeInner::Atomic { .. } => { write!( self.out, "{level}{NAMESPACE}::atomic_store_explicit({ATOMIC_REFERENCE}" )?; access_stack.write(&mut self.out, &self.names)?; writeln!(self.out, ", 0, {NAMESPACE}::memory_order_relaxed);")?; } crate::TypeInner::Array { base, size, .. } => { let count = match size.resolve(module.to_ctx())? { proc::IndexableLength::Known(count) => count, proc::IndexableLength::Dynamic => unreachable!(), }; access_stack.enter_array(|access_stack, array_depth| { writeln!( self.out, "{level}for (int __i{array_depth} = 0; __i{array_depth} < {count}; __i{array_depth}++) {{" )?; self.write_workgroup_variable_initialization( module, module_info, base, access_stack, level.next(), )?; writeln!(self.out, "{level}}}")?; BackendResult::Ok(()) })?; } crate::TypeInner::Struct { ref members, .. } => { for (index, member) in members.iter().enumerate() { access_stack.enter( Access::StructMember(ty, index as u32), |access_stack| { self.write_workgroup_variable_initialization( module, module_info, member.ty, access_stack, level, ) }, )?; } } _ => unreachable!(), } } Ok(()) } } } impl crate::AtomicFunction { const fn to_msl(self) -> &'static str { match self { Self::Add => "fetch_add", Self::Subtract => "fetch_sub", Self::And => "fetch_and", Self::InclusiveOr => "fetch_or", Self::ExclusiveOr => "fetch_xor", Self::Min => "fetch_min", Self::Max => "fetch_max", Self::Exchange { compare: None } => "exchange", Self::Exchange { compare: Some(_) } => ATOMIC_COMP_EXCH_FUNCTION, } } fn to_msl_64_bit(self) -> Result<&'static str, Error> { Ok(match self { Self::Min => "min", Self::Max => "max", _ => Err(Error::FeatureNotImplemented( "64-bit atomic operation other than min/max".to_string(), ))?, }) } } ================================================ FILE: naga/src/back/pipeline_constants.rs ================================================ use alloc::{ borrow::Cow, string::{String, ToString}, vec::Vec, }; use core::mem; use hashbrown::HashSet; use thiserror::Error; use super::PipelineConstants; use crate::{ arena::HandleVec, compact::{compact, KeepUnused}, ir, proc::{ConstantEvaluator, ConstantEvaluatorError, Emitter}, valid::{Capabilities, ModuleInfo, ValidationError, ValidationFlags, Validator}, Arena, Block, Constant, Expression, Function, Handle, Literal, Module, Override, Range, Scalar, Span, Statement, TypeInner, WithSpan, }; // Possibly unused if not compiled with no_std #[allow(unused_imports)] use num_traits::float::FloatCore as _; #[derive(Error, Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pub enum PipelineConstantError { #[error("Missing value for pipeline-overridable constant with identifier string: '{0}'")] MissingValue(String), #[error("pipeline-overridable constant '{0}' not found in the shader")] NotFound(String), #[error( "Source f64 value needs to be finite ({}) for number destinations", "NaNs and Inifinites are not allowed" )] SrcNeedsToBeFinite, #[error("Source f64 value doesn't fit in destination")] DstRangeTooSmall, #[error(transparent)] ConstantEvaluatorError(#[from] ConstantEvaluatorError), #[error(transparent)] ValidationError(#[from] WithSpan), #[error("workgroup_size override isn't strictly positive")] NegativeWorkgroupSize, #[error("max vertices or max primitives is negative")] NegativeMeshOutputMax, } /// Compact `module` and replace all overrides with constants. /// /// `module` must be valid. Both compaction and constant evaluation may produce /// invalid results (e.g. replace an invalid expression with a constant) for /// invalid modules. /// /// If no changes are needed, this just returns `Cow::Borrowed` references to /// `module` and `module_info`. Otherwise, it clones `module`, retains only the /// selected entry point, compacts the module, edits its [`global_expressions`] /// arena to contain only fully-evaluated expressions, and returns the /// simplified module and its validation results. /// /// The module returned has an empty `overrides` arena, and the /// `global_expressions` arena contains only fully-evaluated expressions. /// /// [`global_expressions`]: Module::global_expressions pub fn process_overrides<'a>( module: &'a Module, module_info: &'a ModuleInfo, entry_point: Option<(ir::ShaderStage, &str)>, pipeline_constants: &PipelineConstants, ) -> Result<(Cow<'a, Module>, Cow<'a, ModuleInfo>), PipelineConstantError> { let mut handles = module .overrides .iter() .map(|(handle, _)| handle) .collect::>(); for c in pipeline_constants.keys() { let c_id = c.parse().ok(); if let Some((i, _)) = handles.iter().enumerate().find(|&(_, handle)| { let o = &module.overrides[*handle]; if o.id.is_some() { o.id == c_id } else { o.name.as_deref() == Some(c.as_str()) } }) { handles.swap_remove(i); } else { return Err(PipelineConstantError::NotFound(c.clone())); } } if (entry_point.is_none() || module.entry_points.len() <= 1) && module.overrides.is_empty() { // We skip compacting the module here mostly to reduce the risk of // hitting corner cases like https://github.com/gfx-rs/wgpu/issues/7793. // Compaction doesn't cost very much [1], so it would also be reasonable // to do it unconditionally. Even when there is a single entry point or // when no entry point is specified, it is still possible that there // are unreferenced items in the module that would be removed by this // compaction. // // [1]: https://github.com/gfx-rs/wgpu/pull/7703#issuecomment-2902153760 return Ok((Cow::Borrowed(module), Cow::Borrowed(module_info))); } let mut module = module.clone(); if let Some((ep_stage, ep_name)) = entry_point { module .entry_points .retain(|ep| ep.stage == ep_stage && ep.name == ep_name); } // Compact the module to remove anything not reachable from an entry point. // This is necessary because we may not have values for overrides that are // not reachable from the/an entry point. compact(&mut module, KeepUnused::No); // If there are no overrides in the module, then we can skip the rest. if module.overrides.is_empty() { return revalidate(module); } // A map from override handles to the handles of the constants // we've replaced them with. let mut override_map = HandleVec::with_capacity(module.overrides.len()); // A map from `module`'s original global expression handles to // handles in the new, simplified global expression arena. let mut adjusted_global_expressions = HandleVec::with_capacity(module.global_expressions.len()); // The set of constants whose initializer handles we've already // updated to refer to the newly built global expression arena. // // All constants in `module` must have their `init` handles // updated to point into the new, simplified global expression // arena. Some of these we can most easily handle as a side effect // during the simplification process, but we must handle the rest // in a final fixup pass, guided by `adjusted_global_expressions`. We // add their handles to this set, so that the final fixup step can // leave them alone. let mut adjusted_constant_initializers = HashSet::with_capacity(module.constants.len()); let mut global_expression_kind_tracker = crate::proc::ExpressionKindTracker::new(); let mut layouter = crate::proc::Layouter::default(); // An iterator through the original overrides table, consumed in // approximate tandem with the global expressions. let mut overrides = mem::take(&mut module.overrides); let mut override_iter = overrides.iter_mut_span(); // Do two things in tandem: // // - Rebuild the global expression arena from scratch, fully // evaluating all expressions, and replacing each `Override` // expression in `module.global_expressions` with a `Constant` // expression. // // - Build a new `Constant` in `module.constants` to take the // place of each `Override`. // // Build a map from old global expression handles to their // fully-evaluated counterparts in `adjusted_global_expressions` as we // go. // // Why in tandem? Overrides refer to expressions, and expressions // refer to overrides, so we can't disentangle the two into // separate phases. However, we can take advantage of the fact // that the overrides and expressions must form a DAG, and work // our way from the leaves to the roots, replacing and evaluating // as we go. // // Although the two loops are nested, this is really two // alternating phases: we adjust and evaluate constant expressions // until we hit an `Override` expression, at which point we switch // to building `Constant`s for `Overrides` until we've handled the // one used by the expression. Then we switch back to processing // expressions. Because we know they form a DAG, we know the // `Override` expressions we encounter can only have initializers // referring to global expressions we've already simplified. for (old_h, expr, span) in module.global_expressions.drain() { let mut expr = match expr { Expression::Override(h) => { let c_h = if let Some(new_h) = override_map.get(h) { *new_h } else { let mut new_h = None; for entry in override_iter.by_ref() { let stop = entry.0 == h; new_h = Some(process_override( entry, pipeline_constants, &mut module, &mut override_map, &adjusted_global_expressions, &mut adjusted_constant_initializers, &mut global_expression_kind_tracker, )?); if stop { break; } } new_h.unwrap() }; Expression::Constant(c_h) } Expression::Constant(c_h) => { if adjusted_constant_initializers.insert(c_h) { let init = &mut module.constants[c_h].init; *init = adjusted_global_expressions[*init]; } expr } expr => expr, }; let mut evaluator = ConstantEvaluator::for_wgsl_module( &mut module, &mut global_expression_kind_tracker, &mut layouter, false, ); adjust_expr(&adjusted_global_expressions, &mut expr); let h = evaluator.try_eval_and_append(expr, span)?; adjusted_global_expressions.insert(old_h, h); } // Finish processing any overrides we didn't visit in the loop above. for entry in override_iter { match *entry.1 { Override { name: Some(_), .. } | Override { id: Some(_), .. } => { process_override( entry, pipeline_constants, &mut module, &mut override_map, &adjusted_global_expressions, &mut adjusted_constant_initializers, &mut global_expression_kind_tracker, )?; } Override { init: Some(ref mut init), .. } => { *init = adjusted_global_expressions[*init]; } _ => {} } } // Update the initialization expression handles of all `Constant`s // and `GlobalVariable`s. Skip `Constant`s we'd already updated en // passant. for (_, c) in module .constants .iter_mut() .filter(|&(c_h, _)| !adjusted_constant_initializers.contains(&c_h)) { c.init = adjusted_global_expressions[c.init]; } for (_, v) in module.global_variables.iter_mut() { if let Some(ref mut init) = v.init { *init = adjusted_global_expressions[*init]; } } let mut functions = mem::take(&mut module.functions); for (_, function) in functions.iter_mut() { process_function(&mut module, &override_map, &mut layouter, function)?; } module.functions = functions; let mut entry_points = mem::take(&mut module.entry_points); for ep in entry_points.iter_mut() { process_function(&mut module, &override_map, &mut layouter, &mut ep.function)?; process_workgroup_size_override(&mut module, &adjusted_global_expressions, ep)?; process_mesh_shader_overrides(&mut module, &adjusted_global_expressions, ep)?; } module.entry_points = entry_points; module.overrides = overrides; // Now that we've rewritten all the expressions, we need to // recompute their types and other metadata. For the time being, // do a full re-validation. revalidate(module) } fn revalidate( module: Module, ) -> Result<(Cow<'static, Module>, Cow<'static, ModuleInfo>), PipelineConstantError> { let mut validator = Validator::new(ValidationFlags::all(), Capabilities::all()); let module_info = validator.validate_resolved_overrides(&module)?; Ok((Cow::Owned(module), Cow::Owned(module_info))) } fn process_workgroup_size_override( module: &mut Module, adjusted_global_expressions: &HandleVec>, ep: &mut crate::EntryPoint, ) -> Result<(), PipelineConstantError> { match ep.workgroup_size_overrides { None => {} Some(overrides) => { overrides.iter().enumerate().try_for_each( |(i, overridden)| -> Result<(), PipelineConstantError> { match *overridden { None => Ok(()), Some(h) => { ep.workgroup_size[i] = module .to_ctx() .get_const_val(adjusted_global_expressions[h]) .map(|n| { if n == 0 { Err(PipelineConstantError::NegativeWorkgroupSize) } else { Ok(n) } }) .map_err(|_| PipelineConstantError::NegativeWorkgroupSize)??; Ok(()) } } }, )?; ep.workgroup_size_overrides = None; } } Ok(()) } fn process_mesh_shader_overrides( module: &mut Module, adjusted_global_expressions: &HandleVec>, ep: &mut crate::EntryPoint, ) -> Result<(), PipelineConstantError> { if let Some(ref mut mesh_info) = ep.mesh_info { if let Some(r#override) = mesh_info.max_vertices_override { mesh_info.max_vertices = module .to_ctx() .get_const_val(adjusted_global_expressions[r#override]) .map_err(|_| PipelineConstantError::NegativeMeshOutputMax)?; } if let Some(r#override) = mesh_info.max_primitives_override { mesh_info.max_primitives = module .to_ctx() .get_const_val(adjusted_global_expressions[r#override]) .map_err(|_| PipelineConstantError::NegativeMeshOutputMax)?; } } Ok(()) } /// Add a [`Constant`] to `module` for the override `old_h`. /// /// Add the new `Constant` to `override_map` and `adjusted_constant_initializers`. fn process_override( (old_h, r#override, span): (Handle, &mut Override, &Span), pipeline_constants: &PipelineConstants, module: &mut Module, override_map: &mut HandleVec>, adjusted_global_expressions: &HandleVec>, adjusted_constant_initializers: &mut HashSet>, global_expression_kind_tracker: &mut crate::proc::ExpressionKindTracker, ) -> Result, PipelineConstantError> { // Determine which key to use for `r#override` in `pipeline_constants`. let key = if let Some(id) = r#override.id { Cow::Owned(id.to_string()) } else if let Some(ref name) = r#override.name { Cow::Borrowed(name) } else { unreachable!(); }; // Generate a global expression for `r#override`'s value, either // from the provided `pipeline_constants` table or its initializer // in the module. let init = if let Some(value) = pipeline_constants.get::(&key) { let literal = match module.types[r#override.ty].inner { TypeInner::Scalar(scalar) => map_value_to_literal(*value, scalar)?, _ => unreachable!(), }; let expr = module .global_expressions .append(Expression::Literal(literal), Span::UNDEFINED); global_expression_kind_tracker.insert(expr, crate::proc::ExpressionKind::Const); expr } else if let Some(init) = r#override.init { adjusted_global_expressions[init] } else { return Err(PipelineConstantError::MissingValue(key.to_string())); }; // Generate a new `Constant` to represent the override's value. let constant = Constant { name: r#override.name.clone(), ty: r#override.ty, init, }; let h = module.constants.append(constant, *span); override_map.insert(old_h, h); adjusted_constant_initializers.insert(h); r#override.init = Some(init); Ok(h) } /// Replace all override expressions in `function` with fully-evaluated constants. /// /// Replace all `Expression::Override`s in `function`'s expression arena with /// the corresponding `Expression::Constant`s, as given in `override_map`. /// Replace any expressions whose values are now known with their fully /// evaluated form. /// /// If `h` is a `Handle`, then `override_map[h]` is the /// `Handle` for the override's final value. fn process_function( module: &mut Module, override_map: &HandleVec>, layouter: &mut crate::proc::Layouter, function: &mut Function, ) -> Result<(), ConstantEvaluatorError> { // A map from original local expression handles to // handles in the new, local expression arena. let mut adjusted_local_expressions = HandleVec::with_capacity(function.expressions.len()); let mut local_expression_kind_tracker = crate::proc::ExpressionKindTracker::new(); let mut expressions = mem::take(&mut function.expressions); // Dummy `emitter` and `block` for the constant evaluator. // We can ignore the concept of emitting expressions here since // expressions have already been covered by a `Statement::Emit` // in the frontend. // The only thing we might have to do is remove some expressions // that have been covered by a `Statement::Emit`. See the docs of // `filter_emits_in_block` for the reasoning. let mut emitter = Emitter::default(); let mut block = Block::new(); let mut evaluator = ConstantEvaluator::for_wgsl_function( module, &mut function.expressions, &mut local_expression_kind_tracker, layouter, &mut emitter, &mut block, false, ); for (old_h, mut expr, span) in expressions.drain() { if let Expression::Override(h) = expr { expr = Expression::Constant(override_map[h]); } adjust_expr(&adjusted_local_expressions, &mut expr); let h = evaluator.try_eval_and_append(expr, span)?; adjusted_local_expressions.insert(old_h, h); } adjust_block(&adjusted_local_expressions, &mut function.body); filter_emits_in_block(&mut function.body, &function.expressions); // Update local expression initializers. for (_, local) in function.local_variables.iter_mut() { if let &mut Some(ref mut init) = &mut local.init { *init = adjusted_local_expressions[*init]; } } // We've changed the keys of `function.named_expression`, so we have to // rebuild it from scratch. let named_expressions = mem::take(&mut function.named_expressions); for (expr_h, name) in named_expressions { function .named_expressions .insert(adjusted_local_expressions[expr_h], name); } Ok(()) } /// Replace every expression handle in `expr` with its counterpart /// given by `new_pos`. fn adjust_expr(new_pos: &HandleVec>, expr: &mut Expression) { let adjust = |expr: &mut Handle| { *expr = new_pos[*expr]; }; match *expr { Expression::Compose { ref mut components, ty: _, } => { for c in components.iter_mut() { adjust(c); } } Expression::Access { ref mut base, ref mut index, } => { adjust(base); adjust(index); } Expression::AccessIndex { ref mut base, index: _, } => { adjust(base); } Expression::Splat { ref mut value, size: _, } => { adjust(value); } Expression::Swizzle { ref mut vector, size: _, pattern: _, } => { adjust(vector); } Expression::Load { ref mut pointer } => { adjust(pointer); } Expression::ImageSample { ref mut image, ref mut sampler, ref mut coordinate, ref mut array_index, ref mut offset, ref mut level, ref mut depth_ref, gather: _, clamp_to_edge: _, } => { adjust(image); adjust(sampler); adjust(coordinate); if let Some(e) = array_index.as_mut() { adjust(e); } if let Some(e) = offset.as_mut() { adjust(e); } match *level { crate::SampleLevel::Exact(ref mut expr) | crate::SampleLevel::Bias(ref mut expr) => { adjust(expr); } crate::SampleLevel::Gradient { ref mut x, ref mut y, } => { adjust(x); adjust(y); } _ => {} } if let Some(e) = depth_ref.as_mut() { adjust(e); } } Expression::ImageLoad { ref mut image, ref mut coordinate, ref mut array_index, ref mut sample, ref mut level, } => { adjust(image); adjust(coordinate); if let Some(e) = array_index.as_mut() { adjust(e); } if let Some(e) = sample.as_mut() { adjust(e); } if let Some(e) = level.as_mut() { adjust(e); } } Expression::ImageQuery { ref mut image, ref mut query, } => { adjust(image); match *query { crate::ImageQuery::Size { ref mut level } => { if let Some(e) = level.as_mut() { adjust(e); } } crate::ImageQuery::NumLevels | crate::ImageQuery::NumLayers | crate::ImageQuery::NumSamples => {} } } Expression::Unary { ref mut expr, op: _, } => { adjust(expr); } Expression::Binary { ref mut left, ref mut right, op: _, } => { adjust(left); adjust(right); } Expression::Select { ref mut condition, ref mut accept, ref mut reject, } => { adjust(condition); adjust(accept); adjust(reject); } Expression::Derivative { ref mut expr, axis: _, ctrl: _, } => { adjust(expr); } Expression::Relational { ref mut argument, fun: _, } => { adjust(argument); } Expression::Math { ref mut arg, ref mut arg1, ref mut arg2, ref mut arg3, fun: _, } => { adjust(arg); if let Some(e) = arg1.as_mut() { adjust(e); } if let Some(e) = arg2.as_mut() { adjust(e); } if let Some(e) = arg3.as_mut() { adjust(e); } } Expression::As { ref mut expr, kind: _, convert: _, } => { adjust(expr); } Expression::ArrayLength(ref mut expr) => { adjust(expr); } Expression::RayQueryGetIntersection { ref mut query, committed: _, } => { adjust(query); } Expression::Literal(_) | Expression::FunctionArgument(_) | Expression::GlobalVariable(_) | Expression::LocalVariable(_) | Expression::CallResult(_) | Expression::RayQueryProceedResult | Expression::Constant(_) | Expression::Override(_) | Expression::ZeroValue(_) | Expression::AtomicResult { ty: _, comparison: _, } | Expression::WorkGroupUniformLoadResult { ty: _ } | Expression::SubgroupBallotResult | Expression::SubgroupOperationResult { .. } => {} Expression::RayQueryVertexPositions { ref mut query, committed: _, } => { adjust(query); } Expression::CooperativeLoad { ref mut data, .. } => { adjust(&mut data.pointer); adjust(&mut data.stride); } Expression::CooperativeMultiplyAdd { ref mut a, ref mut b, ref mut c, } => { adjust(a); adjust(b); adjust(c); } } } /// Replace every expression handle in `block` with its counterpart /// given by `new_pos`. fn adjust_block(new_pos: &HandleVec>, block: &mut Block) { for stmt in block.iter_mut() { adjust_stmt(new_pos, stmt); } } /// Replace every expression handle in `stmt` with its counterpart /// given by `new_pos`. fn adjust_stmt(new_pos: &HandleVec>, stmt: &mut Statement) { let adjust = |expr: &mut Handle| { *expr = new_pos[*expr]; }; match *stmt { Statement::Emit(ref mut range) => { if let Some((mut first, mut last)) = range.first_and_last() { adjust(&mut first); adjust(&mut last); *range = Range::new_from_bounds(first, last); } } Statement::Block(ref mut block) => { adjust_block(new_pos, block); } Statement::If { ref mut condition, ref mut accept, ref mut reject, } => { adjust(condition); adjust_block(new_pos, accept); adjust_block(new_pos, reject); } Statement::Switch { ref mut selector, ref mut cases, } => { adjust(selector); for case in cases.iter_mut() { adjust_block(new_pos, &mut case.body); } } Statement::Loop { ref mut body, ref mut continuing, ref mut break_if, } => { adjust_block(new_pos, body); adjust_block(new_pos, continuing); if let Some(e) = break_if.as_mut() { adjust(e); } } Statement::Return { ref mut value } => { if let Some(e) = value.as_mut() { adjust(e); } } Statement::Store { ref mut pointer, ref mut value, } => { adjust(pointer); adjust(value); } Statement::ImageStore { ref mut image, ref mut coordinate, ref mut array_index, ref mut value, } => { adjust(image); adjust(coordinate); if let Some(e) = array_index.as_mut() { adjust(e); } adjust(value); } Statement::Atomic { ref mut pointer, ref mut value, ref mut result, ref mut fun, } => { adjust(pointer); adjust(value); if let Some(ref mut result) = *result { adjust(result); } match *fun { crate::AtomicFunction::Exchange { compare: Some(ref mut compare), } => { adjust(compare); } crate::AtomicFunction::Add | crate::AtomicFunction::Subtract | crate::AtomicFunction::And | crate::AtomicFunction::ExclusiveOr | crate::AtomicFunction::InclusiveOr | crate::AtomicFunction::Min | crate::AtomicFunction::Max | crate::AtomicFunction::Exchange { compare: None } => {} } } Statement::ImageAtomic { ref mut image, ref mut coordinate, ref mut array_index, fun: _, ref mut value, } => { adjust(image); adjust(coordinate); if let Some(ref mut array_index) = *array_index { adjust(array_index); } adjust(value); } Statement::WorkGroupUniformLoad { ref mut pointer, ref mut result, } => { adjust(pointer); adjust(result); } Statement::SubgroupBallot { ref mut result, ref mut predicate, } => { if let Some(ref mut predicate) = *predicate { adjust(predicate); } adjust(result); } Statement::SubgroupCollectiveOperation { ref mut argument, ref mut result, .. } => { adjust(argument); adjust(result); } Statement::SubgroupGather { ref mut mode, ref mut argument, ref mut result, } => { match *mode { crate::GatherMode::BroadcastFirst => {} crate::GatherMode::Broadcast(ref mut index) | crate::GatherMode::Shuffle(ref mut index) | crate::GatherMode::ShuffleDown(ref mut index) | crate::GatherMode::ShuffleUp(ref mut index) | crate::GatherMode::ShuffleXor(ref mut index) | crate::GatherMode::QuadBroadcast(ref mut index) => { adjust(index); } crate::GatherMode::QuadSwap(_) => {} } adjust(argument); adjust(result) } Statement::Call { ref mut arguments, ref mut result, function: _, } => { for argument in arguments.iter_mut() { adjust(argument); } if let Some(e) = result.as_mut() { adjust(e); } } Statement::RayQuery { ref mut query, ref mut fun, } => { adjust(query); match *fun { crate::RayQueryFunction::Initialize { ref mut acceleration_structure, ref mut descriptor, } => { adjust(acceleration_structure); adjust(descriptor); } crate::RayQueryFunction::Proceed { ref mut result } => { adjust(result); } crate::RayQueryFunction::GenerateIntersection { ref mut hit_t } => { adjust(hit_t); } crate::RayQueryFunction::ConfirmIntersection => {} crate::RayQueryFunction::Terminate => {} } } Statement::CooperativeStore { ref mut target, ref mut data, } => { adjust(target); adjust(&mut data.pointer); adjust(&mut data.stride); } Statement::RayPipelineFunction(ref mut func) => match *func { crate::RayPipelineFunction::TraceRay { ref mut acceleration_structure, ref mut descriptor, ref mut payload, } => { adjust(acceleration_structure); adjust(descriptor); adjust(payload); } }, Statement::Break | Statement::Continue | Statement::Kill | Statement::ControlBarrier(_) | Statement::MemoryBarrier(_) => {} } } /// Adjust [`Emit`] statements in `block` to skip [`needs_pre_emit`] expressions we have introduced. /// /// According to validation, [`Emit`] statements must not cover any expressions /// for which [`Expression::needs_pre_emit`] returns true. All expressions built /// by successful constant evaluation fall into that category, meaning that /// `process_function` will usually rewrite [`Override`] expressions and those /// that use their values into pre-emitted expressions, leaving any [`Emit`] /// statements that cover them invalid. /// /// This function rewrites all [`Emit`] statements into zero or more new /// [`Emit`] statements covering only those expressions in the original range /// that are not pre-emitted. /// /// [`Emit`]: Statement::Emit /// [`needs_pre_emit`]: Expression::needs_pre_emit /// [`Override`]: Expression::Override fn filter_emits_in_block(block: &mut Block, expressions: &Arena) { let original = mem::replace(block, Block::with_capacity(block.len())); for (stmt, span) in original.span_into_iter() { match stmt { Statement::Emit(range) => { let mut current = None; for expr_h in range { if expressions[expr_h].needs_pre_emit() { if let Some((first, last)) = current { block.push(Statement::Emit(Range::new_from_bounds(first, last)), span); } current = None; } else if let Some((_, ref mut last)) = current { *last = expr_h; } else { current = Some((expr_h, expr_h)); } } if let Some((first, last)) = current { block.push(Statement::Emit(Range::new_from_bounds(first, last)), span); } } Statement::Block(mut child) => { filter_emits_in_block(&mut child, expressions); block.push(Statement::Block(child), span); } Statement::If { condition, mut accept, mut reject, } => { filter_emits_in_block(&mut accept, expressions); filter_emits_in_block(&mut reject, expressions); block.push( Statement::If { condition, accept, reject, }, span, ); } Statement::Switch { selector, mut cases, } => { for case in &mut cases { filter_emits_in_block(&mut case.body, expressions); } block.push(Statement::Switch { selector, cases }, span); } Statement::Loop { mut body, mut continuing, break_if, } => { filter_emits_in_block(&mut body, expressions); filter_emits_in_block(&mut continuing, expressions); block.push( Statement::Loop { body, continuing, break_if, }, span, ); } stmt => block.push(stmt.clone(), span), } } } fn map_value_to_literal(value: f64, scalar: Scalar) -> Result { // note that in rust 0.0 == -0.0 match scalar { Scalar::BOOL => { // https://webidl.spec.whatwg.org/#js-boolean let value = value != 0.0 && !value.is_nan(); Ok(Literal::Bool(value)) } Scalar::I32 => { // https://webidl.spec.whatwg.org/#js-long if !value.is_finite() { return Err(PipelineConstantError::SrcNeedsToBeFinite); } let value = value.trunc(); if value < f64::from(i32::MIN) || value > f64::from(i32::MAX) { return Err(PipelineConstantError::DstRangeTooSmall); } let value = value as i32; Ok(Literal::I32(value)) } Scalar::U32 => { // https://webidl.spec.whatwg.org/#js-unsigned-long if !value.is_finite() { return Err(PipelineConstantError::SrcNeedsToBeFinite); } let value = value.trunc(); if value < f64::from(u32::MIN) || value > f64::from(u32::MAX) { return Err(PipelineConstantError::DstRangeTooSmall); } let value = value as u32; Ok(Literal::U32(value)) } Scalar::F16 => { // https://webidl.spec.whatwg.org/#js-float if !value.is_finite() { return Err(PipelineConstantError::SrcNeedsToBeFinite); } let value = half::f16::from_f64(value); if !value.is_finite() { return Err(PipelineConstantError::DstRangeTooSmall); } Ok(Literal::F16(value)) } Scalar::F32 => { // https://webidl.spec.whatwg.org/#js-float if !value.is_finite() { return Err(PipelineConstantError::SrcNeedsToBeFinite); } let value = value as f32; if !value.is_finite() { return Err(PipelineConstantError::DstRangeTooSmall); } Ok(Literal::F32(value)) } Scalar::F64 => { // https://webidl.spec.whatwg.org/#js-double if !value.is_finite() { return Err(PipelineConstantError::SrcNeedsToBeFinite); } Ok(Literal::F64(value)) } Scalar::ABSTRACT_FLOAT | Scalar::ABSTRACT_INT => { unreachable!("abstract values should not be validated out of override processing") } _ => unreachable!("unrecognized scalar type for override"), } } #[test] fn test_map_value_to_literal() { let bool_test_cases = [ (0.0, false), (-0.0, false), (f64::NAN, false), (1.0, true), (f64::INFINITY, true), (f64::NEG_INFINITY, true), ]; for (value, out) in bool_test_cases { let res = Ok(Literal::Bool(out)); assert_eq!(map_value_to_literal(value, Scalar::BOOL), res); } for scalar in [Scalar::I32, Scalar::U32, Scalar::F32, Scalar::F64] { for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] { let res = Err(PipelineConstantError::SrcNeedsToBeFinite); assert_eq!(map_value_to_literal(value, scalar), res); } } // i32 assert_eq!( map_value_to_literal(f64::from(i32::MIN), Scalar::I32), Ok(Literal::I32(i32::MIN)) ); assert_eq!( map_value_to_literal(f64::from(i32::MAX), Scalar::I32), Ok(Literal::I32(i32::MAX)) ); assert_eq!( map_value_to_literal(f64::from(i32::MIN) - 1.0, Scalar::I32), Err(PipelineConstantError::DstRangeTooSmall) ); assert_eq!( map_value_to_literal(f64::from(i32::MAX) + 1.0, Scalar::I32), Err(PipelineConstantError::DstRangeTooSmall) ); // u32 assert_eq!( map_value_to_literal(f64::from(u32::MIN), Scalar::U32), Ok(Literal::U32(u32::MIN)) ); assert_eq!( map_value_to_literal(f64::from(u32::MAX), Scalar::U32), Ok(Literal::U32(u32::MAX)) ); assert_eq!( map_value_to_literal(f64::from(u32::MIN) - 1.0, Scalar::U32), Err(PipelineConstantError::DstRangeTooSmall) ); assert_eq!( map_value_to_literal(f64::from(u32::MAX) + 1.0, Scalar::U32), Err(PipelineConstantError::DstRangeTooSmall) ); // f32 assert_eq!( map_value_to_literal(f64::from(f32::MIN), Scalar::F32), Ok(Literal::F32(f32::MIN)) ); assert_eq!( map_value_to_literal(f64::from(f32::MAX), Scalar::F32), Ok(Literal::F32(f32::MAX)) ); assert_eq!( map_value_to_literal(-f64::from_bits(0x47efffffefffffff), Scalar::F32), Ok(Literal::F32(f32::MIN)) ); assert_eq!( map_value_to_literal(f64::from_bits(0x47efffffefffffff), Scalar::F32), Ok(Literal::F32(f32::MAX)) ); assert_eq!( map_value_to_literal(-f64::from_bits(0x47effffff0000000), Scalar::F32), Err(PipelineConstantError::DstRangeTooSmall) ); assert_eq!( map_value_to_literal(f64::from_bits(0x47effffff0000000), Scalar::F32), Err(PipelineConstantError::DstRangeTooSmall) ); // f64 assert_eq!( map_value_to_literal(f64::MIN, Scalar::F64), Ok(Literal::F64(f64::MIN)) ); assert_eq!( map_value_to_literal(f64::MAX, Scalar::F64), Ok(Literal::F64(f64::MAX)) ); } ================================================ FILE: naga/src/back/spv/block.rs ================================================ /*! Implementations for `BlockContext` methods. */ use alloc::vec::Vec; use arrayvec::ArrayVec; use spirv::Word; use super::{ helpers::map_storage_class, index::BoundsCheckResult, selection::Selection, Block, BlockContext, Dimension, Error, IdGenerator, Instruction, LocalType, LookupType, NumericType, ResultMember, WrappedFunction, Writer, WriterFlags, }; use crate::{ arena::Handle, back::spv::helpers::is_uniform_matcx2_struct_member_access, proc::index::GuardedIndex, Statement, }; fn get_dimension(type_inner: &crate::TypeInner) -> Dimension { match *type_inner { crate::TypeInner::Scalar(_) => Dimension::Scalar, crate::TypeInner::Vector { .. } => Dimension::Vector, crate::TypeInner::Matrix { .. } => Dimension::Matrix, crate::TypeInner::CooperativeMatrix { .. } => Dimension::CooperativeMatrix, _ => unreachable!(), } } /// How to derive the type of `OpAccessChain` instructions from Naga IR. /// /// Most of the time, we compile Naga IR to SPIR-V instructions whose result /// types are simply the direct SPIR-V analog of the Naga IR's. But in some /// cases, the Naga IR and SPIR-V types need to diverge. /// /// This enum specifies how [`BlockContext::write_access_chain`] should /// choose a SPIR-V result type for the `OpAccessChain` it generates, based on /// the type of the given Naga IR [`Expression`] it's generating code for. /// /// [`Expression`]: crate::Expression #[derive(Copy, Clone)] enum AccessTypeAdjustment { /// No adjustment needed: the SPIR-V type should be the direct /// analog of the Naga IR expression type. /// /// For most access chains, this is the right thing: the Naga IR access /// expression produces a [`Pointer`] to the element / component, and the /// SPIR-V `OpAccessChain` instruction does the same. /// /// [`Pointer`]: crate::TypeInner::Pointer None, /// The SPIR-V type should be an `OpPointer` to the direct analog of the /// Naga IR expression's type. /// /// This is necessary for indexing binding arrays in the [`Handle`] address /// space: /// /// - In Naga IR, referencing a binding array [`GlobalVariable`] in the /// [`Handle`] address space produces a value of type [`BindingArray`], /// not a pointer to such. And [`Access`] and [`AccessIndex`] expressions /// operate on handle binding arrays by value, and produce handle values, /// not pointers. /// /// - In SPIR-V, a binding array `OpVariable` produces a pointer to an /// array, and `OpAccessChain` instructions operate on pointers, /// regardless of whether the elements are opaque types or not. /// /// See also the documentation for [`BindingArray`]. /// /// [`Handle`]: crate::AddressSpace::Handle /// [`GlobalVariable`]: crate::GlobalVariable /// [`BindingArray`]: crate::TypeInner::BindingArray /// [`Access`]: crate::Expression::Access /// [`AccessIndex`]: crate::Expression::AccessIndex IntroducePointer(spirv::StorageClass), /// The SPIR-V type should be an `OpPointer` to the std140 layout /// compatible variant of the Naga IR expression's base type. /// /// This is used when accessing a type through an [`AddressSpace::Uniform`] /// pointer in cases where the original type is incompatible with std140 /// layout requirements and we have therefore declared the uniform to be of /// an alternative std140 compliant type. /// /// [`AddressSpace::Uniform`]: crate::AddressSpace::Uniform UseStd140CompatType, } /// The results of emitting code for a left-hand-side expression. /// /// On success, `write_access_chain` returns one of these. enum ExpressionPointer { /// The pointer to the expression's value is available, as the value of the /// expression with the given id. Ready { pointer_id: Word }, /// The access expression must be conditional on the value of `condition`, a boolean /// expression that is true if all indices are in bounds. If `condition` is true, then /// `access` is an `OpAccessChain` instruction that will compute a pointer to the /// expression's value. If `condition` is false, then executing `access` would be /// undefined behavior. Conditional { condition: Word, access: Instruction, }, } /// The termination statement to be added to the end of the block enum BlockExit { /// Generates an OpReturn (void return) Return, /// Generates an OpBranch to the specified block Branch { /// The branch target block target: Word, }, /// Translates a loop `break if` into an `OpBranchConditional` to the /// merge block if true (the merge block is passed through [`LoopContext::break_id`] /// or else to the loop header (passed through [`preamble_id`]) /// /// [`preamble_id`]: Self::BreakIf::preamble_id BreakIf { /// The condition of the `break if` condition: Handle, /// The loop header block id preamble_id: Word, }, } /// What code generation did with a provided [`BlockExit`] value. /// /// A function that accepts a [`BlockExit`] argument should return a value of /// this type, to indicate whether the code it generated ended up using the /// provided exit, or ignored it and did a non-local exit of some other kind /// (say, [`Break`] or [`Continue`]). Some callers must use this information to /// decide whether to generate the target block at all. /// /// [`Break`]: Statement::Break /// [`Continue`]: Statement::Continue #[must_use] enum BlockExitDisposition { /// The generated code used the provided `BlockExit` value. If it included a /// block label, the caller should be sure to actually emit the block it /// refers to. Used, /// The generated code did not use the provided `BlockExit` value. If it /// included a block label, the caller should not bother to actually emit /// the block it refers to, unless it knows the block is needed for /// something else. Discarded, } #[derive(Clone, Copy, Default)] struct LoopContext { continuing_id: Option, break_id: Option, } #[derive(Debug)] pub(crate) struct DebugInfoInner<'a> { pub source_code: &'a str, pub source_file_id: Word, } impl Writer { // Flip Y coordinate to adjust for coordinate space difference // between SPIR-V and our IR. // The `position_id` argument is a pointer to a `vecN`, // whose `y` component we will negate. fn write_epilogue_position_y_flip( &mut self, position_id: Word, body: &mut Vec, ) -> Result<(), Error> { let float_ptr_type_id = self.get_f32_pointer_type_id(spirv::StorageClass::Output); let index_y_id = self.get_index_constant(1); let access_id = self.id_gen.next(); body.push(Instruction::access_chain( float_ptr_type_id, access_id, position_id, &[index_y_id], )); let float_type_id = self.get_f32_type_id(); let load_id = self.id_gen.next(); body.push(Instruction::load(float_type_id, load_id, access_id, None)); let neg_id = self.id_gen.next(); body.push(Instruction::unary( spirv::Op::FNegate, float_type_id, neg_id, load_id, )); body.push(Instruction::store(access_id, neg_id, None)); Ok(()) } // Clamp fragment depth between 0 and 1. fn write_epilogue_frag_depth_clamp( &mut self, frag_depth_id: Word, body: &mut Vec, ) -> Result<(), Error> { let float_type_id = self.get_f32_type_id(); let zero_scalar_id = self.get_constant_scalar(crate::Literal::F32(0.0)); let one_scalar_id = self.get_constant_scalar(crate::Literal::F32(1.0)); let original_id = self.id_gen.next(); body.push(Instruction::load( float_type_id, original_id, frag_depth_id, None, )); let clamp_id = self.id_gen.next(); body.push(Instruction::ext_inst_gl_op( self.gl450_ext_inst_id, spirv::GlslStd450Op::FClamp, float_type_id, clamp_id, &[original_id, zero_scalar_id, one_scalar_id], )); body.push(Instruction::store(frag_depth_id, clamp_id, None)); Ok(()) } fn write_entry_point_return( &mut self, value_id: Word, ir_result: &crate::FunctionResult, result_members: &[ResultMember], body: &mut Vec, ) -> Result { for (index, res_member) in result_members.iter().enumerate() { // This isn't a real builtin, and is handled elsewhere if res_member.built_in == Some(crate::BuiltIn::MeshTaskSize) { return Ok(Instruction::return_value(value_id)); } let member_value_id = match ir_result.binding { Some(_) => value_id, None => { let member_value_id = self.id_gen.next(); body.push(Instruction::composite_extract( res_member.type_id, member_value_id, value_id, &[index as u32], )); member_value_id } }; self.store_io_with_f16_polyfill(body, res_member.id, member_value_id); match res_member.built_in { Some(crate::BuiltIn::Position { .. }) if self.flags.contains(WriterFlags::ADJUST_COORDINATE_SPACE) => { self.write_epilogue_position_y_flip(res_member.id, body)?; } Some(crate::BuiltIn::FragDepth) if self.flags.contains(WriterFlags::CLAMP_FRAG_DEPTH) => { self.write_epilogue_frag_depth_clamp(res_member.id, body)?; } _ => {} } } Ok(Instruction::return_void()) } } impl BlockContext<'_> { /// Generates code to ensure that a loop is bounded. Should be called immediately /// after adding the OpLoopMerge instruction to `block`. This function will /// [`consume()`](crate::back::spv::Function::consume) `block` and append its /// instructions to a new [`Block`], which will be returned to the caller for it to /// consumed prior to writing the loop body. /// /// Additionally this function will populate [`force_loop_bounding_vars`](crate::back::spv::Function::force_loop_bounding_vars), /// ensuring that [`Function::to_words()`](crate::back::spv::Function::to_words) will /// declare the required variables. /// /// See [`crate::back::msl::Writer::gen_force_bounded_loop_statements`] for details /// of why this is required. fn write_force_bounded_loop_instructions(&mut self, mut block: Block, merge_id: Word) -> Block { let uint_type_id = self.writer.get_u32_type_id(); let uint2_type_id = self.writer.get_vec2u_type_id(); let uint2_ptr_type_id = self .writer .get_vec2u_pointer_type_id(spirv::StorageClass::Function); let bool_type_id = self.writer.get_bool_type_id(); let bool2_type_id = self.writer.get_vec2_bool_type_id(); let zero_uint_const_id = self.writer.get_constant_scalar(crate::Literal::U32(0)); let zero_uint2_const_id = self.writer.get_constant_composite( LookupType::Local(LocalType::Numeric(NumericType::Vector { size: crate::VectorSize::Bi, scalar: crate::Scalar::U32, })), &[zero_uint_const_id, zero_uint_const_id], ); let one_uint_const_id = self.writer.get_constant_scalar(crate::Literal::U32(1)); let max_uint_const_id = self .writer .get_constant_scalar(crate::Literal::U32(u32::MAX)); let max_uint2_const_id = self.writer.get_constant_composite( LookupType::Local(LocalType::Numeric(NumericType::Vector { size: crate::VectorSize::Bi, scalar: crate::Scalar::U32, })), &[max_uint_const_id, max_uint_const_id], ); let loop_counter_var_id = self.gen_id(); if self.writer.flags.contains(WriterFlags::DEBUG) { self.writer .debugs .push(Instruction::name(loop_counter_var_id, "loop_bound")); } let var = super::LocalVariable { id: loop_counter_var_id, instruction: Instruction::variable( uint2_ptr_type_id, loop_counter_var_id, spirv::StorageClass::Function, Some(max_uint2_const_id), ), }; self.function.force_loop_bounding_vars.push(var); let break_if_block = self.gen_id(); self.function .consume(block, Instruction::branch(break_if_block)); block = Block::new(break_if_block); // Load the current loop counter value from its variable. We use a vec2 to // simulate a 64-bit counter. let load_id = self.gen_id(); block.body.push(Instruction::load( uint2_type_id, load_id, loop_counter_var_id, None, )); // If both the high and low u32s have reached 0 then break. ie // if (all(eq(loop_counter, vec2(0)))) { break; } let eq_id = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::IEqual, bool2_type_id, eq_id, zero_uint2_const_id, load_id, )); let all_eq_id = self.gen_id(); block.body.push(Instruction::relational( spirv::Op::All, bool_type_id, all_eq_id, eq_id, )); let inc_counter_block_id = self.gen_id(); block.body.push(Instruction::selection_merge( inc_counter_block_id, spirv::SelectionControl::empty(), )); self.function.consume( block, Instruction::branch_conditional(all_eq_id, merge_id, inc_counter_block_id), ); block = Block::new(inc_counter_block_id); // To simulate a 64-bit counter we always decrement the low u32, and decrement // the high u32 when the low u32 overflows. ie // counter -= vec2(select(0u, 1u, counter.y == 0), 1u); // Count down from u32::MAX rather than up from 0 to avoid hang on // certain Intel drivers. See . let low_id = self.gen_id(); block.body.push(Instruction::composite_extract( uint_type_id, low_id, load_id, &[1], )); let low_overflow_id = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::IEqual, bool_type_id, low_overflow_id, low_id, zero_uint_const_id, )); let carry_bit_id = self.gen_id(); block.body.push(Instruction::select( uint_type_id, carry_bit_id, low_overflow_id, one_uint_const_id, zero_uint_const_id, )); let decrement_id = self.gen_id(); block.body.push(Instruction::composite_construct( uint2_type_id, decrement_id, &[carry_bit_id, one_uint_const_id], )); let result_id = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::ISub, uint2_type_id, result_id, load_id, decrement_id, )); block .body .push(Instruction::store(loop_counter_var_id, result_id, None)); block } /// If `pointer` refers to an access chain that contains a dynamic indexing /// of a two-row matrix in the [`Uniform`] address space, write code to /// access the value returning the ID of the result. Else return None. /// /// Two-row matrices in the uniform address space will have been declared /// using a alternative std140 layout compatible type, where each column is /// a member of a containing struct. As a result, SPIR-V is unable to access /// its columns with a non-constant index. To work around this limitation /// this function will call [`Self::write_checked_load()`] to load the /// matrix itself, which handles conversion from the std140 compatible type /// to the real matrix type. It then calls a [`wrapper function`] to obtain /// the correct column from the matrix, and possibly extracts a component /// from the vector too. /// /// [`Uniform`]: crate::AddressSpace::Uniform /// [`wrapper function`]: super::Writer::write_wrapped_matcx2_get_column fn maybe_write_uniform_matcx2_dynamic_access( &mut self, pointer: Handle, block: &mut Block, ) -> Result, Error> { // If this access chain contains a dynamic matrix access, `pointer` is // either a pointer to a vector (the column) or a scalar (a component // within the column). In either case grab the pointer to the column, // and remember the component index if there is one. If `pointer` // points to any other type we're not interested. let (column_pointer, component_index) = match self.fun_info[pointer] .ty .inner_with(&self.ir_module.types) .pointer_base_type() { Some(resolution) => match *resolution.inner_with(&self.ir_module.types) { crate::TypeInner::Scalar(_) => match self.ir_function.expressions[pointer] { crate::Expression::Access { base, index } => { (base, Some(GuardedIndex::Expression(index))) } crate::Expression::AccessIndex { base, index } => { (base, Some(GuardedIndex::Known(index))) } _ => return Ok(None), }, crate::TypeInner::Vector { .. } => (pointer, None), _ => return Ok(None), }, None => return Ok(None), }; // Ensure the column is accessed with a dynamic index (i.e. // `Expression::Access`), and grab the pointer to the matrix. let crate::Expression::Access { base: matrix_pointer, index: column_index, } = self.ir_function.expressions[column_pointer] else { return Ok(None); }; // Ensure the matrix pointer is in the uniform address space. let crate::TypeInner::Pointer { base: matrix_pointer_base_type, space: crate::AddressSpace::Uniform, } = *self.fun_info[matrix_pointer] .ty .inner_with(&self.ir_module.types) else { return Ok(None); }; // Ensure the matrix pointer actually points to a Cx2 matrix. let crate::TypeInner::Matrix { columns, rows: rows @ crate::VectorSize::Bi, scalar, } = self.ir_module.types[matrix_pointer_base_type].inner else { return Ok(None); }; let matrix_type_id = self.get_numeric_type_id(NumericType::Matrix { columns, rows, scalar, }); let column_type_id = self.get_numeric_type_id(NumericType::Vector { size: rows, scalar }); let component_type_id = self.get_numeric_type_id(NumericType::Scalar(scalar)); let get_column_function_id = self.writer.wrapped_functions [&WrappedFunction::MatCx2GetColumn { r#type: matrix_pointer_base_type, }]; let matrix_load_id = self.write_checked_load( matrix_pointer, block, AccessTypeAdjustment::None, matrix_type_id, )?; // Naga IR allows the index to be either an I32 or U32 but our wrapper // function expects a U32 argument, so convert it if required. let column_index_id = match *self.fun_info[column_index] .ty .inner_with(&self.ir_module.types) { crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Uint, .. }) => self.cached[column_index], crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Sint, .. }) => { let cast_id = self.gen_id(); let u32_type_id = self.writer.get_u32_type_id(); block.body.push(Instruction::unary( spirv::Op::Bitcast, u32_type_id, cast_id, self.cached[column_index], )); cast_id } _ => return Err(Error::Validation("Matrix access index must be u32 or i32")), }; let column_id = self.gen_id(); block.body.push(Instruction::function_call( column_type_id, column_id, get_column_function_id, &[matrix_load_id, column_index_id], )); let result_id = match component_index { Some(index) => self.write_vector_access( component_type_id, column_pointer, Some(column_id), index, block, )?, None => column_id, }; Ok(Some(result_id)) } /// If `pointer` refers to two-row matrix that is a member of a struct in /// the [`Uniform`] address space, write code to load the matrix returning /// the ID of the result. Else return None. /// /// Two-row matrices that are struct members in the uniform address space /// will have been decomposed such that the struct contains a separate /// vector member for each column of the matrix. This function will load /// each column separately from the containing struct, then composite them /// into the real matrix type. /// /// [`Uniform`]: crate::AddressSpace::Uniform fn maybe_write_load_uniform_matcx2_struct_member( &mut self, pointer: Handle, block: &mut Block, ) -> Result, Error> { // Check this is a uniform address space pointer to a two-row matrix. let crate::TypeInner::Pointer { base: matrix_type, space: space @ crate::AddressSpace::Uniform, } = *self.fun_info[pointer].ty.inner_with(&self.ir_module.types) else { return Ok(None); }; let crate::TypeInner::Matrix { columns, rows: rows @ crate::VectorSize::Bi, scalar, } = self.ir_module.types[matrix_type].inner else { return Ok(None); }; // Check this is a struct member. Note struct members can only be // accessed with `AccessIndex`. let crate::Expression::AccessIndex { base: struct_pointer, index: member_index, } = self.ir_function.expressions[pointer] else { return Ok(None); }; let crate::TypeInner::Pointer { base: struct_type, .. } = *self.fun_info[struct_pointer] .ty .inner_with(&self.ir_module.types) else { return Ok(None); }; let crate::TypeInner::Struct { .. } = self.ir_module.types[struct_type].inner else { return Ok(None); }; let matrix_type_id = self.get_numeric_type_id(NumericType::Matrix { columns, rows, scalar, }); let column_type_id = self.get_numeric_type_id(NumericType::Vector { size: rows, scalar }); let column_pointer_type_id = self.get_pointer_type_id(column_type_id, map_storage_class(space)); let column0_index = self.writer.std140_compat_uniform_types[&struct_type].member_indices [member_index as usize]; let column_indices = (0..columns as u32) .map(|c| self.get_index_constant(column0_index + c)) .collect::>(); // Load each column from the struct, then composite into the real // matrix type. let load_mat_from_struct = |struct_pointer_id: Word, id_gen: &mut IdGenerator, block: &mut Block| -> Word { let mut column_ids: ArrayVec = ArrayVec::new(); for index in &column_indices { let column_pointer_id = id_gen.next(); block.body.push(Instruction::access_chain( column_pointer_type_id, column_pointer_id, struct_pointer_id, &[*index], )); let column_id = id_gen.next(); block.body.push(Instruction::load( column_type_id, column_id, column_pointer_id, None, )); column_ids.push(column_id); } let result_id = id_gen.next(); block.body.push(Instruction::composite_construct( matrix_type_id, result_id, &column_ids, )); result_id }; let result_id = match self.write_access_chain( struct_pointer, block, AccessTypeAdjustment::UseStd140CompatType, )? { ExpressionPointer::Ready { pointer_id } => { load_mat_from_struct(pointer_id, &mut self.writer.id_gen, block) } ExpressionPointer::Conditional { condition, access } => self .write_conditional_indexed_load( matrix_type_id, condition, block, |id_gen, block| { let pointer_id = access.result_id.unwrap(); block.body.push(access); load_mat_from_struct(pointer_id, id_gen, block) }, ), }; Ok(Some(result_id)) } /// Cache an expression for a value. pub(super) fn cache_expression_value( &mut self, expr_handle: Handle, block: &mut Block, ) -> Result<(), Error> { let is_named_expression = self .ir_function .named_expressions .contains_key(&expr_handle); if self.fun_info[expr_handle].ref_count == 0 && !is_named_expression { return Ok(()); } let result_type_id = self.get_expression_type_id(&self.fun_info[expr_handle].ty); let id = match self.ir_function.expressions[expr_handle] { crate::Expression::Literal(literal) => self.writer.get_constant_scalar(literal), crate::Expression::Constant(handle) => { let init = self.ir_module.constants[handle].init; self.writer.constant_ids[init] } crate::Expression::Override(_) => return Err(Error::Override), crate::Expression::ZeroValue(_) => self.writer.get_constant_null(result_type_id), crate::Expression::Compose { ty, ref components } => { self.temp_list.clear(); if self.expression_constness.is_const(expr_handle) { self.temp_list.extend( crate::proc::flatten_compose( ty, components, &self.ir_function.expressions, &self.ir_module.types, ) .map(|component| self.cached[component]), ); self.writer .get_constant_composite(LookupType::Handle(ty), &self.temp_list) } else { self.temp_list .extend(components.iter().map(|&component| self.cached[component])); let id = self.gen_id(); block.body.push(Instruction::composite_construct( result_type_id, id, &self.temp_list, )); id } } crate::Expression::Splat { size, value } => { let value_id = self.cached[value]; let components = &[value_id; 4][..size as usize]; if self.expression_constness.is_const(expr_handle) { let ty = self .writer .get_expression_lookup_type(&self.fun_info[expr_handle].ty); self.writer.get_constant_composite(ty, components) } else { let id = self.gen_id(); block.body.push(Instruction::composite_construct( result_type_id, id, components, )); id } } crate::Expression::Access { base, index } => { let base_ty_inner = self.fun_info[base].ty.inner_with(&self.ir_module.types); match *base_ty_inner { crate::TypeInner::Pointer { .. } | crate::TypeInner::ValuePointer { .. } => { // When we have a chain of `Access` and `AccessIndex` expressions // operating on pointers, we want to generate a single // `OpAccessChain` instruction for the whole chain. Put off // generating any code for this until we find the `Expression` // that actually dereferences the pointer. 0 } _ if self.function.spilled_accesses.contains(base) => { // As far as Naga IR is concerned, this expression does not yield // a pointer (we just checked, above), but this backend spilled it // to a temporary variable, so SPIR-V thinks we're accessing it // via a pointer. // Since the base expression was spilled, mark this access to it // as spilled, too. self.function.spilled_accesses.insert(expr_handle); self.maybe_access_spilled_composite(expr_handle, block, result_type_id)? } crate::TypeInner::Vector { .. } => self.write_vector_access( result_type_id, base, None, GuardedIndex::Expression(index), block, )?, crate::TypeInner::Array { .. } | crate::TypeInner::Matrix { .. } => { // See if `index` is known at compile time. match GuardedIndex::from_expression( index, &self.ir_function.expressions, self.ir_module, ) { GuardedIndex::Known(value) => { // If `index` is known and in bounds, we can just use // `OpCompositeExtract`. // // At the moment, validation rejects programs if this // index is out of bounds, so we don't need bounds checks. // However, that rejection is incorrect, since WGSL says // that `let` bindings are not constant expressions // (#6396). So eventually we will need to emulate bounds // checks here. let id = self.gen_id(); let base_id = self.cached[base]; block.body.push(Instruction::composite_extract( result_type_id, id, base_id, &[value], )); id } GuardedIndex::Expression(_) => { // We are subscripting an array or matrix that is not // behind a pointer, using an index computed at runtime. // SPIR-V has no instructions that do this, so the best we // can do is spill the value to a new temporary variable, // at which point we can get a pointer to that and just // use `OpAccessChain` in the usual way. self.spill_to_internal_variable(base, block); // Since the base was spilled, mark this access to it as // spilled, too. self.function.spilled_accesses.insert(expr_handle); self.maybe_access_spilled_composite( expr_handle, block, result_type_id, )? } } } crate::TypeInner::BindingArray { base: binding_type, .. } => { // Only binding arrays in the `Handle` address space will take // this path, since we handled the `Pointer` case above. let result_id = match self.write_access_chain( expr_handle, block, AccessTypeAdjustment::IntroducePointer( spirv::StorageClass::UniformConstant, ), )? { ExpressionPointer::Ready { pointer_id } => pointer_id, ExpressionPointer::Conditional { .. } => { return Err(Error::FeatureNotImplemented( "Texture array out-of-bounds handling", )); } }; let binding_type_id = self.get_handle_type_id(binding_type); let load_id = self.gen_id(); block.body.push(Instruction::load( binding_type_id, load_id, result_id, None, )); // Subsequent image operations require the image/sampler to be decorated as NonUniform // if the image/sampler binding array was accessed with a non-uniform index // see VUID-RuntimeSpirv-NonUniform-06274 if self.fun_info[index].uniformity.non_uniform_result.is_some() { self.writer .decorate_non_uniform_binding_array_access(load_id)?; } load_id } ref other => { log::error!( "Unable to access base {:?} of type {:?}", self.ir_function.expressions[base], other ); return Err(Error::Validation( "only vectors and arrays may be dynamically indexed by value", )); } } } crate::Expression::AccessIndex { base, index } => { match *self.fun_info[base].ty.inner_with(&self.ir_module.types) { crate::TypeInner::Pointer { .. } | crate::TypeInner::ValuePointer { .. } => { // When we have a chain of `Access` and `AccessIndex` expressions // operating on pointers, we want to generate a single // `OpAccessChain` instruction for the whole chain. Put off // generating any code for this until we find the `Expression` // that actually dereferences the pointer. 0 } _ if self.function.spilled_accesses.contains(base) => { // As far as Naga IR is concerned, this expression does not yield // a pointer (we just checked, above), but this backend spilled it // to a temporary variable, so SPIR-V thinks we're accessing it // via a pointer. // Since the base expression was spilled, mark this access to it // as spilled, too. self.function.spilled_accesses.insert(expr_handle); self.maybe_access_spilled_composite(expr_handle, block, result_type_id)? } crate::TypeInner::Vector { .. } | crate::TypeInner::Matrix { .. } | crate::TypeInner::Array { .. } | crate::TypeInner::Struct { .. } => { // We never need bounds checks here: dynamically sized arrays can // only appear behind pointers, and are thus handled by the // `is_intermediate` case above. Everything else's size is // statically known and checked in validation. let id = self.gen_id(); let base_id = self.cached[base]; block.body.push(Instruction::composite_extract( result_type_id, id, base_id, &[index], )); id } crate::TypeInner::BindingArray { base: binding_type, .. } => { // Only binding arrays in the `Handle` address space will take // this path, since we handled the `Pointer` case above. let result_id = match self.write_access_chain( expr_handle, block, AccessTypeAdjustment::IntroducePointer( spirv::StorageClass::UniformConstant, ), )? { ExpressionPointer::Ready { pointer_id } => pointer_id, ExpressionPointer::Conditional { .. } => { return Err(Error::FeatureNotImplemented( "Texture array out-of-bounds handling", )); } }; let binding_type_id = self.get_handle_type_id(binding_type); let load_id = self.gen_id(); block.body.push(Instruction::load( binding_type_id, load_id, result_id, None, )); load_id } ref other => { log::error!("Unable to access index of {other:?}"); return Err(Error::FeatureNotImplemented("access index for type")); } } } crate::Expression::GlobalVariable(handle) => { self.writer.global_variables[handle].access_id } crate::Expression::Swizzle { size, vector, pattern, } => { let vector_id = self.cached[vector]; self.temp_list.clear(); for &sc in pattern[..size as usize].iter() { self.temp_list.push(sc as Word); } let id = self.gen_id(); block.body.push(Instruction::vector_shuffle( result_type_id, id, vector_id, vector_id, &self.temp_list, )); id } crate::Expression::Unary { op, expr } => { let id = self.gen_id(); let expr_id = self.cached[expr]; let expr_ty_inner = self.fun_info[expr].ty.inner_with(&self.ir_module.types); let spirv_op = match op { crate::UnaryOperator::Negate => match expr_ty_inner.scalar_kind() { Some(crate::ScalarKind::Float) => spirv::Op::FNegate, Some(crate::ScalarKind::Sint) => spirv::Op::SNegate, _ => return Err(Error::Validation("Unexpected kind for negation")), }, crate::UnaryOperator::LogicalNot => spirv::Op::LogicalNot, crate::UnaryOperator::BitwiseNot => spirv::Op::Not, }; block .body .push(Instruction::unary(spirv_op, result_type_id, id, expr_id)); id } crate::Expression::Binary { op, left, right } => { let id = self.gen_id(); let left_id = self.cached[left]; let right_id = self.cached[right]; let left_type_id = self.get_expression_type_id(&self.fun_info[left].ty); let right_type_id = self.get_expression_type_id(&self.fun_info[right].ty); if let Some(function_id) = self.writer .wrapped_functions .get(&WrappedFunction::BinaryOp { op, left_type_id, right_type_id, }) { block.body.push(Instruction::function_call( result_type_id, id, *function_id, &[left_id, right_id], )); } else { let left_ty_inner = self.fun_info[left].ty.inner_with(&self.ir_module.types); let right_ty_inner = self.fun_info[right].ty.inner_with(&self.ir_module.types); let left_dimension = get_dimension(left_ty_inner); let right_dimension = get_dimension(right_ty_inner); let mut reverse_operands = false; let spirv_op = match op { crate::BinaryOperator::Add => match *left_ty_inner { crate::TypeInner::Scalar(scalar) | crate::TypeInner::Vector { scalar, .. } => match scalar.kind { crate::ScalarKind::Float => spirv::Op::FAdd, _ => spirv::Op::IAdd, }, crate::TypeInner::Matrix { columns, rows, scalar, } => { //TODO: why not just rely on `Fadd` for matrices? self.write_matrix_matrix_column_op( block, id, result_type_id, left_id, right_id, columns, rows, scalar.width, spirv::Op::FAdd, ); self.cached[expr_handle] = id; return Ok(()); } crate::TypeInner::CooperativeMatrix { .. } => spirv::Op::FAdd, _ => unimplemented!(), }, crate::BinaryOperator::Subtract => match *left_ty_inner { crate::TypeInner::Scalar(scalar) | crate::TypeInner::Vector { scalar, .. } => match scalar.kind { crate::ScalarKind::Float => spirv::Op::FSub, _ => spirv::Op::ISub, }, crate::TypeInner::Matrix { columns, rows, scalar, } => { self.write_matrix_matrix_column_op( block, id, result_type_id, left_id, right_id, columns, rows, scalar.width, spirv::Op::FSub, ); self.cached[expr_handle] = id; return Ok(()); } crate::TypeInner::CooperativeMatrix { .. } => spirv::Op::FSub, _ => unimplemented!(), }, crate::BinaryOperator::Multiply => { match (left_dimension, right_dimension) { (Dimension::Scalar, Dimension::Vector) => { self.write_vector_scalar_mult( block, id, result_type_id, right_id, left_id, right_ty_inner, ); self.cached[expr_handle] = id; return Ok(()); } (Dimension::Vector, Dimension::Scalar) => { self.write_vector_scalar_mult( block, id, result_type_id, left_id, right_id, left_ty_inner, ); self.cached[expr_handle] = id; return Ok(()); } (Dimension::Vector, Dimension::Matrix) => { spirv::Op::VectorTimesMatrix } (Dimension::Matrix, Dimension::Scalar) | (Dimension::CooperativeMatrix, Dimension::Scalar) => { spirv::Op::MatrixTimesScalar } (Dimension::Scalar, Dimension::Matrix) | (Dimension::Scalar, Dimension::CooperativeMatrix) => { reverse_operands = true; spirv::Op::MatrixTimesScalar } (Dimension::Matrix, Dimension::Vector) => { spirv::Op::MatrixTimesVector } (Dimension::Matrix, Dimension::Matrix) => { spirv::Op::MatrixTimesMatrix } (Dimension::Vector, Dimension::Vector) | (Dimension::Scalar, Dimension::Scalar) if left_ty_inner.scalar_kind() == Some(crate::ScalarKind::Float) => { spirv::Op::FMul } (Dimension::Vector, Dimension::Vector) | (Dimension::Scalar, Dimension::Scalar) => spirv::Op::IMul, (Dimension::CooperativeMatrix, Dimension::CooperativeMatrix) //Note: technically can do `FMul` but IR doesn't have matrix per-component multiplication | (Dimension::CooperativeMatrix, _) | (_, Dimension::CooperativeMatrix) => { unimplemented!() } } } crate::BinaryOperator::Divide => match left_ty_inner.scalar_kind() { Some(crate::ScalarKind::Sint) => spirv::Op::SDiv, Some(crate::ScalarKind::Uint) => spirv::Op::UDiv, Some(crate::ScalarKind::Float) => spirv::Op::FDiv, _ => unimplemented!(), }, crate::BinaryOperator::Modulo => match left_ty_inner.scalar_kind() { // TODO: handle undefined behavior // if right == 0 return ? see https://github.com/gpuweb/gpuweb/issues/2798 Some(crate::ScalarKind::Float) => spirv::Op::FRem, Some(crate::ScalarKind::Sint | crate::ScalarKind::Uint) => { unreachable!("Should have been handled by wrapped function") } _ => unimplemented!(), }, crate::BinaryOperator::Equal => match left_ty_inner.scalar_kind() { Some(crate::ScalarKind::Sint | crate::ScalarKind::Uint) => { spirv::Op::IEqual } Some(crate::ScalarKind::Float) => spirv::Op::FOrdEqual, Some(crate::ScalarKind::Bool) => spirv::Op::LogicalEqual, _ => unimplemented!(), }, crate::BinaryOperator::NotEqual => match left_ty_inner.scalar_kind() { Some(crate::ScalarKind::Sint | crate::ScalarKind::Uint) => { spirv::Op::INotEqual } Some(crate::ScalarKind::Float) => spirv::Op::FOrdNotEqual, Some(crate::ScalarKind::Bool) => spirv::Op::LogicalNotEqual, _ => unimplemented!(), }, crate::BinaryOperator::Less => match left_ty_inner.scalar_kind() { Some(crate::ScalarKind::Sint) => spirv::Op::SLessThan, Some(crate::ScalarKind::Uint) => spirv::Op::ULessThan, Some(crate::ScalarKind::Float) => spirv::Op::FOrdLessThan, _ => unimplemented!(), }, crate::BinaryOperator::LessEqual => match left_ty_inner.scalar_kind() { Some(crate::ScalarKind::Sint) => spirv::Op::SLessThanEqual, Some(crate::ScalarKind::Uint) => spirv::Op::ULessThanEqual, Some(crate::ScalarKind::Float) => spirv::Op::FOrdLessThanEqual, _ => unimplemented!(), }, crate::BinaryOperator::Greater => match left_ty_inner.scalar_kind() { Some(crate::ScalarKind::Sint) => spirv::Op::SGreaterThan, Some(crate::ScalarKind::Uint) => spirv::Op::UGreaterThan, Some(crate::ScalarKind::Float) => spirv::Op::FOrdGreaterThan, _ => unimplemented!(), }, crate::BinaryOperator::GreaterEqual => match left_ty_inner.scalar_kind() { Some(crate::ScalarKind::Sint) => spirv::Op::SGreaterThanEqual, Some(crate::ScalarKind::Uint) => spirv::Op::UGreaterThanEqual, Some(crate::ScalarKind::Float) => spirv::Op::FOrdGreaterThanEqual, _ => unimplemented!(), }, crate::BinaryOperator::And => match left_ty_inner.scalar_kind() { Some(crate::ScalarKind::Bool) => spirv::Op::LogicalAnd, _ => spirv::Op::BitwiseAnd, }, crate::BinaryOperator::ExclusiveOr => spirv::Op::BitwiseXor, crate::BinaryOperator::InclusiveOr => match left_ty_inner.scalar_kind() { Some(crate::ScalarKind::Bool) => spirv::Op::LogicalOr, _ => spirv::Op::BitwiseOr, }, crate::BinaryOperator::LogicalAnd => spirv::Op::LogicalAnd, crate::BinaryOperator::LogicalOr => spirv::Op::LogicalOr, crate::BinaryOperator::ShiftLeft => spirv::Op::ShiftLeftLogical, crate::BinaryOperator::ShiftRight => match left_ty_inner.scalar_kind() { Some(crate::ScalarKind::Sint) => spirv::Op::ShiftRightArithmetic, Some(crate::ScalarKind::Uint) => spirv::Op::ShiftRightLogical, _ => unimplemented!(), }, }; block.body.push(Instruction::binary( spirv_op, result_type_id, id, if reverse_operands { right_id } else { left_id }, if reverse_operands { left_id } else { right_id }, )); } id } crate::Expression::Math { fun, arg, arg1, arg2, arg3, } => { use crate::MathFunction as Mf; enum MathOp { Ext(spirv::GlslStd450Op), Custom(Instruction), } let arg0_id = self.cached[arg]; let arg_ty = self.fun_info[arg].ty.inner_with(&self.ir_module.types); let arg_scalar_kind = arg_ty.scalar_kind(); let arg1_id = match arg1 { Some(handle) => self.cached[handle], None => 0, }; let arg2_id = match arg2 { Some(handle) => self.cached[handle], None => 0, }; let arg3_id = match arg3 { Some(handle) => self.cached[handle], None => 0, }; let id = self.gen_id(); let math_op = match fun { // comparison Mf::Abs => { match arg_scalar_kind { Some(crate::ScalarKind::Float) => { MathOp::Ext(spirv::GlslStd450Op::FAbs) } Some(crate::ScalarKind::Sint) => MathOp::Ext(spirv::GlslStd450Op::SAbs), Some(crate::ScalarKind::Uint) => { MathOp::Custom(Instruction::unary( spirv::Op::CopyObject, // do nothing result_type_id, id, arg0_id, )) } other => unimplemented!("Unexpected abs({:?})", other), } } Mf::Min => MathOp::Ext(match arg_scalar_kind { Some(crate::ScalarKind::Float) => spirv::GlslStd450Op::FMin, Some(crate::ScalarKind::Sint) => spirv::GlslStd450Op::SMin, Some(crate::ScalarKind::Uint) => spirv::GlslStd450Op::UMin, other => unimplemented!("Unexpected min({:?})", other), }), Mf::Max => MathOp::Ext(match arg_scalar_kind { Some(crate::ScalarKind::Float) => spirv::GlslStd450Op::FMax, Some(crate::ScalarKind::Sint) => spirv::GlslStd450Op::SMax, Some(crate::ScalarKind::Uint) => spirv::GlslStd450Op::UMax, other => unimplemented!("Unexpected max({:?})", other), }), Mf::Clamp => match arg_scalar_kind { // Clamp is undefined if min > max. In practice this means it can use a median-of-three // instruction to determine the value. This is fine according to the WGSL spec for float // clamp, but integer clamp _must_ use min-max. As such we write out min/max. Some(crate::ScalarKind::Float) => MathOp::Ext(spirv::GlslStd450Op::FClamp), Some(_) => { let (min_op, max_op) = match arg_scalar_kind { Some(crate::ScalarKind::Sint) => { (spirv::GlslStd450Op::SMin, spirv::GlslStd450Op::SMax) } Some(crate::ScalarKind::Uint) => { (spirv::GlslStd450Op::UMin, spirv::GlslStd450Op::UMax) } _ => unreachable!(), }; let max_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, max_op, result_type_id, max_id, &[arg0_id, arg1_id], )); MathOp::Custom(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, min_op, result_type_id, id, &[max_id, arg2_id], )) } other => unimplemented!("Unexpected max({:?})", other), }, Mf::Saturate => { let (maybe_size, scalar) = match *arg_ty { crate::TypeInner::Vector { size, scalar } => (Some(size), scalar), crate::TypeInner::Scalar(scalar) => (None, scalar), ref other => unimplemented!("Unexpected saturate({:?})", other), }; let scalar = crate::Scalar::float(scalar.width); let mut arg1_id = self.writer.get_constant_scalar_with(0, scalar)?; let mut arg2_id = self.writer.get_constant_scalar_with(1, scalar)?; if let Some(size) = maybe_size { let ty = LocalType::Numeric(NumericType::Vector { size, scalar }).into(); self.temp_list.clear(); self.temp_list.resize(size as _, arg1_id); arg1_id = self.writer.get_constant_composite(ty, &self.temp_list); self.temp_list.fill(arg2_id); arg2_id = self.writer.get_constant_composite(ty, &self.temp_list); } MathOp::Custom(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, spirv::GlslStd450Op::FClamp, result_type_id, id, &[arg0_id, arg1_id, arg2_id], )) } // trigonometry Mf::Sin => MathOp::Ext(spirv::GlslStd450Op::Sin), Mf::Sinh => MathOp::Ext(spirv::GlslStd450Op::Sinh), Mf::Asin => MathOp::Ext(spirv::GlslStd450Op::Asin), Mf::Cos => MathOp::Ext(spirv::GlslStd450Op::Cos), Mf::Cosh => MathOp::Ext(spirv::GlslStd450Op::Cosh), Mf::Acos => MathOp::Ext(spirv::GlslStd450Op::Acos), Mf::Tan => MathOp::Ext(spirv::GlslStd450Op::Tan), Mf::Tanh => MathOp::Ext(spirv::GlslStd450Op::Tanh), Mf::Atan => MathOp::Ext(spirv::GlslStd450Op::Atan), Mf::Atan2 => MathOp::Ext(spirv::GlslStd450Op::Atan2), Mf::Asinh => MathOp::Ext(spirv::GlslStd450Op::Asinh), Mf::Acosh => MathOp::Ext(spirv::GlslStd450Op::Acosh), Mf::Atanh => MathOp::Ext(spirv::GlslStd450Op::Atanh), Mf::Radians => MathOp::Ext(spirv::GlslStd450Op::Radians), Mf::Degrees => MathOp::Ext(spirv::GlslStd450Op::Degrees), // decomposition Mf::Ceil => MathOp::Ext(spirv::GlslStd450Op::Ceil), Mf::Round => MathOp::Ext(spirv::GlslStd450Op::RoundEven), Mf::Floor => MathOp::Ext(spirv::GlslStd450Op::Floor), Mf::Fract => MathOp::Ext(spirv::GlslStd450Op::Fract), Mf::Trunc => MathOp::Ext(spirv::GlslStd450Op::Trunc), Mf::Modf => MathOp::Ext(spirv::GlslStd450Op::ModfStruct), Mf::Frexp => MathOp::Ext(spirv::GlslStd450Op::FrexpStruct), Mf::Ldexp => MathOp::Ext(spirv::GlslStd450Op::Ldexp), // geometry Mf::Dot => match *self.fun_info[arg].ty.inner_with(&self.ir_module.types) { crate::TypeInner::Vector { scalar: crate::Scalar { kind: crate::ScalarKind::Float, .. }, .. } => MathOp::Custom(Instruction::binary( spirv::Op::Dot, result_type_id, id, arg0_id, arg1_id, )), // TODO: consider using integer dot product if VK_KHR_shader_integer_dot_product is available crate::TypeInner::Vector { size, .. } => { self.write_dot_product( id, result_type_id, arg0_id, arg1_id, size as u32, block, |result_id, composite_id, index| { Instruction::composite_extract( result_type_id, result_id, composite_id, &[index], ) }, ); self.cached[expr_handle] = id; return Ok(()); } _ => unreachable!( "Correct TypeInner for dot product should be already validated" ), }, fun @ (Mf::Dot4I8Packed | Mf::Dot4U8Packed) => { if self .writer .require_all(&[ spirv::Capability::DotProduct, spirv::Capability::DotProductInput4x8BitPacked, ]) .is_ok() { // Write optimized code using `PackedVectorFormat4x8Bit`. if self.writer.lang_version() < (1, 6) { // SPIR-V 1.6 supports the required capabilities natively, so the extension // is only required for earlier versions. See right column of // . self.writer.use_extension("SPV_KHR_integer_dot_product"); } let op = match fun { Mf::Dot4I8Packed => spirv::Op::SDot, Mf::Dot4U8Packed => spirv::Op::UDot, _ => unreachable!(), }; block.body.push(Instruction::ternary( op, result_type_id, id, arg0_id, arg1_id, spirv::PackedVectorFormat::PackedVectorFormat4x8Bit as Word, )); } else { // Fall back to a polyfill since `PackedVectorFormat4x8Bit` is not available. let (extract_op, arg0_id, arg1_id) = match fun { Mf::Dot4U8Packed => (spirv::Op::BitFieldUExtract, arg0_id, arg1_id), Mf::Dot4I8Packed => { // Convert both packed arguments to signed integers so that we can apply the // `BitFieldSExtract` operation on them in `write_dot_product` below. let new_arg0_id = self.gen_id(); block.body.push(Instruction::unary( spirv::Op::Bitcast, result_type_id, new_arg0_id, arg0_id, )); let new_arg1_id = self.gen_id(); block.body.push(Instruction::unary( spirv::Op::Bitcast, result_type_id, new_arg1_id, arg1_id, )); (spirv::Op::BitFieldSExtract, new_arg0_id, new_arg1_id) } _ => unreachable!(), }; let eight = self.writer.get_constant_scalar(crate::Literal::U32(8)); const VEC_LENGTH: u8 = 4; let bit_shifts: [_; VEC_LENGTH as usize] = core::array::from_fn(|index| { self.writer .get_constant_scalar(crate::Literal::U32(index as u32 * 8)) }); self.write_dot_product( id, result_type_id, arg0_id, arg1_id, VEC_LENGTH as Word, block, |result_id, composite_id, index| { Instruction::ternary( extract_op, result_type_id, result_id, composite_id, bit_shifts[index as usize], eight, ) }, ); } self.cached[expr_handle] = id; return Ok(()); } Mf::Outer => MathOp::Custom(Instruction::binary( spirv::Op::OuterProduct, result_type_id, id, arg0_id, arg1_id, )), Mf::Cross => MathOp::Ext(spirv::GlslStd450Op::Cross), Mf::Distance => MathOp::Ext(spirv::GlslStd450Op::Distance), Mf::Length => MathOp::Ext(spirv::GlslStd450Op::Length), Mf::Normalize => MathOp::Ext(spirv::GlslStd450Op::Normalize), Mf::FaceForward => MathOp::Ext(spirv::GlslStd450Op::FaceForward), Mf::Reflect => MathOp::Ext(spirv::GlslStd450Op::Reflect), Mf::Refract => MathOp::Ext(spirv::GlslStd450Op::Refract), // exponent Mf::Exp => MathOp::Ext(spirv::GlslStd450Op::Exp), Mf::Exp2 => MathOp::Ext(spirv::GlslStd450Op::Exp2), Mf::Log => MathOp::Ext(spirv::GlslStd450Op::Log), Mf::Log2 => MathOp::Ext(spirv::GlslStd450Op::Log2), Mf::Pow => MathOp::Ext(spirv::GlslStd450Op::Pow), // computational Mf::Sign => MathOp::Ext(match arg_scalar_kind { Some(crate::ScalarKind::Float) => spirv::GlslStd450Op::FSign, Some(crate::ScalarKind::Sint) => spirv::GlslStd450Op::SSign, other => unimplemented!("Unexpected sign({:?})", other), }), Mf::Fma => MathOp::Ext(spirv::GlslStd450Op::Fma), Mf::Mix => { let selector = arg2.unwrap(); let selector_ty = self.fun_info[selector].ty.inner_with(&self.ir_module.types); match (arg_ty, selector_ty) { // if the selector is a scalar, we need to splat it ( &crate::TypeInner::Vector { size, .. }, &crate::TypeInner::Scalar(scalar), ) => { let selector_type_id = self.get_numeric_type_id(NumericType::Vector { size, scalar }); self.temp_list.clear(); self.temp_list.resize(size as usize, arg2_id); let selector_id = self.gen_id(); block.body.push(Instruction::composite_construct( selector_type_id, selector_id, &self.temp_list, )); MathOp::Custom(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, spirv::GlslStd450Op::FMix, result_type_id, id, &[arg0_id, arg1_id, selector_id], )) } _ => MathOp::Ext(spirv::GlslStd450Op::FMix), } } Mf::Step => MathOp::Ext(spirv::GlslStd450Op::Step), Mf::SmoothStep => MathOp::Ext(spirv::GlslStd450Op::SmoothStep), Mf::Sqrt => MathOp::Ext(spirv::GlslStd450Op::Sqrt), Mf::InverseSqrt => MathOp::Ext(spirv::GlslStd450Op::InverseSqrt), Mf::Inverse => MathOp::Ext(spirv::GlslStd450Op::MatrixInverse), Mf::Transpose => MathOp::Custom(Instruction::unary( spirv::Op::Transpose, result_type_id, id, arg0_id, )), Mf::Determinant => MathOp::Ext(spirv::GlslStd450Op::Determinant), Mf::QuantizeToF16 => MathOp::Custom(Instruction::unary( spirv::Op::QuantizeToF16, result_type_id, id, arg0_id, )), Mf::ReverseBits => MathOp::Custom(Instruction::unary( spirv::Op::BitReverse, result_type_id, id, arg0_id, )), Mf::CountTrailingZeros => { let uint_id = match *arg_ty { crate::TypeInner::Vector { size, scalar } => { let ty = LocalType::Numeric(NumericType::Vector { size, scalar }).into(); self.temp_list.clear(); self.temp_list.resize( size as _, self.writer .get_constant_scalar_with(scalar.width * 8, scalar)?, ); self.writer.get_constant_composite(ty, &self.temp_list) } crate::TypeInner::Scalar(scalar) => self .writer .get_constant_scalar_with(scalar.width * 8, scalar)?, _ => unreachable!(), }; let lsb_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, spirv::GlslStd450Op::FindILsb, result_type_id, lsb_id, &[arg0_id], )); MathOp::Custom(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, spirv::GlslStd450Op::UMin, result_type_id, id, &[uint_id, lsb_id], )) } Mf::CountLeadingZeros => { let (int_type_id, int_id, width) = match *arg_ty { crate::TypeInner::Vector { size, scalar } => { let ty = LocalType::Numeric(NumericType::Vector { size, scalar }).into(); self.temp_list.clear(); self.temp_list.resize( size as _, self.writer .get_constant_scalar_with(scalar.width * 8 - 1, scalar)?, ); ( self.get_type_id(ty), self.writer.get_constant_composite(ty, &self.temp_list), scalar.width, ) } crate::TypeInner::Scalar(scalar) => ( self.get_numeric_type_id(NumericType::Scalar(scalar)), self.writer .get_constant_scalar_with(scalar.width * 8 - 1, scalar)?, scalar.width, ), _ => unreachable!(), }; if width != 4 { unreachable!("This is validated out until a polyfill is implemented. https://github.com/gfx-rs/wgpu/issues/5276"); }; let msb_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, if width != 4 { spirv::GlslStd450Op::FindILsb } else { spirv::GlslStd450Op::FindUMsb }, int_type_id, msb_id, &[arg0_id], )); MathOp::Custom(Instruction::binary( spirv::Op::ISub, result_type_id, id, int_id, msb_id, )) } Mf::CountOneBits => MathOp::Custom(Instruction::unary( spirv::Op::BitCount, result_type_id, id, arg0_id, )), Mf::ExtractBits => { let op = match arg_scalar_kind { Some(crate::ScalarKind::Uint) => spirv::Op::BitFieldUExtract, Some(crate::ScalarKind::Sint) => spirv::Op::BitFieldSExtract, other => unimplemented!("Unexpected sign({:?})", other), }; // The behavior of ExtractBits is undefined when offset + count > bit_width. We need // to first sanitize the offset and count first. If we don't do this, AMD and Intel // will return out-of-spec values if the extracted range is not within the bit width. // // This encodes the exact formula specified by the wgsl spec: // https://gpuweb.github.io/gpuweb/wgsl/#extractBits-unsigned-builtin // // w = sizeof(x) * 8 // o = min(offset, w) // tmp = w - o // c = min(count, tmp) // // bitfieldExtract(x, o, c) let bit_width = arg_ty.scalar_width().unwrap() * 8; let width_constant = self .writer .get_constant_scalar(crate::Literal::U32(bit_width as u32)); let u32_type = self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::U32)); // o = min(offset, w) let offset_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, spirv::GlslStd450Op::UMin, u32_type, offset_id, &[arg1_id, width_constant], )); // tmp = w - o let max_count_id = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::ISub, u32_type, max_count_id, width_constant, offset_id, )); // c = min(count, tmp) let count_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, spirv::GlslStd450Op::UMin, u32_type, count_id, &[arg2_id, max_count_id], )); MathOp::Custom(Instruction::ternary( op, result_type_id, id, arg0_id, offset_id, count_id, )) } Mf::InsertBits => { // The behavior of InsertBits has the same undefined behavior as ExtractBits. let bit_width = arg_ty.scalar_width().unwrap() * 8; let width_constant = self .writer .get_constant_scalar(crate::Literal::U32(bit_width as u32)); let u32_type = self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::U32)); // o = min(offset, w) let offset_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, spirv::GlslStd450Op::UMin, u32_type, offset_id, &[arg2_id, width_constant], )); // tmp = w - o let max_count_id = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::ISub, u32_type, max_count_id, width_constant, offset_id, )); // c = min(count, tmp) let count_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, spirv::GlslStd450Op::UMin, u32_type, count_id, &[arg3_id, max_count_id], )); MathOp::Custom(Instruction::quaternary( spirv::Op::BitFieldInsert, result_type_id, id, arg0_id, arg1_id, offset_id, count_id, )) } Mf::FirstTrailingBit => MathOp::Ext(spirv::GlslStd450Op::FindILsb), Mf::FirstLeadingBit => { if arg_ty.scalar_width() == Some(4) { let thing = match arg_scalar_kind { Some(crate::ScalarKind::Uint) => spirv::GlslStd450Op::FindUMsb, Some(crate::ScalarKind::Sint) => spirv::GlslStd450Op::FindSMsb, other => unimplemented!("Unexpected firstLeadingBit({:?})", other), }; MathOp::Ext(thing) } else { unreachable!("This is validated out until a polyfill is implemented. https://github.com/gfx-rs/wgpu/issues/5276"); } } Mf::Pack4x8unorm => MathOp::Ext(spirv::GlslStd450Op::PackUnorm4x8), Mf::Pack4x8snorm => MathOp::Ext(spirv::GlslStd450Op::PackSnorm4x8), Mf::Pack2x16float => MathOp::Ext(spirv::GlslStd450Op::PackHalf2x16), Mf::Pack2x16unorm => MathOp::Ext(spirv::GlslStd450Op::PackUnorm2x16), Mf::Pack2x16snorm => MathOp::Ext(spirv::GlslStd450Op::PackSnorm2x16), fun @ (Mf::Pack4xI8 | Mf::Pack4xU8 | Mf::Pack4xI8Clamp | Mf::Pack4xU8Clamp) => { let is_signed = matches!(fun, Mf::Pack4xI8 | Mf::Pack4xI8Clamp); let should_clamp = matches!(fun, Mf::Pack4xI8Clamp | Mf::Pack4xU8Clamp); let last_instruction = if self.writer.require_all(&[spirv::Capability::Int8]).is_ok() { self.write_pack4x8_optimized( block, result_type_id, arg0_id, id, is_signed, should_clamp, ) } else { self.write_pack4x8_polyfill( block, result_type_id, arg0_id, id, is_signed, should_clamp, ) }; MathOp::Custom(last_instruction) } Mf::Unpack4x8unorm => MathOp::Ext(spirv::GlslStd450Op::UnpackUnorm4x8), Mf::Unpack4x8snorm => MathOp::Ext(spirv::GlslStd450Op::UnpackSnorm4x8), Mf::Unpack2x16float => MathOp::Ext(spirv::GlslStd450Op::UnpackHalf2x16), Mf::Unpack2x16unorm => MathOp::Ext(spirv::GlslStd450Op::UnpackUnorm2x16), Mf::Unpack2x16snorm => MathOp::Ext(spirv::GlslStd450Op::UnpackSnorm2x16), fun @ (Mf::Unpack4xI8 | Mf::Unpack4xU8) => { let is_signed = matches!(fun, Mf::Unpack4xI8); let last_instruction = if self.writer.require_all(&[spirv::Capability::Int8]).is_ok() { self.write_unpack4x8_optimized( block, result_type_id, arg0_id, id, is_signed, ) } else { self.write_unpack4x8_polyfill( block, result_type_id, arg0_id, id, is_signed, ) }; MathOp::Custom(last_instruction) } }; block.body.push(match math_op { MathOp::Ext(op) => Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, op, result_type_id, id, &[arg0_id, arg1_id, arg2_id, arg3_id][..fun.argument_count()], ), MathOp::Custom(inst) => inst, }); id } crate::Expression::LocalVariable(variable) => { if let Some(rq_tracker) = self .function .ray_query_initialization_tracker_variables .get(&variable) { self.ray_query_tracker_expr.insert( expr_handle, super::RayQueryTrackers { initialized_tracker: rq_tracker.id, t_max_tracker: self .function .ray_query_t_max_tracker_variables .get(&variable) .expect("Both trackers are set at the same time.") .id, }, ); } self.function.variables[&variable].id } crate::Expression::Load { pointer } => { self.write_checked_load(pointer, block, AccessTypeAdjustment::None, result_type_id)? } crate::Expression::FunctionArgument(index) => self.function.parameter_id(index), crate::Expression::CallResult(_) | crate::Expression::AtomicResult { .. } | crate::Expression::WorkGroupUniformLoadResult { .. } | crate::Expression::RayQueryProceedResult | crate::Expression::SubgroupBallotResult | crate::Expression::SubgroupOperationResult { .. } => self.cached[expr_handle], crate::Expression::As { expr, kind, convert, } => self.write_as_expression(expr, convert, kind, block, result_type_id)?, crate::Expression::ImageLoad { image, coordinate, array_index, sample, level, } => self.write_image_load( result_type_id, image, coordinate, array_index, level, sample, block, )?, crate::Expression::ImageSample { image, sampler, gather, coordinate, array_index, offset, level, depth_ref, clamp_to_edge, } => self.write_image_sample( result_type_id, image, sampler, gather, coordinate, array_index, offset, level, depth_ref, clamp_to_edge, block, )?, crate::Expression::Select { condition, accept, reject, } => { let id = self.gen_id(); let mut condition_id = self.cached[condition]; let accept_id = self.cached[accept]; let reject_id = self.cached[reject]; let condition_ty = self.fun_info[condition] .ty .inner_with(&self.ir_module.types); let object_ty = self.fun_info[accept].ty.inner_with(&self.ir_module.types); if let ( &crate::TypeInner::Scalar( condition_scalar @ crate::Scalar { kind: crate::ScalarKind::Bool, .. }, ), &crate::TypeInner::Vector { size, .. }, ) = (condition_ty, object_ty) { self.temp_list.clear(); self.temp_list.resize(size as usize, condition_id); let bool_vector_type_id = self.get_numeric_type_id(NumericType::Vector { size, scalar: condition_scalar, }); let id = self.gen_id(); block.body.push(Instruction::composite_construct( bool_vector_type_id, id, &self.temp_list, )); condition_id = id } let instruction = Instruction::select(result_type_id, id, condition_id, accept_id, reject_id); block.body.push(instruction); id } crate::Expression::Derivative { axis, ctrl, expr } => { use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; match ctrl { Ctrl::Coarse | Ctrl::Fine => { self.writer.require_any( "DerivativeControl", &[spirv::Capability::DerivativeControl], )?; } Ctrl::None => {} } let id = self.gen_id(); let expr_id = self.cached[expr]; let op = match (axis, ctrl) { (Axis::X, Ctrl::Coarse) => spirv::Op::DPdxCoarse, (Axis::X, Ctrl::Fine) => spirv::Op::DPdxFine, (Axis::X, Ctrl::None) => spirv::Op::DPdx, (Axis::Y, Ctrl::Coarse) => spirv::Op::DPdyCoarse, (Axis::Y, Ctrl::Fine) => spirv::Op::DPdyFine, (Axis::Y, Ctrl::None) => spirv::Op::DPdy, (Axis::Width, Ctrl::Coarse) => spirv::Op::FwidthCoarse, (Axis::Width, Ctrl::Fine) => spirv::Op::FwidthFine, (Axis::Width, Ctrl::None) => spirv::Op::Fwidth, }; block .body .push(Instruction::derivative(op, result_type_id, id, expr_id)); id } crate::Expression::ImageQuery { image, query } => { self.write_image_query(result_type_id, image, query, block)? } crate::Expression::Relational { fun, argument } => { use crate::RelationalFunction as Rf; let arg_id = self.cached[argument]; let op = match fun { Rf::All => spirv::Op::All, Rf::Any => spirv::Op::Any, Rf::IsNan => spirv::Op::IsNan, Rf::IsInf => spirv::Op::IsInf, }; let id = self.gen_id(); block .body .push(Instruction::relational(op, result_type_id, id, arg_id)); id } crate::Expression::ArrayLength(expr) => self.write_runtime_array_length(expr, block)?, crate::Expression::RayQueryGetIntersection { query, committed } => { let query_id = self.cached[query]; let init_tracker_id = *self .ray_query_tracker_expr .get(&query) .expect("not a cached ray query"); let func_id = self .writer .write_ray_query_get_intersection_function(committed, self.ir_module); let ray_intersection = self.ir_module.special_types.ray_intersection.unwrap(); let intersection_type_id = self.get_handle_type_id(ray_intersection); let id = self.gen_id(); block.body.push(Instruction::function_call( intersection_type_id, id, func_id, &[query_id, init_tracker_id.initialized_tracker], )); id } crate::Expression::RayQueryVertexPositions { query, committed } => { self.writer.require_any( "RayQueryVertexPositions", &[spirv::Capability::RayQueryPositionFetchKHR], )?; self.write_ray_query_return_vertex_position(query, block, committed) } crate::Expression::CooperativeLoad { ref data, .. } => { self.writer.require_any( "CooperativeMatrix", &[spirv::Capability::CooperativeMatrixKHR], )?; let layout = if data.row_major { spirv::CooperativeMatrixLayout::RowMajorKHR } else { spirv::CooperativeMatrixLayout::ColumnMajorKHR }; let layout_id = self.get_index_constant(layout as u32); let stride_id = self.cached[data.stride]; match self.write_access_chain(data.pointer, block, AccessTypeAdjustment::None)? { ExpressionPointer::Ready { pointer_id } => { let id = self.gen_id(); block.body.push(Instruction::coop_load( result_type_id, id, pointer_id, layout_id, stride_id, )); id } ExpressionPointer::Conditional { condition, access } => self .write_conditional_indexed_load( result_type_id, condition, block, |id_gen, block| { let pointer_id = access.result_id.unwrap(); block.body.push(access); let id = id_gen.next(); block.body.push(Instruction::coop_load( result_type_id, id, pointer_id, layout_id, stride_id, )); id }, ), } } crate::Expression::CooperativeMultiplyAdd { a, b, c } => { self.writer.require_any( "CooperativeMatrix", &[spirv::Capability::CooperativeMatrixKHR], )?; let a_id = self.cached[a]; let b_id = self.cached[b]; let c_id = self.cached[c]; let id = self.gen_id(); block.body.push(Instruction::coop_mul_add( result_type_id, id, a_id, b_id, c_id, )); id } }; self.cached[expr_handle] = id; Ok(()) } /// Helper which focuses on generating the `As` expressions and the various conversions /// that need to happen because of that. fn write_as_expression( &mut self, expr: Handle, convert: Option, kind: crate::ScalarKind, block: &mut Block, result_type_id: u32, ) -> Result { use crate::ScalarKind as Sk; let expr_id = self.cached[expr]; let ty = self.fun_info[expr].ty.inner_with(&self.ir_module.types); // Matrix casts needs special treatment in SPIR-V, as the cast functions // can take vectors or scalars, but not matrices. In order to cast a matrix // we need to cast each column of the matrix individually and construct a new // matrix from the converted columns. if let crate::TypeInner::Matrix { columns, rows, scalar, } = *ty { let Some(convert) = convert else { // No conversion needs to be done, passes through. return Ok(expr_id); }; if convert == scalar.width { // No conversion needs to be done, passes through. return Ok(expr_id); } if kind != Sk::Float { // Only float conversions are supported for matrices. return Err(Error::Validation("Matrices must be floats")); } // Type of each extracted column let column_src_ty = self.get_type_id(LookupType::Local(LocalType::Numeric(NumericType::Vector { size: rows, scalar, }))); // Type of the column after conversion let column_dst_ty = self.get_type_id(LookupType::Local(LocalType::Numeric(NumericType::Vector { size: rows, scalar: crate::Scalar { kind, width: convert, }, }))); let mut components = ArrayVec::::new(); for column in 0..columns as usize { let column_id = self.gen_id(); block.body.push(Instruction::composite_extract( column_src_ty, column_id, expr_id, &[column as u32], )); let column_conv_id = self.gen_id(); block.body.push(Instruction::unary( spirv::Op::FConvert, column_dst_ty, column_conv_id, column_id, )); components.push(column_conv_id); } let construct_id = self.gen_id(); block.body.push(Instruction::composite_construct( result_type_id, construct_id, &components, )); return Ok(construct_id); } let (src_scalar, src_size) = match *ty { crate::TypeInner::Scalar(scalar) => (scalar, None), crate::TypeInner::Vector { scalar, size } => (scalar, Some(size)), ref other => { log::error!("As source {other:?}"); return Err(Error::Validation("Unexpected Expression::As source")); } }; enum Cast { Identity(Word), Unary(spirv::Op, Word), Binary(spirv::Op, Word, Word), Ternary(spirv::Op, Word, Word, Word), } let cast = match (src_scalar.kind, kind, convert) { // Filter out identity casts. Some Adreno drivers are // confused by no-op OpBitCast instructions. (src_kind, kind, convert) if src_kind == kind && convert.filter(|&width| width != src_scalar.width).is_none() => { Cast::Identity(expr_id) } (Sk::Bool, Sk::Bool, _) => Cast::Unary(spirv::Op::CopyObject, expr_id), (_, _, None) => Cast::Unary(spirv::Op::Bitcast, expr_id), // casting to a bool - generate `OpXxxNotEqual` (_, Sk::Bool, Some(_)) => { let op = match src_scalar.kind { Sk::Sint | Sk::Uint => spirv::Op::INotEqual, Sk::Float => spirv::Op::FUnordNotEqual, Sk::Bool | Sk::AbstractInt | Sk::AbstractFloat => unreachable!(), }; let zero_scalar_id = self.writer.get_constant_scalar_with(0, src_scalar)?; let zero_id = match src_size { Some(size) => { let ty = LocalType::Numeric(NumericType::Vector { size, scalar: src_scalar, }) .into(); self.temp_list.clear(); self.temp_list.resize(size as _, zero_scalar_id); self.writer.get_constant_composite(ty, &self.temp_list) } None => zero_scalar_id, }; Cast::Binary(op, expr_id, zero_id) } // casting from a bool - generate `OpSelect` (Sk::Bool, _, Some(dst_width)) => { let dst_scalar = crate::Scalar { kind, width: dst_width, }; let zero_scalar_id = self.writer.get_constant_scalar_with(0, dst_scalar)?; let one_scalar_id = self.writer.get_constant_scalar_with(1, dst_scalar)?; let (accept_id, reject_id) = match src_size { Some(size) => { let ty = LocalType::Numeric(NumericType::Vector { size, scalar: dst_scalar, }) .into(); self.temp_list.clear(); self.temp_list.resize(size as _, zero_scalar_id); let vec0_id = self.writer.get_constant_composite(ty, &self.temp_list); self.temp_list.fill(one_scalar_id); let vec1_id = self.writer.get_constant_composite(ty, &self.temp_list); (vec1_id, vec0_id) } None => (one_scalar_id, zero_scalar_id), }; Cast::Ternary(spirv::Op::Select, expr_id, accept_id, reject_id) } // Avoid undefined behaviour when casting from a float to integer // when the value is out of range for the target type. Additionally // ensure we clamp to the correct value as per the WGSL spec. // // https://www.w3.org/TR/WGSL/#floating-point-conversion: // * If X is exactly representable in the target type T, then the // result is that value. // * Otherwise, the result is the value in T closest to // truncate(X) and also exactly representable in the original // floating point type. (Sk::Float, Sk::Sint | Sk::Uint, Some(width)) => { let dst_scalar = crate::Scalar { kind, width }; let (min, max) = crate::proc::min_max_float_representable_by(src_scalar, dst_scalar); let expr_type_id = self.get_expression_type_id(&self.fun_info[expr].ty); let maybe_splat_const = |writer: &mut Writer, const_id| match src_size { None => const_id, Some(size) => { let constituent_ids = [const_id; crate::VectorSize::MAX]; writer.get_constant_composite( LookupType::Local(LocalType::Numeric(NumericType::Vector { size, scalar: src_scalar, })), &constituent_ids[..size as usize], ) } }; let min_const_id = self.writer.get_constant_scalar(min); let min_const_id = maybe_splat_const(self.writer, min_const_id); let max_const_id = self.writer.get_constant_scalar(max); let max_const_id = maybe_splat_const(self.writer, max_const_id); let clamp_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, spirv::GlslStd450Op::FClamp, expr_type_id, clamp_id, &[expr_id, min_const_id, max_const_id], )); let op = match dst_scalar.kind { crate::ScalarKind::Sint => spirv::Op::ConvertFToS, crate::ScalarKind::Uint => spirv::Op::ConvertFToU, _ => unreachable!(), }; Cast::Unary(op, clamp_id) } (Sk::Float, Sk::Float, Some(dst_width)) if src_scalar.width != dst_width => { Cast::Unary(spirv::Op::FConvert, expr_id) } (Sk::Sint, Sk::Float, Some(_)) => Cast::Unary(spirv::Op::ConvertSToF, expr_id), (Sk::Sint, Sk::Sint, Some(dst_width)) if src_scalar.width != dst_width => { Cast::Unary(spirv::Op::SConvert, expr_id) } (Sk::Uint, Sk::Float, Some(_)) => Cast::Unary(spirv::Op::ConvertUToF, expr_id), (Sk::Uint, Sk::Uint, Some(dst_width)) if src_scalar.width != dst_width => { Cast::Unary(spirv::Op::UConvert, expr_id) } (Sk::Uint, Sk::Sint, Some(dst_width)) if src_scalar.width != dst_width => { Cast::Unary(spirv::Op::SConvert, expr_id) } (Sk::Sint, Sk::Uint, Some(dst_width)) if src_scalar.width != dst_width => { Cast::Unary(spirv::Op::UConvert, expr_id) } // We assume it's either an identity cast, or int-uint. _ => Cast::Unary(spirv::Op::Bitcast, expr_id), }; Ok(match cast { Cast::Identity(expr) => expr, Cast::Unary(op, op1) => { let id = self.gen_id(); block .body .push(Instruction::unary(op, result_type_id, id, op1)); id } Cast::Binary(op, op1, op2) => { let id = self.gen_id(); block .body .push(Instruction::binary(op, result_type_id, id, op1, op2)); id } Cast::Ternary(op, op1, op2, op3) => { let id = self.gen_id(); block .body .push(Instruction::ternary(op, result_type_id, id, op1, op2, op3)); id } }) } /// Build an `OpAccessChain` instruction. /// /// Emit any needed bounds-checking expressions to `block`. /// /// Give the `OpAccessChain` a result type based on `expr_handle`, adjusted /// according to `type_adjustment`; see the documentation for /// [`AccessTypeAdjustment`] for details. /// /// On success, the return value is an [`ExpressionPointer`] value; see the /// documentation for that type. fn write_access_chain( &mut self, mut expr_handle: Handle, block: &mut Block, type_adjustment: AccessTypeAdjustment, ) -> Result { let result_type_id = { let resolution = &self.fun_info[expr_handle].ty; match type_adjustment { AccessTypeAdjustment::None => self.writer.get_expression_type_id(resolution), AccessTypeAdjustment::IntroducePointer(class) => { self.writer.get_resolution_pointer_id(resolution, class) } AccessTypeAdjustment::UseStd140CompatType => { match *resolution.inner_with(&self.ir_module.types) { crate::TypeInner::Pointer { base, space: space @ crate::AddressSpace::Uniform, } => self.writer.get_pointer_type_id( self.writer.std140_compat_uniform_types[&base].type_id, map_storage_class(space), ), _ => unreachable!( "`UseStd140CompatType` must only be used with uniform pointer types" ), } } } }; // The id of the boolean `and` of all dynamic bounds checks up to this point. // // See `extend_bounds_check_condition_chain` for a full explanation. let mut accumulated_checks = None; // Is true if we are accessing into a binding array with a non-uniform index. let mut is_non_uniform_binding_array = false; // The index value if the previously encountered expression was an // `AccessIndex` of a matrix which has been decomposed into individual // column vectors directly in the containing struct. The subsequent // iteration will append the correct index to the list for accessing // said column from the containing struct. let mut prev_decomposed_matrix_index = None; self.temp_list.clear(); let root_id = loop { // If `expr_handle` was spilled, then the temporary variable has exactly // the value we want to start from. if let Some(spilled) = self.function.spilled_composites.get(&expr_handle) { // The root id of the `OpAccessChain` instruction is the temporary // variable we spilled the composite to. break spilled.id; } expr_handle = match self.ir_function.expressions[expr_handle] { crate::Expression::Access { base, index } => { is_non_uniform_binding_array |= self.is_nonuniform_binding_array_access(base, index); let index = GuardedIndex::Expression(index); let index_id = self.write_access_chain_index(base, index, &mut accumulated_checks, block)?; self.temp_list.push(index_id); base } crate::Expression::AccessIndex { base, index } => { // Decide whether we're indexing a struct (bounds checks // forbidden) or anything else (bounds checks required). let mut base_ty = self.fun_info[base].ty.inner_with(&self.ir_module.types); let mut base_ty_handle = self.fun_info[base].ty.handle(); let mut pointer_space = None; if let crate::TypeInner::Pointer { base, space } = *base_ty { base_ty = &self.ir_module.types[base].inner; base_ty_handle = Some(base); pointer_space = Some(space); } match *base_ty { // When indexing a struct bounds checks are forbidden. If accessing the // struct through a uniform address space pointer, where the struct has // been declared with an alternative std140 compatible layout, we must use // the remapped member index. Additionally if the previous iteration was // accessing a column of a matrix member which has been decomposed directly // into the struct, we must ensure we access the correct column. crate::TypeInner::Struct { .. } => { let index = match base_ty_handle.and_then(|handle| { self.writer.std140_compat_uniform_types.get(&handle) }) { Some(std140_type_info) if pointer_space == Some(crate::AddressSpace::Uniform) => { std140_type_info.member_indices[index as usize] + prev_decomposed_matrix_index.take().unwrap_or(0) } _ => index, }; let index_id = self.get_index_constant(index); self.temp_list.push(index_id); } // Bounds checks are not required when indexing a matrix. If indexing a // two-row matrix contained within a struct through a uniform address space // pointer then the matrix' columns will have been decomposed directly into // the containing struct. We skip adding an index to the list on this // iteration and instead adjust the index on the next iteration when // accessing the struct member. _ if is_uniform_matcx2_struct_member_access( self.ir_function, self.fun_info, self.ir_module, base, ) => { assert!(prev_decomposed_matrix_index.is_none()); prev_decomposed_matrix_index = Some(index); } _ => { // `index` is constant, so this can't possibly require // setting `is_nonuniform_binding_array_access`. // Even though the index value is statically known, `base` // may be a runtime-sized array, so we still need to go // through the bounds check process. let index_id = self.write_access_chain_index( base, GuardedIndex::Known(index), &mut accumulated_checks, block, )?; self.temp_list.push(index_id); } } base } crate::Expression::GlobalVariable(handle) => { let gv = &self.writer.global_variables[handle]; break gv.access_id; } crate::Expression::LocalVariable(variable) => { let local_var = &self.function.variables[&variable]; break local_var.id; } crate::Expression::FunctionArgument(index) => { break self.function.parameter_id(index); } ref other => unimplemented!("Unexpected pointer expression {:?}", other), } }; let (pointer_id, expr_pointer) = if self.temp_list.is_empty() { ( root_id, ExpressionPointer::Ready { pointer_id: root_id, }, ) } else { self.temp_list.reverse(); let pointer_id = self.gen_id(); let access = Instruction::access_chain(result_type_id, pointer_id, root_id, &self.temp_list); // If we generated some bounds checks, we need to leave it to our // caller to generate the branch, the access, the load or store, and // the zero value (for loads). Otherwise, we can emit the access // ourselves, and just hand them the id of the pointer. let expr_pointer = match accumulated_checks { Some(condition) => ExpressionPointer::Conditional { condition, access }, None => { block.body.push(access); ExpressionPointer::Ready { pointer_id } } }; (pointer_id, expr_pointer) }; // Subsequent load, store and atomic operations require the pointer to be decorated as NonUniform // if the binding array was accessed with a non-uniform index // see VUID-RuntimeSpirv-NonUniform-06274 if is_non_uniform_binding_array { self.writer .decorate_non_uniform_binding_array_access(pointer_id)?; } Ok(expr_pointer) } fn is_nonuniform_binding_array_access( &mut self, base: Handle, index: Handle, ) -> bool { let crate::Expression::GlobalVariable(var_handle) = self.ir_function.expressions[base] else { return false; }; // The access chain needs to be decorated as NonUniform // see VUID-RuntimeSpirv-NonUniform-06274 let gvar = &self.ir_module.global_variables[var_handle]; let crate::TypeInner::BindingArray { .. } = self.ir_module.types[gvar.ty].inner else { return false; }; self.fun_info[index].uniformity.non_uniform_result.is_some() } /// Compute a single index operand to an `OpAccessChain` instruction. /// /// Given that we are indexing `base` with `index`, apply the appropriate /// bounds check policies, emitting code to `block` to clamp `index` or /// determine whether it's in bounds. Return the SPIR-V instruction id of /// the index value we should actually use. /// /// Extend `accumulated_checks` to include the results of any needed bounds /// checks. See [`BlockContext::extend_bounds_check_condition_chain`]. fn write_access_chain_index( &mut self, base: Handle, index: GuardedIndex, accumulated_checks: &mut Option, block: &mut Block, ) -> Result { match self.write_bounds_check(base, index, block)? { BoundsCheckResult::KnownInBounds(known_index) => { // Even if the index is known, `OpAccessChain` // requires expression operands, not literals. let scalar = crate::Literal::U32(known_index); Ok(self.writer.get_constant_scalar(scalar)) } BoundsCheckResult::Computed(computed_index_id) => Ok(computed_index_id), BoundsCheckResult::Conditional { condition_id: condition, index_id: index, } => { self.extend_bounds_check_condition_chain(accumulated_checks, condition, block); // Use the index from the `Access` expression unchanged. Ok(index) } } } /// Add a condition to a chain of bounds checks. /// /// As we build an `OpAccessChain` instruction govered by /// [`BoundsCheckPolicy::ReadZeroSkipWrite`], we accumulate a chain of /// dynamic bounds checks, one for each index in the chain, which must all /// be true for that `OpAccessChain`'s execution to be well-defined. This /// function adds the boolean instruction id `comparison_id` to `chain`. /// /// If `chain` is `None`, that means there are no bounds checks in the chain /// yet. If chain is `Some(id)`, then `id` is the conjunction of all the /// bounds checks in the chain. /// /// When we have multiple bounds checks, we combine them with /// `OpLogicalAnd`, not a short-circuit branch. This means we might do /// comparisons we don't need to, but we expect these checks to almost /// always succeed, and keeping branches to a minimum is essential. /// /// [`BoundsCheckPolicy::ReadZeroSkipWrite`]: crate::proc::BoundsCheckPolicy fn extend_bounds_check_condition_chain( &mut self, chain: &mut Option, comparison_id: Word, block: &mut Block, ) { match *chain { Some(ref mut prior_checks) => { let combined = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::LogicalAnd, self.writer.get_bool_type_id(), combined, *prior_checks, comparison_id, )); *prior_checks = combined; } None => { // Start a fresh chain of checks. *chain = Some(comparison_id); } } } fn write_checked_load( &mut self, pointer: Handle, block: &mut Block, access_type_adjustment: AccessTypeAdjustment, result_type_id: Word, ) -> Result { if let Some(result_id) = self.maybe_write_uniform_matcx2_dynamic_access(pointer, block)? { Ok(result_id) } else if let Some(result_id) = self.maybe_write_load_uniform_matcx2_struct_member(pointer, block)? { Ok(result_id) } else { // If `pointer` refers to a uniform address space pointer to a type // which was declared using a std140 compatible type variant (i.e. // is a two-row matrix, or a struct or array containing such a // matrix) we must ensure the access chain and the type of the load // instruction use the std140 compatible type variant. struct WrappedLoad { access_type_adjustment: AccessTypeAdjustment, r#type: Handle, } let mut wrapped_load = None; if let crate::TypeInner::Pointer { base: pointer_base_type, space: crate::AddressSpace::Uniform, } = *self.fun_info[pointer].ty.inner_with(&self.ir_module.types) { if self .writer .std140_compat_uniform_types .contains_key(&pointer_base_type) { wrapped_load = Some(WrappedLoad { access_type_adjustment: AccessTypeAdjustment::UseStd140CompatType, r#type: pointer_base_type, }); }; }; let (load_type_id, access_type_adjustment) = match wrapped_load { Some(ref wrapped_load) => ( self.writer.std140_compat_uniform_types[&wrapped_load.r#type].type_id, wrapped_load.access_type_adjustment, ), None => (result_type_id, access_type_adjustment), }; let load_id = match self.write_access_chain(pointer, block, access_type_adjustment)? { ExpressionPointer::Ready { pointer_id } => { let id = self.gen_id(); let atomic_space = match *self.fun_info[pointer].ty.inner_with(&self.ir_module.types) { crate::TypeInner::Pointer { base, space } => { match self.ir_module.types[base].inner { crate::TypeInner::Atomic { .. } => Some(space), _ => None, } } _ => None, }; let instruction = if let Some(space) = atomic_space { let (semantics, scope) = space.to_spirv_semantics_and_scope(); let scope_constant_id = self.get_scope_constant(scope as u32); let semantics_id = self.get_index_constant(semantics.bits()); Instruction::atomic_load( result_type_id, id, pointer_id, scope_constant_id, semantics_id, ) } else { Instruction::load(load_type_id, id, pointer_id, None) }; block.body.push(instruction); id } ExpressionPointer::Conditional { condition, access } => { //TODO: support atomics? self.write_conditional_indexed_load( load_type_id, condition, block, move |id_gen, block| { // The in-bounds path. Perform the access and the load. let pointer_id = access.result_id.unwrap(); let value_id = id_gen.next(); block.body.push(access); block.body.push(Instruction::load( load_type_id, value_id, pointer_id, None, )); value_id }, ) } }; match wrapped_load { Some(ref wrapped_load) => { // If we loaded a std140 compat type then we must call the // function to convert the loaded value to the regular type. let result_id = self.gen_id(); let function_id = self.writer.wrapped_functions [&WrappedFunction::ConvertFromStd140CompatType { r#type: wrapped_load.r#type, }]; block.body.push(Instruction::function_call( result_type_id, result_id, function_id, &[load_id], )); Ok(result_id) } None => Ok(load_id), } } } fn spill_to_internal_variable(&mut self, base: Handle, block: &mut Block) { use indexmap::map::Entry; // Make sure we have an internal variable to spill `base` to. let spill_variable_id = match self.function.spilled_composites.entry(base) { Entry::Occupied(preexisting) => preexisting.get().id, Entry::Vacant(vacant) => { // Generate a new internal variable of the appropriate // type for `base`. let pointer_type_id = self.writer.get_resolution_pointer_id( &self.fun_info[base].ty, spirv::StorageClass::Function, ); let id = self.writer.id_gen.next(); vacant.insert(super::LocalVariable { id, instruction: Instruction::variable( pointer_type_id, id, spirv::StorageClass::Function, None, ), }); id } }; // Perform the store even if we already had a spill variable for `base`. // Consider this code: // // var x = ...; // var y = ...; // var z = ...; // for (i = 0; i<2; i++) { // let a = array(i, i, i); // if (i == 0) { // x += a[y]; // } else [ // x += a[z]; // } // } // // The value of `a` needs to be spilled so we can subscript it with `y` and `z`. // // When we generate SPIR-V for `a[y]`, we will create the spill // variable, and store `a`'s value in it. // // When we generate SPIR-V for `a[z]`, we will notice that the spill // variable for `a` has already been declared, but it is still essential // that we store `a` into it, so that `a[z]` sees this iteration's value // of `a`. let base_id = self.cached[base]; block .body .push(Instruction::store(spill_variable_id, base_id, None)); } /// Generate an access to a spilled temporary, if necessary. /// /// Given `access`, an [`Access`] or [`AccessIndex`] expression that refers /// to a component of a composite value that has been spilled to a temporary /// variable, determine whether other expressions are going to use /// `access`'s value: /// /// - If so, perform the access and cache that as the value of `access`. /// /// - Otherwise, generate no code and cache no value for `access`. /// /// Return `Ok(0)` if no value was fetched, or `Ok(id)` if we loaded it into /// the instruction given by `id`. /// /// [`Access`]: crate::Expression::Access /// [`AccessIndex`]: crate::Expression::AccessIndex fn maybe_access_spilled_composite( &mut self, access: Handle, block: &mut Block, result_type_id: Word, ) -> Result { let access_uses = self.function.access_uses.get(&access).map_or(0, |r| *r); if access_uses == self.fun_info[access].ref_count { // This expression is only used by other `Access` and // `AccessIndex` expressions, so we don't need to cache a // value for it yet. Ok(0) } else { // There are other expressions that are going to expect this // expression's value to be cached, not just other `Access` or // `AccessIndex` expressions. We must actually perform the // access on the spill variable now. self.write_checked_load( access, block, AccessTypeAdjustment::IntroducePointer(spirv::StorageClass::Function), result_type_id, ) } } /// Build the instructions for matrix - matrix column operations #[allow(clippy::too_many_arguments)] fn write_matrix_matrix_column_op( &mut self, block: &mut Block, result_id: Word, result_type_id: Word, left_id: Word, right_id: Word, columns: crate::VectorSize, rows: crate::VectorSize, width: u8, op: spirv::Op, ) { self.temp_list.clear(); let vector_type_id = self.get_numeric_type_id(NumericType::Vector { size: rows, scalar: crate::Scalar::float(width), }); for index in 0..columns as u32 { let column_id_left = self.gen_id(); let column_id_right = self.gen_id(); let column_id_res = self.gen_id(); block.body.push(Instruction::composite_extract( vector_type_id, column_id_left, left_id, &[index], )); block.body.push(Instruction::composite_extract( vector_type_id, column_id_right, right_id, &[index], )); block.body.push(Instruction::binary( op, vector_type_id, column_id_res, column_id_left, column_id_right, )); self.temp_list.push(column_id_res); } block.body.push(Instruction::composite_construct( result_type_id, result_id, &self.temp_list, )); } /// Build the instructions for vector - scalar multiplication fn write_vector_scalar_mult( &mut self, block: &mut Block, result_id: Word, result_type_id: Word, vector_id: Word, scalar_id: Word, vector: &crate::TypeInner, ) { let (size, kind) = match *vector { crate::TypeInner::Vector { size, scalar: crate::Scalar { kind, .. }, } => (size, kind), _ => unreachable!(), }; let (op, operand_id) = match kind { crate::ScalarKind::Float => (spirv::Op::VectorTimesScalar, scalar_id), _ => { let operand_id = self.gen_id(); self.temp_list.clear(); self.temp_list.resize(size as usize, scalar_id); block.body.push(Instruction::composite_construct( result_type_id, operand_id, &self.temp_list, )); (spirv::Op::IMul, operand_id) } }; block.body.push(Instruction::binary( op, result_type_id, result_id, vector_id, operand_id, )); } /// Build the instructions for the arithmetic expression of a dot product /// /// The argument `extractor` is a function that maps `(result_id, /// composite_id, index)` to an instruction that extracts the `index`th /// entry of the value with ID `composite_id` and assigns it to the slot /// with id `result_id` (which must have type `result_type_id`). #[expect(clippy::too_many_arguments)] fn write_dot_product( &mut self, result_id: Word, result_type_id: Word, arg0_id: Word, arg1_id: Word, size: u32, block: &mut Block, extractor: impl Fn(Word, Word, Word) -> Instruction, ) { let mut partial_sum = self.writer.get_constant_null(result_type_id); let last_component = size - 1; for index in 0..=last_component { // compute the product of the current components let a_id = self.gen_id(); block.body.push(extractor(a_id, arg0_id, index)); let b_id = self.gen_id(); block.body.push(extractor(b_id, arg1_id, index)); let prod_id = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::IMul, result_type_id, prod_id, a_id, b_id, )); // choose the id for the next sum, depending on current index let id = if index == last_component { result_id } else { self.gen_id() }; // sum the computed product with the partial sum block.body.push(Instruction::binary( spirv::Op::IAdd, result_type_id, id, partial_sum, prod_id, )); // set the id of the result as the previous partial sum partial_sum = id; } } /// Emit code for `pack4x{I,U}8[Clamp]` if capability "Int8" is available. fn write_pack4x8_optimized( &mut self, block: &mut Block, result_type_id: u32, arg0_id: u32, id: u32, is_signed: bool, should_clamp: bool, ) -> Instruction { let int_type = if is_signed { crate::ScalarKind::Sint } else { crate::ScalarKind::Uint }; let wide_vector_type = NumericType::Vector { size: crate::VectorSize::Quad, scalar: crate::Scalar { kind: int_type, width: 4, }, }; let wide_vector_type_id = self.get_numeric_type_id(wide_vector_type); let packed_vector_type_id = self.get_numeric_type_id(NumericType::Vector { size: crate::VectorSize::Quad, scalar: crate::Scalar { kind: crate::ScalarKind::Uint, width: 1, }, }); let mut wide_vector = arg0_id; if should_clamp { let (min, max, clamp_op) = if is_signed { ( crate::Literal::I32(-128), crate::Literal::I32(127), spirv::GlslStd450Op::SClamp, ) } else { ( crate::Literal::U32(0), crate::Literal::U32(255), spirv::GlslStd450Op::UClamp, ) }; let [min, max] = [min, max].map(|lit| { let scalar = self.writer.get_constant_scalar(lit); self.writer.get_constant_composite( LookupType::Local(LocalType::Numeric(wide_vector_type)), &[scalar; 4], ) }); let clamp_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, clamp_op, wide_vector_type_id, clamp_id, &[wide_vector, min, max], )); wide_vector = clamp_id; } let packed_vector = self.gen_id(); block.body.push(Instruction::unary( spirv::Op::UConvert, // We truncate, so `UConvert` and `SConvert` behave identically. packed_vector_type_id, packed_vector, wide_vector, )); // The SPIR-V spec [1] defines the bit order for bit casting between a vector // and a scalar precisely as required by the WGSL spec [2]. // [1]: https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpBitcast // [2]: https://www.w3.org/TR/WGSL/#pack4xI8-builtin Instruction::unary(spirv::Op::Bitcast, result_type_id, id, packed_vector) } /// Emit code for `pack4x{I,U}8[Clamp]` if capability "Int8" is not available. fn write_pack4x8_polyfill( &mut self, block: &mut Block, result_type_id: u32, arg0_id: u32, id: u32, is_signed: bool, should_clamp: bool, ) -> Instruction { let int_type = if is_signed { crate::ScalarKind::Sint } else { crate::ScalarKind::Uint }; let uint_type_id = self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::U32)); let int_type_id = self.get_numeric_type_id(NumericType::Scalar(crate::Scalar { kind: int_type, width: 4, })); let mut last_instruction = Instruction::new(spirv::Op::Nop); let zero = self.writer.get_constant_scalar(crate::Literal::U32(0)); let mut preresult = zero; block .body .reserve(usize::from(VEC_LENGTH) * (2 + usize::from(is_signed))); let eight = self.writer.get_constant_scalar(crate::Literal::U32(8)); const VEC_LENGTH: u8 = 4; for i in 0..u32::from(VEC_LENGTH) { let offset = self.writer.get_constant_scalar(crate::Literal::U32(i * 8)); let mut extracted = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::CompositeExtract, int_type_id, extracted, arg0_id, i, )); if is_signed { let casted = self.gen_id(); block.body.push(Instruction::unary( spirv::Op::Bitcast, uint_type_id, casted, extracted, )); extracted = casted; } if should_clamp { let (min, max, clamp_op) = if is_signed { ( crate::Literal::I32(-128), crate::Literal::I32(127), spirv::GlslStd450Op::SClamp, ) } else { ( crate::Literal::U32(0), crate::Literal::U32(255), spirv::GlslStd450Op::UClamp, ) }; let [min, max] = [min, max].map(|lit| self.writer.get_constant_scalar(lit)); let clamp_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, clamp_op, result_type_id, clamp_id, &[extracted, min, max], )); extracted = clamp_id; } let is_last = i == u32::from(VEC_LENGTH - 1); if is_last { last_instruction = Instruction::quaternary( spirv::Op::BitFieldInsert, result_type_id, id, preresult, extracted, offset, eight, ) } else { let new_preresult = self.gen_id(); block.body.push(Instruction::quaternary( spirv::Op::BitFieldInsert, result_type_id, new_preresult, preresult, extracted, offset, eight, )); preresult = new_preresult; } } last_instruction } /// Emit code for `unpack4x{I,U}8` if capability "Int8" is available. fn write_unpack4x8_optimized( &mut self, block: &mut Block, result_type_id: u32, arg0_id: u32, id: u32, is_signed: bool, ) -> Instruction { let (int_type, convert_op) = if is_signed { (crate::ScalarKind::Sint, spirv::Op::SConvert) } else { (crate::ScalarKind::Uint, spirv::Op::UConvert) }; let packed_vector_type_id = self.get_numeric_type_id(NumericType::Vector { size: crate::VectorSize::Quad, scalar: crate::Scalar { kind: int_type, width: 1, }, }); // The SPIR-V spec [1] defines the bit order for bit casting between a vector // and a scalar precisely as required by the WGSL spec [2]. // [1]: https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpBitcast // [2]: https://www.w3.org/TR/WGSL/#pack4xI8-builtin let packed_vector = self.gen_id(); block.body.push(Instruction::unary( spirv::Op::Bitcast, packed_vector_type_id, packed_vector, arg0_id, )); Instruction::unary(convert_op, result_type_id, id, packed_vector) } /// Emit code for `unpack4x{I,U}8` if capability "Int8" is not available. fn write_unpack4x8_polyfill( &mut self, block: &mut Block, result_type_id: u32, arg0_id: u32, id: u32, is_signed: bool, ) -> Instruction { let (int_type, extract_op) = if is_signed { (crate::ScalarKind::Sint, spirv::Op::BitFieldSExtract) } else { (crate::ScalarKind::Uint, spirv::Op::BitFieldUExtract) }; let sint_type_id = self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::I32)); let eight = self.writer.get_constant_scalar(crate::Literal::U32(8)); let int_type_id = self.get_numeric_type_id(NumericType::Scalar(crate::Scalar { kind: int_type, width: 4, })); block .body .reserve(usize::from(VEC_LENGTH) * 2 + usize::from(is_signed)); let arg_id = if is_signed { let new_arg_id = self.gen_id(); block.body.push(Instruction::unary( spirv::Op::Bitcast, sint_type_id, new_arg_id, arg0_id, )); new_arg_id } else { arg0_id }; const VEC_LENGTH: u8 = 4; let parts: [_; VEC_LENGTH as usize] = core::array::from_fn(|_| self.gen_id()); for (i, part_id) in parts.into_iter().enumerate() { let index = self .writer .get_constant_scalar(crate::Literal::U32(i as u32 * 8)); block.body.push(Instruction::ternary( extract_op, int_type_id, part_id, arg_id, index, eight, )); } Instruction::composite_construct(result_type_id, id, &parts) } /// Generate one or more SPIR-V blocks for `naga_block`. /// /// Use `label_id` as the label for the SPIR-V entry point block. /// /// If control reaches the end of the SPIR-V block, terminate it according /// to `exit`. This function's return value indicates whether it acted on /// this parameter or not; see [`BlockExitDisposition`]. /// /// If the block contains [`Break`] or [`Continue`] statements, /// `loop_context` supplies the labels of the SPIR-V blocks to jump to. If /// either of these labels are `None`, then it should have been a Naga /// validation error for the corresponding statement to occur in this /// context. /// /// [`Break`]: Statement::Break /// [`Continue`]: Statement::Continue fn write_block( &mut self, label_id: Word, naga_block: &crate::Block, exit: BlockExit, loop_context: LoopContext, debug_info: Option<&DebugInfoInner>, ) -> Result { let mut block = Block::new(label_id); for (statement, span) in naga_block.span_iter() { if let (Some(debug_info), false) = ( debug_info, matches!( statement, &(Statement::Block(..) | Statement::Break | Statement::Continue | Statement::Kill | Statement::Return { .. } | Statement::Loop { .. }) ), ) { let loc: crate::SourceLocation = span.location(debug_info.source_code); block.body.push(Instruction::line( debug_info.source_file_id, loc.line_number, loc.line_position, )); }; match *statement { Statement::Emit(ref range) => { for handle in range.clone() { // omit const expressions as we've already cached those if !self.expression_constness.is_const(handle) { self.cache_expression_value(handle, &mut block)?; } } } Statement::Block(ref block_statements) => { let scope_id = self.gen_id(); self.function.consume(block, Instruction::branch(scope_id)); let merge_id = self.gen_id(); let merge_used = self.write_block( scope_id, block_statements, BlockExit::Branch { target: merge_id }, loop_context, debug_info, )?; match merge_used { BlockExitDisposition::Used => { block = Block::new(merge_id); } BlockExitDisposition::Discarded => { return Ok(BlockExitDisposition::Discarded); } } } Statement::If { condition, ref accept, ref reject, } => { // In spirv 1.6, in a conditional branch the two block ids // of the branches can't have the same label. If `accept` // and `reject` are both empty (e.g. in `if (condition) {}`) // merge id will be both labels. Because both branches are // empty, we can skip the if statement. if !(accept.is_empty() && reject.is_empty()) { let condition_id = self.cached[condition]; let merge_id = self.gen_id(); block.body.push(Instruction::selection_merge( merge_id, spirv::SelectionControl::NONE, )); let accept_id = if accept.is_empty() { None } else { Some(self.gen_id()) }; let reject_id = if reject.is_empty() { None } else { Some(self.gen_id()) }; self.function.consume( block, Instruction::branch_conditional( condition_id, accept_id.unwrap_or(merge_id), reject_id.unwrap_or(merge_id), ), ); if let Some(block_id) = accept_id { // We can ignore the `BlockExitDisposition` returned here because, // even if `merge_id` is not actually reachable, it is always // referred to by the `OpSelectionMerge` instruction we emitted // earlier. let _ = self.write_block( block_id, accept, BlockExit::Branch { target: merge_id }, loop_context, debug_info, )?; } if let Some(block_id) = reject_id { // We can ignore the `BlockExitDisposition` returned here because, // even if `merge_id` is not actually reachable, it is always // referred to by the `OpSelectionMerge` instruction we emitted // earlier. let _ = self.write_block( block_id, reject, BlockExit::Branch { target: merge_id }, loop_context, debug_info, )?; } block = Block::new(merge_id); } } Statement::Switch { selector, ref cases, } => { let selector_id = self.cached[selector]; let merge_id = self.gen_id(); block.body.push(Instruction::selection_merge( merge_id, spirv::SelectionControl::NONE, )); let mut default_id = None; // id of previous empty fall-through case let mut last_id = None; let mut raw_cases = Vec::with_capacity(cases.len()); let mut case_ids = Vec::with_capacity(cases.len()); for case in cases.iter() { // take id of previous empty fall-through case or generate a new one let label_id = last_id.take().unwrap_or_else(|| self.gen_id()); if case.fall_through && case.body.is_empty() { last_id = Some(label_id); } case_ids.push(label_id); match case.value { crate::SwitchValue::I32(value) => { raw_cases.push(super::instructions::Case { value: value as Word, label_id, }); } crate::SwitchValue::U32(value) => { raw_cases.push(super::instructions::Case { value, label_id }); } crate::SwitchValue::Default => { default_id = Some(label_id); } } } let default_id = default_id.unwrap(); self.function.consume( block, Instruction::switch(selector_id, default_id, &raw_cases), ); let inner_context = LoopContext { break_id: Some(merge_id), ..loop_context }; for (i, (case, label_id)) in cases .iter() .zip(case_ids.iter()) .filter(|&(case, _)| !(case.fall_through && case.body.is_empty())) .enumerate() { let case_finish_id = if case.fall_through { case_ids[i + 1] } else { merge_id }; // We can ignore the `BlockExitDisposition` returned here because // `case_finish_id` is always referred to by either: // // - the `OpSwitch`, if it's the next case's label for a // fall-through, or // // - the `OpSelectionMerge`, if it's the switch's overall merge // block because there's no fall-through. let _ = self.write_block( *label_id, &case.body, BlockExit::Branch { target: case_finish_id, }, inner_context, debug_info, )?; } block = Block::new(merge_id); } Statement::Loop { ref body, ref continuing, break_if, } => { let preamble_id = self.gen_id(); self.function .consume(block, Instruction::branch(preamble_id)); let merge_id = self.gen_id(); let body_id = self.gen_id(); let continuing_id = self.gen_id(); // SPIR-V requires the continuing to the `OpLoopMerge`, // so we have to start a new block with it. block = Block::new(preamble_id); // HACK the loop statement is begin with branch instruction, // so we need to put `OpLine` debug info before merge instruction if let Some(debug_info) = debug_info { let loc: crate::SourceLocation = span.location(debug_info.source_code); block.body.push(Instruction::line( debug_info.source_file_id, loc.line_number, loc.line_position, )) } block.body.push(Instruction::loop_merge( merge_id, continuing_id, spirv::SelectionControl::NONE, )); if self.force_loop_bounding { block = self.write_force_bounded_loop_instructions(block, merge_id); } self.function.consume(block, Instruction::branch(body_id)); // We can ignore the `BlockExitDisposition` returned here because, // even if `continuing_id` is not actually reachable, it is always // referred to by the `OpLoopMerge` instruction we emitted earlier. let _ = self.write_block( body_id, body, BlockExit::Branch { target: continuing_id, }, LoopContext { continuing_id: Some(continuing_id), break_id: Some(merge_id), }, debug_info, )?; let exit = match break_if { Some(condition) => BlockExit::BreakIf { condition, preamble_id, }, None => BlockExit::Branch { target: preamble_id, }, }; // We can ignore the `BlockExitDisposition` returned here because, // even if `merge_id` is not actually reachable, it is always referred // to by the `OpLoopMerge` instruction we emitted earlier. let _ = self.write_block( continuing_id, continuing, exit, LoopContext { continuing_id: None, break_id: Some(merge_id), }, debug_info, )?; block = Block::new(merge_id); } Statement::Break => { self.function .consume(block, Instruction::branch(loop_context.break_id.unwrap())); return Ok(BlockExitDisposition::Discarded); } Statement::Continue => { self.function.consume( block, Instruction::branch(loop_context.continuing_id.unwrap()), ); return Ok(BlockExitDisposition::Discarded); } Statement::Return { value: Some(value) } => { let value_id = self.cached[value]; let instruction = match self.function.entry_point_context { // If this is an entry point, and we need to return anything, // let's instead store the output variables and return `void`. Some(ref context) => self.writer.write_entry_point_return( value_id, self.ir_function.result.as_ref().unwrap(), &context.results, &mut block.body, )?, None => Instruction::return_value(value_id), }; self.function.consume(block, instruction); return Ok(BlockExitDisposition::Discarded); } Statement::Return { value: None } => { self.function.consume(block, Instruction::return_void()); return Ok(BlockExitDisposition::Discarded); } Statement::Kill => { self.function.consume(block, Instruction::kill()); return Ok(BlockExitDisposition::Discarded); } Statement::ControlBarrier(flags) => { self.writer.write_control_barrier(flags, &mut block.body); } Statement::MemoryBarrier(flags) => { self.writer.write_memory_barrier(flags, &mut block); } Statement::Store { pointer, value } => { let value_id = self.cached[value]; match self.write_access_chain( pointer, &mut block, AccessTypeAdjustment::None, )? { ExpressionPointer::Ready { pointer_id } => { let atomic_space = match *self.fun_info[pointer] .ty .inner_with(&self.ir_module.types) { crate::TypeInner::Pointer { base, space } => { match self.ir_module.types[base].inner { crate::TypeInner::Atomic { .. } => Some(space), _ => None, } } _ => None, }; let instruction = if let Some(space) = atomic_space { let (semantics, scope) = space.to_spirv_semantics_and_scope(); let scope_constant_id = self.get_scope_constant(scope as u32); let semantics_id = self.get_index_constant(semantics.bits()); Instruction::atomic_store( pointer_id, scope_constant_id, semantics_id, value_id, ) } else { Instruction::store(pointer_id, value_id, None) }; block.body.push(instruction); } ExpressionPointer::Conditional { condition, access } => { let mut selection = Selection::start(&mut block, ()); selection.if_true(self, condition, ()); // The in-bounds path. Perform the access and the store. let pointer_id = access.result_id.unwrap(); selection.block().body.push(access); selection .block() .body .push(Instruction::store(pointer_id, value_id, None)); // Finish the in-bounds block and start the merge block. This // is the block we'll leave current on return. selection.finish(self, ()); } }; } Statement::ImageStore { image, coordinate, array_index, value, } => self.write_image_store(image, coordinate, array_index, value, &mut block)?, Statement::Call { function: local_function, ref arguments, result, } => { let id = self.gen_id(); self.temp_list.clear(); for &argument in arguments { self.temp_list.push(self.cached[argument]); } let type_id = match result { Some(expr) => { self.cached[expr] = id; self.get_expression_type_id(&self.fun_info[expr].ty) } None => self.writer.void_type, }; block.body.push(Instruction::function_call( type_id, id, self.writer.lookup_function[&local_function], &self.temp_list, )); } Statement::Atomic { pointer, ref fun, value, result, } => { let id = self.gen_id(); // Compare-and-exchange operations produce a struct result, // so use `result`'s type if it is available. For no-result // operations, fall back to `value`'s type. let result_type_id = self.get_expression_type_id(&self.fun_info[result.unwrap_or(value)].ty); if let Some(result) = result { self.cached[result] = id; } let pointer_id = match self.write_access_chain( pointer, &mut block, AccessTypeAdjustment::None, )? { ExpressionPointer::Ready { pointer_id } => pointer_id, ExpressionPointer::Conditional { .. } => { return Err(Error::FeatureNotImplemented( "Atomics out-of-bounds handling", )); } }; let space = self.fun_info[pointer] .ty .inner_with(&self.ir_module.types) .pointer_space() .unwrap(); let (semantics, scope) = space.to_spirv_semantics_and_scope(); let scope_constant_id = self.get_scope_constant(scope as u32); let semantics_id = self.get_index_constant(semantics.bits()); let value_id = self.cached[value]; let value_inner = self.fun_info[value].ty.inner_with(&self.ir_module.types); let crate::TypeInner::Scalar(scalar) = *value_inner else { return Err(Error::FeatureNotImplemented( "Atomics with non-scalar values", )); }; let instruction = match *fun { crate::AtomicFunction::Add => { let spirv_op = match scalar.kind { crate::ScalarKind::Sint | crate::ScalarKind::Uint => { spirv::Op::AtomicIAdd } crate::ScalarKind::Float => spirv::Op::AtomicFAddEXT, _ => unimplemented!(), }; Instruction::atomic_binary( spirv_op, result_type_id, id, pointer_id, scope_constant_id, semantics_id, value_id, ) } crate::AtomicFunction::Subtract => { let (spirv_op, value_id) = match scalar.kind { crate::ScalarKind::Sint | crate::ScalarKind::Uint => { (spirv::Op::AtomicISub, value_id) } crate::ScalarKind::Float => { // HACK: SPIR-V doesn't have a atomic subtraction, // so we add the negated value instead. let neg_result_id = self.gen_id(); block.body.push(Instruction::unary( spirv::Op::FNegate, result_type_id, neg_result_id, value_id, )); (spirv::Op::AtomicFAddEXT, neg_result_id) } _ => unimplemented!(), }; Instruction::atomic_binary( spirv_op, result_type_id, id, pointer_id, scope_constant_id, semantics_id, value_id, ) } crate::AtomicFunction::And => { let spirv_op = match scalar.kind { crate::ScalarKind::Sint | crate::ScalarKind::Uint => { spirv::Op::AtomicAnd } _ => unimplemented!(), }; Instruction::atomic_binary( spirv_op, result_type_id, id, pointer_id, scope_constant_id, semantics_id, value_id, ) } crate::AtomicFunction::InclusiveOr => { let spirv_op = match scalar.kind { crate::ScalarKind::Sint | crate::ScalarKind::Uint => { spirv::Op::AtomicOr } _ => unimplemented!(), }; Instruction::atomic_binary( spirv_op, result_type_id, id, pointer_id, scope_constant_id, semantics_id, value_id, ) } crate::AtomicFunction::ExclusiveOr => { let spirv_op = match scalar.kind { crate::ScalarKind::Sint | crate::ScalarKind::Uint => { spirv::Op::AtomicXor } _ => unimplemented!(), }; Instruction::atomic_binary( spirv_op, result_type_id, id, pointer_id, scope_constant_id, semantics_id, value_id, ) } crate::AtomicFunction::Min => { let spirv_op = match scalar.kind { crate::ScalarKind::Sint => spirv::Op::AtomicSMin, crate::ScalarKind::Uint => spirv::Op::AtomicUMin, _ => unimplemented!(), }; Instruction::atomic_binary( spirv_op, result_type_id, id, pointer_id, scope_constant_id, semantics_id, value_id, ) } crate::AtomicFunction::Max => { let spirv_op = match scalar.kind { crate::ScalarKind::Sint => spirv::Op::AtomicSMax, crate::ScalarKind::Uint => spirv::Op::AtomicUMax, _ => unimplemented!(), }; Instruction::atomic_binary( spirv_op, result_type_id, id, pointer_id, scope_constant_id, semantics_id, value_id, ) } crate::AtomicFunction::Exchange { compare: None } => { Instruction::atomic_binary( spirv::Op::AtomicExchange, result_type_id, id, pointer_id, scope_constant_id, semantics_id, value_id, ) } crate::AtomicFunction::Exchange { compare: Some(cmp) } => { let scalar_type_id = self.get_numeric_type_id(NumericType::Scalar(scalar)); let bool_type_id = self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::BOOL)); let cas_result_id = self.gen_id(); let equality_result_id = self.gen_id(); let equality_operator = match scalar.kind { crate::ScalarKind::Sint | crate::ScalarKind::Uint => { spirv::Op::IEqual } _ => unimplemented!(), }; let mut cas_instr = Instruction::new(spirv::Op::AtomicCompareExchange); cas_instr.set_type(scalar_type_id); cas_instr.set_result(cas_result_id); cas_instr.add_operand(pointer_id); cas_instr.add_operand(scope_constant_id); cas_instr.add_operand(semantics_id); // semantics if equal cas_instr.add_operand(semantics_id); // semantics if not equal cas_instr.add_operand(value_id); cas_instr.add_operand(self.cached[cmp]); block.body.push(cas_instr); block.body.push(Instruction::binary( equality_operator, bool_type_id, equality_result_id, cas_result_id, self.cached[cmp], )); Instruction::composite_construct( result_type_id, id, &[cas_result_id, equality_result_id], ) } }; block.body.push(instruction); } Statement::ImageAtomic { image, coordinate, array_index, fun, value, } => { self.write_image_atomic( image, coordinate, array_index, fun, value, &mut block, )?; } Statement::WorkGroupUniformLoad { pointer, result } => { self.writer .write_control_barrier(crate::Barrier::WORK_GROUP, &mut block.body); let result_type_id = self.get_expression_type_id(&self.fun_info[result].ty); // Match `Expression::Load` behavior, including `OpAtomicLoad` when // loading from a pointer to `atomic`. let id = self.write_checked_load( pointer, &mut block, AccessTypeAdjustment::None, result_type_id, )?; self.cached[result] = id; self.writer .write_control_barrier(crate::Barrier::WORK_GROUP, &mut block.body); } Statement::RayQuery { query, ref fun } => { self.write_ray_query_function(query, fun, &mut block); } Statement::SubgroupBallot { result, ref predicate, } => { self.write_subgroup_ballot(predicate, result, &mut block)?; } Statement::SubgroupCollectiveOperation { ref op, ref collective_op, argument, result, } => { self.write_subgroup_operation(op, collective_op, argument, result, &mut block)?; } Statement::SubgroupGather { ref mode, argument, result, } => { self.write_subgroup_gather(mode, argument, result, &mut block)?; } Statement::CooperativeStore { target, ref data } => { let target_id = self.cached[target]; let layout = if data.row_major { spirv::CooperativeMatrixLayout::RowMajorKHR } else { spirv::CooperativeMatrixLayout::ColumnMajorKHR }; let layout_id = self.get_index_constant(layout as u32); let stride_id = self.cached[data.stride]; match self.write_access_chain( data.pointer, &mut block, AccessTypeAdjustment::None, )? { ExpressionPointer::Ready { pointer_id } => { block.body.push(Instruction::coop_store( target_id, pointer_id, layout_id, stride_id, )); } ExpressionPointer::Conditional { condition, access } => { let mut selection = Selection::start(&mut block, ()); selection.if_true(self, condition, ()); // The in-bounds path. Perform the access and the store. let pointer_id = access.result_id.unwrap(); selection.block().body.push(access); selection.block().body.push(Instruction::coop_store( target_id, pointer_id, layout_id, stride_id, )); // Finish the in-bounds block and start the merge block. This // is the block we'll leave current on return. selection.finish(self, ()); } }; } Statement::RayPipelineFunction(_) => unreachable!(), } } let termination = match exit { // We're generating code for the top-level Block of the function, so we // need to end it with some kind of return instruction. BlockExit::Return => match self.ir_function.result { Some(ref result) if self.function.entry_point_context.is_none() => { let type_id = self.get_handle_type_id(result.ty); let null_id = self.writer.get_constant_null(type_id); Instruction::return_value(null_id) } _ => Instruction::return_void(), }, BlockExit::Branch { target } => Instruction::branch(target), BlockExit::BreakIf { condition, preamble_id, } => { let condition_id = self.cached[condition]; Instruction::branch_conditional( condition_id, loop_context.break_id.unwrap(), preamble_id, ) } }; self.function.consume(block, termination); Ok(BlockExitDisposition::Used) } pub(super) fn write_function_body( &mut self, entry_id: Word, debug_info: Option<&DebugInfoInner>, ) -> Result<(), Error> { // We can ignore the `BlockExitDisposition` returned here because // `BlockExit::Return` doesn't refer to a block. let _ = self.write_block( entry_id, &self.ir_function.body, BlockExit::Return, LoopContext::default(), debug_info, )?; Ok(()) } } ================================================ FILE: naga/src/back/spv/f16_polyfill.rs ================================================ /*! This module provides functionality for polyfilling `f16` input/output variables when the `StorageInputOutput16` capability is not available or disabled. It works by: 1. Declaring `f16` I/O variables as `f32` in SPIR-V 2. Converting between `f16` and `f32` at runtime using `OpFConvert` 3. Maintaining mappings to track which variables need conversion */ use crate::back::spv::{Instruction, LocalType, NumericType, Word}; use alloc::vec::Vec; /// Manages `f16` I/O polyfill state and operations. #[derive(Default)] pub(in crate::back::spv) struct F16IoPolyfill { use_native: bool, io_var_to_f32_type: crate::FastHashMap, } impl F16IoPolyfill { pub fn new(use_storage_input_output_16: bool) -> Self { Self { use_native: use_storage_input_output_16, io_var_to_f32_type: crate::FastHashMap::default(), } } pub fn needs_polyfill(&self, ty_inner: &crate::TypeInner) -> bool { use crate::{ScalarKind as Sk, TypeInner}; !self.use_native && match *ty_inner { TypeInner::Scalar(ref s) if s.kind == Sk::Float && s.width == 2 => true, TypeInner::Vector { scalar, .. } if scalar.kind == Sk::Float && scalar.width == 2 => { true } _ => false, } } pub fn register_io_var(&mut self, variable_id: Word, f32_type_id: Word) { self.io_var_to_f32_type.insert(variable_id, f32_type_id); } pub fn get_f32_io_type(&self, variable_id: Word) -> Option { self.io_var_to_f32_type.get(&variable_id).copied() } pub fn emit_f16_to_f32_conversion( f16_value_id: Word, f32_type_id: Word, converted_id: Word, body: &mut Vec, ) { body.push(Instruction::unary( spirv::Op::FConvert, f32_type_id, converted_id, f16_value_id, )); } pub fn emit_f32_to_f16_conversion( f32_value_id: Word, f16_type_id: Word, converted_id: Word, body: &mut Vec, ) { body.push(Instruction::unary( spirv::Op::FConvert, f16_type_id, converted_id, f32_value_id, )); } pub fn create_polyfill_type(ty_inner: &crate::TypeInner) -> Option { use crate::{ScalarKind as Sk, TypeInner}; match *ty_inner { TypeInner::Scalar(ref s) if s.kind == Sk::Float && s.width == 2 => { Some(LocalType::Numeric(NumericType::Scalar(crate::Scalar::F32))) } TypeInner::Vector { size, scalar } if scalar.kind == Sk::Float && scalar.width == 2 => { Some(LocalType::Numeric(NumericType::Vector { size, scalar: crate::Scalar::F32, })) } _ => None, } } } impl crate::back::spv::reclaimable::Reclaimable for F16IoPolyfill { fn reclaim(mut self) -> Self { self.io_var_to_f32_type = self.io_var_to_f32_type.reclaim(); self } } ================================================ FILE: naga/src/back/spv/helpers.rs ================================================ use alloc::{vec, vec::Vec}; use arrayvec::ArrayVec; use spirv::Word; use crate::{Handle, UniqueArena}; pub(super) fn bytes_to_words(bytes: &[u8]) -> Vec { bytes .chunks(4) .map(|chars| chars.iter().rev().fold(0u32, |u, c| (u << 8) | *c as u32)) .collect() } pub(super) fn string_to_words(input: &str) -> Vec { let bytes = input.as_bytes(); str_bytes_to_words(bytes) } pub(super) fn str_bytes_to_words(bytes: &[u8]) -> Vec { let mut words = bytes_to_words(bytes); if bytes.len().is_multiple_of(4) { // nul-termination words.push(0x0u32); } words } /// split a string into chunks and keep utf8 valid #[allow(unstable_name_collisions)] pub(super) fn string_to_byte_chunks(input: &str, limit: usize) -> Vec<&[u8]> { let mut offset: usize = 0; let mut start: usize = 0; let mut words = vec![]; while offset < input.len() { offset = input.floor_char_boundary_polyfill(offset + limit); // Clippy wants us to call as_bytes() first to avoid the UTF-8 check, // but we want to assert the output is valid UTF-8. #[allow(clippy::sliced_string_as_bytes)] words.push(input[start..offset].as_bytes()); start = offset; } words } pub(super) const fn map_storage_class(space: crate::AddressSpace) -> spirv::StorageClass { match space { crate::AddressSpace::Handle => spirv::StorageClass::UniformConstant, crate::AddressSpace::Function => spirv::StorageClass::Function, crate::AddressSpace::Private => spirv::StorageClass::Private, crate::AddressSpace::Storage { .. } => spirv::StorageClass::StorageBuffer, crate::AddressSpace::Uniform => spirv::StorageClass::Uniform, crate::AddressSpace::WorkGroup => spirv::StorageClass::Workgroup, crate::AddressSpace::Immediate => spirv::StorageClass::PushConstant, crate::AddressSpace::TaskPayload => spirv::StorageClass::TaskPayloadWorkgroupEXT, crate::AddressSpace::IncomingRayPayload | crate::AddressSpace::RayPayload => unreachable!(), } } pub(super) fn contains_builtin( binding: Option<&crate::Binding>, ty: Handle, arena: &UniqueArena, built_in: crate::BuiltIn, ) -> bool { if let Some(&crate::Binding::BuiltIn(bi)) = binding { bi == built_in } else if let crate::TypeInner::Struct { ref members, .. } = arena[ty].inner { members .iter() .any(|member| contains_builtin(member.binding.as_ref(), member.ty, arena, built_in)) } else { false // unreachable } } impl crate::AddressSpace { pub(super) const fn to_spirv_semantics_and_scope( self, ) -> (spirv::MemorySemantics, spirv::Scope) { match self { Self::Storage { .. } => (spirv::MemorySemantics::empty(), spirv::Scope::Device), Self::WorkGroup => (spirv::MemorySemantics::empty(), spirv::Scope::Workgroup), Self::Uniform => (spirv::MemorySemantics::empty(), spirv::Scope::Device), Self::Handle => (spirv::MemorySemantics::empty(), spirv::Scope::Device), _ => (spirv::MemorySemantics::empty(), spirv::Scope::Invocation), } } } /// Return true if the global requires a type decorated with `Block`. /// /// See [`back::spv::GlobalVariable`] for details. /// /// [`back::spv::GlobalVariable`]: super::GlobalVariable pub fn global_needs_wrapper(ir_module: &crate::Module, var: &crate::GlobalVariable) -> bool { match var.space { crate::AddressSpace::Uniform | crate::AddressSpace::Storage { .. } | crate::AddressSpace::Immediate => {} _ => return false, }; match ir_module.types[var.ty].inner { crate::TypeInner::Struct { ref members, span: _, } => match members.last() { Some(member) => match ir_module.types[member.ty].inner { // Structs with dynamically sized arrays can't be copied and can't be wrapped. crate::TypeInner::Array { size: crate::ArraySize::Dynamic, .. } => false, _ => true, }, None => false, }, crate::TypeInner::BindingArray { .. } => false, // if it's not a structure or a binding array, let's wrap it to be able to put "Block" _ => true, } } /// Returns true if `pointer` refers to two-row matrix which is a member of a /// struct in the [`crate::AddressSpace::Uniform`] address space. pub fn is_uniform_matcx2_struct_member_access( ir_function: &crate::Function, fun_info: &crate::valid::FunctionInfo, ir_module: &crate::Module, pointer: Handle, ) -> bool { if let crate::TypeInner::Pointer { base: pointer_base_type, space: crate::AddressSpace::Uniform, } = *fun_info[pointer].ty.inner_with(&ir_module.types) { if let crate::TypeInner::Matrix { rows: crate::VectorSize::Bi, .. } = ir_module.types[pointer_base_type].inner { if let crate::Expression::AccessIndex { base: parent_pointer, .. } = ir_function.expressions[pointer] { if let crate::TypeInner::Pointer { base: parent_type, .. } = *fun_info[parent_pointer].ty.inner_with(&ir_module.types) { if let crate::TypeInner::Struct { .. } = ir_module.types[parent_type].inner { return true; } } } } } false } ///HACK: this is taken from std unstable, remove it when std's floor_char_boundary is stable /// and available in our msrv. trait U8Internal { fn is_utf8_char_boundary_polyfill(&self) -> bool; } impl U8Internal for u8 { fn is_utf8_char_boundary_polyfill(&self) -> bool { // This is bit magic equivalent to: b < 128 || b >= 192 (*self as i8) >= -0x40 } } trait StrUnstable { fn floor_char_boundary_polyfill(&self, index: usize) -> usize; } impl StrUnstable for str { fn floor_char_boundary_polyfill(&self, index: usize) -> usize { if index >= self.len() { self.len() } else { let lower_bound = index.saturating_sub(3); let new_index = self.as_bytes()[lower_bound..=index] .iter() .rposition(|b| b.is_utf8_char_boundary_polyfill()); // We know that the character boundary will be within four bytes. lower_bound + new_index.unwrap() } } } pub enum BindingDecorations { BuiltIn(spirv::BuiltIn, ArrayVec), Location { location: u32, others: ArrayVec, /// If this is `Some`, use Decoration::Index with blend_src as an operand blend_src: Option, }, None, } ================================================ FILE: naga/src/back/spv/image.rs ================================================ /*! Generating SPIR-V for image operations. */ use spirv::Word; use super::{ selection::{MergeTuple, Selection}, Block, BlockContext, Error, IdGenerator, Instruction, LocalType, LookupType, NumericType, }; use crate::arena::Handle; /// Information about a vector of coordinates. /// /// The coordinate vectors expected by SPIR-V `OpImageRead` and `OpImageFetch` /// supply the array index for arrayed images as an additional component at /// the end, whereas Naga's `ImageLoad`, `ImageStore`, and `ImageSample` carry /// the array index as a separate field. /// /// In the process of generating code to compute the combined vector, we also /// produce SPIR-V types and vector lengths that are useful elsewhere. This /// struct gathers that information into one place, with standard names. struct ImageCoordinates { /// The SPIR-V id of the combined coordinate/index vector value. /// /// Note: when indexing a non-arrayed 1D image, this will be a scalar. value_id: Word, /// The SPIR-V id of the type of `value`. type_id: Word, /// The number of components in `value`, if it is a vector, or `None` if it /// is a scalar. size: Option, } /// A trait for image access (load or store) code generators. /// /// Types implementing this trait hold information about an `ImageStore` or /// `ImageLoad` operation that is not affected by the bounds check policy. The /// `generate` method emits code for the access, given the results of bounds /// checking. /// /// The [`image`] bounds checks policy affects access coordinates, level of /// detail, and sample index, but never the image id, result type (if any), or /// the specific SPIR-V instruction used. Types that implement this trait gather /// together the latter category, so we don't have to plumb them through the /// bounds-checking code. /// /// [`image`]: crate::proc::BoundsCheckPolicies::index trait Access { /// The Rust type that represents SPIR-V values and types for this access. /// /// For operations like loads, this is `Word`. For operations like stores, /// this is `()`. /// /// For `ReadZeroSkipWrite`, this will be the type of the selection /// construct that performs the bounds checks, so it must implement /// `MergeTuple`. type Output: MergeTuple + Copy + Clone; /// Write an image access to `block`. /// /// Access the texel at `coordinates_id`. The optional `level_id` indicates /// the level of detail, and `sample_id` is the index of the sample to /// access in a multisampled texel. /// /// This method assumes that `coordinates_id` has already had the image array /// index, if any, folded in, as done by `write_image_coordinates`. /// /// Return the value id produced by the instruction, if any. /// /// Use `id_gen` to generate SPIR-V ids as necessary. fn generate( &self, id_gen: &mut IdGenerator, coordinates_id: Word, level_id: Option, sample_id: Option, block: &mut Block, ) -> Self::Output; /// Return the SPIR-V type of the value produced by the code written by /// `generate`. If the access does not produce a value, `Self::Output` /// should be `()`. fn result_type(&self) -> Self::Output; /// Construct the SPIR-V 'zero' value to be returned for an out-of-bounds /// access under the `ReadZeroSkipWrite` policy. If the access does not /// produce a value, `Self::Output` should be `()`. fn out_of_bounds_value(&self, ctx: &mut BlockContext<'_>) -> Self::Output; } /// Texel access information for an [`ImageLoad`] expression. /// /// [`ImageLoad`]: crate::Expression::ImageLoad struct Load { /// The specific opcode we'll use to perform the fetch. Storage images /// require `OpImageRead`, while sampled images require `OpImageFetch`. opcode: spirv::Op, /// The type id produced by the actual image access instruction. type_id: Word, /// The id of the image being accessed. image_id: Word, } impl Load { fn from_image_expr( ctx: &mut BlockContext<'_>, image_id: Word, image_class: crate::ImageClass, result_type_id: Word, ) -> Result { let opcode = match image_class { crate::ImageClass::Storage { .. } => spirv::Op::ImageRead, crate::ImageClass::Depth { .. } | crate::ImageClass::Sampled { .. } => { spirv::Op::ImageFetch } crate::ImageClass::External => unimplemented!(), }; // `OpImageRead` and `OpImageFetch` instructions produce vec4 // values. Most of the time, we can just use `result_type_id` for // this. The exception is that `Expression::ImageLoad` from a depth // image produces a scalar `f32`, so in that case we need to find // the right SPIR-V type for the access instruction here. let type_id = match image_class { crate::ImageClass::Depth { .. } => ctx.get_numeric_type_id(NumericType::Vector { size: crate::VectorSize::Quad, scalar: crate::Scalar::F32, }), _ => result_type_id, }; Ok(Load { opcode, type_id, image_id, }) } } impl Access for Load { type Output = Word; /// Write an instruction to access a given texel of this image. fn generate( &self, id_gen: &mut IdGenerator, coordinates_id: Word, level_id: Option, sample_id: Option, block: &mut Block, ) -> Word { let texel_id = id_gen.next(); let mut instruction = Instruction::image_fetch_or_read( self.opcode, self.type_id, texel_id, self.image_id, coordinates_id, ); match (level_id, sample_id) { (None, None) => {} (Some(level_id), None) => { instruction.add_operand(spirv::ImageOperands::LOD.bits()); instruction.add_operand(level_id); } (None, Some(sample_id)) => { instruction.add_operand(spirv::ImageOperands::SAMPLE.bits()); instruction.add_operand(sample_id); } // There's no such thing as a multi-sampled mipmap. (Some(_), Some(_)) => unreachable!(), } block.body.push(instruction); texel_id } fn result_type(&self) -> Word { self.type_id } fn out_of_bounds_value(&self, ctx: &mut BlockContext<'_>) -> Word { ctx.writer.get_constant_null(self.type_id) } } /// Texel access information for a [`Store`] statement. /// /// [`Store`]: crate::Statement::Store struct Store { /// The id of the image being written to. image_id: Word, /// The value we're going to write to the texel. value_id: Word, } impl Access for Store { /// Stores don't generate any value. type Output = (); fn generate( &self, _id_gen: &mut IdGenerator, coordinates_id: Word, _level_id: Option, _sample_id: Option, block: &mut Block, ) { block.body.push(Instruction::image_write( self.image_id, coordinates_id, self.value_id, )); } /// Stores don't generate any value, so this just returns `()`. fn result_type(&self) {} /// Stores don't generate any value, so this just returns `()`. fn out_of_bounds_value(&self, _ctx: &mut BlockContext<'_>) {} } impl BlockContext<'_> { /// Extend image coordinates with an array index, if necessary. /// /// Whereas [`Expression::ImageLoad`] and [`ImageSample`] treat the array /// index as a separate operand from the coordinates, SPIR-V image access /// instructions include the array index in the `coordinates` operand. This /// function builds a SPIR-V coordinate vector from a Naga coordinate vector /// and array index, if one is supplied, and returns a `ImageCoordinates` /// struct describing what it built. /// /// If `array_index` is `Some(expr)`, then this function constructs a new /// vector that is `coordinates` with `array_index` concatenated onto the /// end: a `vec2` becomes a `vec3`, a scalar becomes a `vec2`, and so on. /// /// If `array_index` is `None`, then the return value uses `coordinates` /// unchanged. Note that, when indexing a non-arrayed 1D image, this will be /// a scalar value. /// /// If needed, this function generates code to convert the array index, /// always an integer scalar, to match the component type of `coordinates`. /// Naga's `ImageLoad` and SPIR-V's `OpImageRead`, `OpImageFetch`, and /// `OpImageWrite` all use integer coordinates, while Naga's `ImageSample` /// and SPIR-V's `OpImageSample...` instructions all take floating-point /// coordinate vectors. /// /// [`Expression::ImageLoad`]: crate::Expression::ImageLoad /// [`ImageSample`]: crate::Expression::ImageSample fn write_image_coordinates( &mut self, coordinates: Handle, array_index: Option>, block: &mut Block, ) -> Result { use crate::TypeInner as Ti; use crate::VectorSize as Vs; let coordinates_id = self.cached[coordinates]; let ty = &self.fun_info[coordinates].ty; let inner_ty = ty.inner_with(&self.ir_module.types); // If there's no array index, the image coordinates are exactly the // `coordinate` field of the `Expression::ImageLoad`. No work is needed. let array_index = match array_index { None => { let value_id = coordinates_id; let type_id = self.get_expression_type_id(ty); let size = match *inner_ty { Ti::Scalar { .. } => None, Ti::Vector { size, .. } => Some(size), _ => return Err(Error::Validation("coordinate type")), }; return Ok(ImageCoordinates { value_id, type_id, size, }); } Some(ix) => ix, }; // Find the component type of `coordinates`, and figure out the size the // combined coordinate vector will have. let (component_scalar, size) = match *inner_ty { Ti::Scalar(scalar @ crate::Scalar { width: 4, .. }) => (scalar, Vs::Bi), Ti::Vector { scalar: scalar @ crate::Scalar { width: 4, .. }, size: Vs::Bi, } => (scalar, Vs::Tri), Ti::Vector { scalar: scalar @ crate::Scalar { width: 4, .. }, size: Vs::Tri, } => (scalar, Vs::Quad), Ti::Vector { size: Vs::Quad, .. } => { return Err(Error::Validation("extending vec4 coordinate")); } ref other => { log::error!("wrong coordinate type {other:?}"); return Err(Error::Validation("coordinate type")); } }; // Convert the index to the coordinate component type, if necessary. let array_index_id = self.cached[array_index]; let ty = &self.fun_info[array_index].ty; let inner_ty = ty.inner_with(&self.ir_module.types); let array_index_scalar = match *inner_ty { Ti::Scalar( scalar @ crate::Scalar { kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, width: 4, }, ) => scalar, _ => unreachable!("we only allow i32 and u32"), }; let cast = match (component_scalar.kind, array_index_scalar.kind) { (crate::ScalarKind::Sint, crate::ScalarKind::Sint) | (crate::ScalarKind::Uint, crate::ScalarKind::Uint) => None, (crate::ScalarKind::Sint, crate::ScalarKind::Uint) | (crate::ScalarKind::Uint, crate::ScalarKind::Sint) => Some(spirv::Op::Bitcast), (crate::ScalarKind::Float, crate::ScalarKind::Sint) => Some(spirv::Op::ConvertSToF), (crate::ScalarKind::Float, crate::ScalarKind::Uint) => Some(spirv::Op::ConvertUToF), (crate::ScalarKind::Bool, _) => unreachable!("we don't allow bool for component"), (_, crate::ScalarKind::Bool | crate::ScalarKind::Float) => { unreachable!("we don't allow bool or float for array index") } (crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat, _) | (_, crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat) => { unreachable!("abstract types should never reach backends") } }; let reconciled_array_index_id = if let Some(cast) = cast { let component_ty_id = self.get_numeric_type_id(NumericType::Scalar(component_scalar)); let reconciled_id = self.gen_id(); block.body.push(Instruction::unary( cast, component_ty_id, reconciled_id, array_index_id, )); reconciled_id } else { array_index_id }; // Find the SPIR-V type for the combined coordinates/index vector. let type_id = self.get_numeric_type_id(NumericType::Vector { size, scalar: component_scalar, }); // Schmear the coordinates and index together. let value_id = self.gen_id(); block.body.push(Instruction::composite_construct( type_id, value_id, &[coordinates_id, reconciled_array_index_id], )); Ok(ImageCoordinates { value_id, type_id, size: Some(size), }) } pub(super) fn get_handle_id(&mut self, expr_handle: Handle) -> Word { let id = match self.ir_function.expressions[expr_handle] { crate::Expression::GlobalVariable(handle) => { self.writer.global_variables[handle].handle_id } crate::Expression::FunctionArgument(i) => { self.function.parameters[i as usize].handle_id } crate::Expression::Access { .. } | crate::Expression::AccessIndex { .. } => { self.cached[expr_handle] } ref other => unreachable!("Unexpected image expression {:?}", other), }; if id == 0 { unreachable!( "Image expression {:?} doesn't have a handle ID", expr_handle ); } id } /// Generate a vector or scalar 'one' for arithmetic on `coordinates`. /// /// If `coordinates` is a scalar, return a scalar one. Otherwise, return /// a vector of ones. fn write_coordinate_one(&mut self, coordinates: &ImageCoordinates) -> Result { let one = self.get_scope_constant(1); match coordinates.size { None => Ok(one), Some(vector_size) => { let ones = [one; 4]; let id = self.gen_id(); Instruction::constant_composite( coordinates.type_id, id, &ones[..vector_size as usize], ) .to_words(&mut self.writer.logical_layout.declarations); Ok(id) } } } /// Generate code to restrict `input` to fall between zero and one less than /// `size_id`. /// /// Both must be 32-bit scalar integer values, whose type is given by /// `type_id`. The computed value is also of type `type_id`. fn restrict_scalar( &mut self, type_id: Word, input_id: Word, size_id: Word, block: &mut Block, ) -> Result { let i32_one_id = self.get_scope_constant(1); // Subtract one from `size` to get the largest valid value. let limit_id = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::ISub, type_id, limit_id, size_id, i32_one_id, )); // Use an unsigned minimum, to handle both positive out-of-range values // and negative values in a single instruction: negative values of // `input_id` get treated as very large positive values. let restricted_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, spirv::GlslStd450Op::UMin, type_id, restricted_id, &[input_id, limit_id], )); Ok(restricted_id) } /// Write instructions to query the size of an image. /// /// This takes care of selecting the right instruction depending on whether /// a level of detail parameter is present. fn write_coordinate_bounds( &mut self, type_id: Word, image_id: Word, level_id: Option, block: &mut Block, ) -> Word { let coordinate_bounds_id = self.gen_id(); match level_id { Some(level_id) => { // A level of detail was provided, so fetch the image size for // that level. let mut inst = Instruction::image_query( spirv::Op::ImageQuerySizeLod, type_id, coordinate_bounds_id, image_id, ); inst.add_operand(level_id); block.body.push(inst); } _ => { // No level of detail was given. block.body.push(Instruction::image_query( spirv::Op::ImageQuerySize, type_id, coordinate_bounds_id, image_id, )); } } coordinate_bounds_id } /// Write code to restrict coordinates for an image reference. /// /// First, clamp the level of detail or sample index to fall within bounds. /// Then, obtain the image size, possibly using the clamped level of detail. /// Finally, use an unsigned minimum instruction to force all coordinates /// into range. /// /// Return a triple `(COORDS, LEVEL, SAMPLE)`, where `COORDS` is a coordinate /// vector (including the array index, if any), `LEVEL` is an optional level /// of detail, and `SAMPLE` is an optional sample index, all guaranteed to /// be in-bounds for `image_id`. /// /// The result is usually a vector, but it is a scalar when indexing /// non-arrayed 1D images. fn write_restricted_coordinates( &mut self, image_id: Word, coordinates: ImageCoordinates, level_id: Option, sample_id: Option, block: &mut Block, ) -> Result<(Word, Option, Option), Error> { self.writer.require_any( "the `Restrict` image bounds check policy", &[spirv::Capability::ImageQuery], )?; let i32_type_id = self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::I32)); // If `level` is `Some`, clamp it to fall within bounds. This must // happen first, because we'll use it to query the image size for // clamping the actual coordinates. let level_id = level_id .map(|level_id| { // Find the number of mipmap levels in this image. let num_levels_id = self.gen_id(); block.body.push(Instruction::image_query( spirv::Op::ImageQueryLevels, i32_type_id, num_levels_id, image_id, )); self.restrict_scalar(i32_type_id, level_id, num_levels_id, block) }) .transpose()?; // If `sample_id` is `Some`, clamp it to fall within bounds. let sample_id = sample_id .map(|sample_id| { // Find the number of samples per texel. let num_samples_id = self.gen_id(); block.body.push(Instruction::image_query( spirv::Op::ImageQuerySamples, i32_type_id, num_samples_id, image_id, )); self.restrict_scalar(i32_type_id, sample_id, num_samples_id, block) }) .transpose()?; // Obtain the image bounds, including the array element count. let coordinate_bounds_id = self.write_coordinate_bounds(coordinates.type_id, image_id, level_id, block); // Compute maximum valid values from the bounds. let ones = self.write_coordinate_one(&coordinates)?; let coordinate_limit_id = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::ISub, coordinates.type_id, coordinate_limit_id, coordinate_bounds_id, ones, )); // Restrict the coordinates to fall within those bounds. // // Use an unsigned minimum, to handle both positive out-of-range values // and negative values in a single instruction: negative values of // `coordinates` get treated as very large positive values. let restricted_coordinates_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, spirv::GlslStd450Op::UMin, coordinates.type_id, restricted_coordinates_id, &[coordinates.value_id, coordinate_limit_id], )); Ok((restricted_coordinates_id, level_id, sample_id)) } fn write_conditional_image_access( &mut self, image_id: Word, coordinates: ImageCoordinates, level_id: Option, sample_id: Option, block: &mut Block, access: &A, ) -> Result { self.writer.require_any( "the `ReadZeroSkipWrite` image bounds check policy", &[spirv::Capability::ImageQuery], )?; let bool_type_id = self.writer.get_bool_type_id(); let i32_type_id = self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::I32)); let null_id = access.out_of_bounds_value(self); let mut selection = Selection::start(block, access.result_type()); // If `level_id` is `Some`, check whether it is within bounds. This must // happen first, because we'll be supplying this as an argument when we // query the image size. if let Some(level_id) = level_id { // Find the number of mipmap levels in this image. let num_levels_id = self.gen_id(); selection.block().body.push(Instruction::image_query( spirv::Op::ImageQueryLevels, i32_type_id, num_levels_id, image_id, )); let lod_cond_id = self.gen_id(); selection.block().body.push(Instruction::binary( spirv::Op::ULessThan, bool_type_id, lod_cond_id, level_id, num_levels_id, )); selection.if_true(self, lod_cond_id, null_id); } // If `sample_id` is `Some`, check whether it is in bounds. if let Some(sample_id) = sample_id { // Find the number of samples per texel. let num_samples_id = self.gen_id(); selection.block().body.push(Instruction::image_query( spirv::Op::ImageQuerySamples, i32_type_id, num_samples_id, image_id, )); let samples_cond_id = self.gen_id(); selection.block().body.push(Instruction::binary( spirv::Op::ULessThan, bool_type_id, samples_cond_id, sample_id, num_samples_id, )); selection.if_true(self, samples_cond_id, null_id); } // Obtain the image bounds, including any array element count. let coordinate_bounds_id = self.write_coordinate_bounds( coordinates.type_id, image_id, level_id, selection.block(), ); // Compare the coordinates against the bounds. let coords_numeric_type = match coordinates.size { Some(size) => NumericType::Vector { size, scalar: crate::Scalar::BOOL, }, None => NumericType::Scalar(crate::Scalar::BOOL), }; let coords_bool_type_id = self.get_numeric_type_id(coords_numeric_type); let coords_conds_id = self.gen_id(); selection.block().body.push(Instruction::binary( spirv::Op::ULessThan, coords_bool_type_id, coords_conds_id, coordinates.value_id, coordinate_bounds_id, )); // If the comparison above was a vector comparison, then we need to // check that all components of the comparison are true. let coords_cond_id = if coords_bool_type_id != bool_type_id { let id = self.gen_id(); selection.block().body.push(Instruction::relational( spirv::Op::All, bool_type_id, id, coords_conds_id, )); id } else { coords_conds_id }; selection.if_true(self, coords_cond_id, null_id); // All conditions are met. We can carry out the access. let texel_id = access.generate( &mut self.writer.id_gen, coordinates.value_id, level_id, sample_id, selection.block(), ); // This, then, is the value of the 'true' branch. Ok(selection.finish(self, texel_id)) } /// Generate code for an `ImageLoad` expression. /// /// The arguments are the components of an `Expression::ImageLoad` variant. #[allow(clippy::too_many_arguments)] pub(super) fn write_image_load( &mut self, result_type_id: Word, image: Handle, coordinate: Handle, array_index: Option>, level: Option>, sample: Option>, block: &mut Block, ) -> Result { let image_id = self.get_handle_id(image); let image_type = self.fun_info[image].ty.inner_with(&self.ir_module.types); let image_class = match *image_type { crate::TypeInner::Image { class, .. } => class, _ => return Err(Error::Validation("image type")), }; let access = Load::from_image_expr(self, image_id, image_class, result_type_id)?; let coordinates = self.write_image_coordinates(coordinate, array_index, block)?; let level_id = level.map(|expr| self.cached[expr]); let sample_id = sample.map(|expr| self.cached[expr]); // Perform the access, according to the bounds check policy. let access_id = match self.writer.bounds_check_policies.image_load { crate::proc::BoundsCheckPolicy::Restrict => { let (coords, level_id, sample_id) = self.write_restricted_coordinates( image_id, coordinates, level_id, sample_id, block, )?; access.generate(&mut self.writer.id_gen, coords, level_id, sample_id, block) } crate::proc::BoundsCheckPolicy::ReadZeroSkipWrite => self .write_conditional_image_access( image_id, coordinates, level_id, sample_id, block, &access, )?, crate::proc::BoundsCheckPolicy::Unchecked => access.generate( &mut self.writer.id_gen, coordinates.value_id, level_id, sample_id, block, ), }; // For depth images, `ImageLoad` expressions produce a single f32, // whereas the SPIR-V instructions always produce a vec4. So we may have // to pull out the component we need. let result_id = if result_type_id == access.result_type() { // The instruction produced the type we expected. We can use // its result as-is. access_id } else { // For `ImageClass::Depth` images, SPIR-V gave us four components, // but we only want the first one. let component_id = self.gen_id(); block.body.push(Instruction::composite_extract( result_type_id, component_id, access_id, &[0], )); component_id }; Ok(result_id) } /// Generate code for an `ImageSample` expression. /// /// The arguments are the components of an `Expression::ImageSample` variant. #[allow(clippy::too_many_arguments)] pub(super) fn write_image_sample( &mut self, result_type_id: Word, image: Handle, sampler: Handle, gather: Option, coordinate: Handle, array_index: Option>, offset: Option>, level: crate::SampleLevel, depth_ref: Option>, clamp_to_edge: bool, block: &mut Block, ) -> Result { use super::instructions::SampleLod; // image let image_id = self.get_handle_id(image); let image_type = self.fun_info[image].ty.handle().unwrap(); // SPIR-V doesn't know about our `Depth` class, and it returns // `vec4`, so we need to grab the first component out of it. let needs_sub_access = match self.ir_module.types[image_type].inner { crate::TypeInner::Image { class: crate::ImageClass::Depth { .. }, .. } => depth_ref.is_none() && gather.is_none(), _ => false, }; let sample_result_type_id = if needs_sub_access { self.get_numeric_type_id(NumericType::Vector { size: crate::VectorSize::Quad, scalar: crate::Scalar::F32, }) } else { result_type_id }; // OpTypeSampledImage let image_type_id = self.get_handle_type_id(image_type); let sampled_image_type_id = self.get_type_id(LookupType::Local(LocalType::SampledImage { image_type_id })); let sampler_id = self.get_handle_id(sampler); let coordinates = self.write_image_coordinates(coordinate, array_index, block)?; let coordinates_id = if clamp_to_edge { self.writer.require_any( "clamp sample coordinates to edge", &[spirv::Capability::ImageQuery], )?; // clamp_to_edge can only be used with Level 0, and no array offset, offset, // depth_ref or gather. This should have been caught by validation. Rather // than entirely duplicate validation code here just ensure the level is // zero, as we rely on that to query the texture size in order to calculate // the clamped coordinates. if level != crate::SampleLevel::Zero { return Err(Error::Validation( "ImageSample::clamp_to_edge requires SampleLevel::Zero", )); } // Query the size of level 0 of the texture. let image_size_id = self.gen_id(); let vec2u_type_id = self.writer.get_vec2u_type_id(); let const_zero_uint_id = self.writer.get_constant_scalar(crate::Literal::U32(0)); let mut query_inst = Instruction::image_query( spirv::Op::ImageQuerySizeLod, vec2u_type_id, image_size_id, image_id, ); query_inst.add_operand(const_zero_uint_id); block.body.push(query_inst); let image_size_f_id = self.gen_id(); let vec2f_type_id = self.writer.get_vec2f_type_id(); block.body.push(Instruction::unary( spirv::Op::ConvertUToF, vec2f_type_id, image_size_f_id, image_size_id, )); // Calculate the top-left and bottom-right margin for clamping to. I.e. a // half-texel from each side. let const_0_5_f32_id = self.writer.get_constant_scalar(crate::Literal::F32(0.5)); let const_0_5_vec2f_id = self.writer.get_constant_composite( LookupType::Local(LocalType::Numeric(NumericType::Vector { size: crate::VectorSize::Bi, scalar: crate::Scalar::F32, })), &[const_0_5_f32_id, const_0_5_f32_id], ); let margin_left_id = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::FDiv, vec2f_type_id, margin_left_id, const_0_5_vec2f_id, image_size_f_id, )); let const_1_f32_id = self.writer.get_constant_scalar(crate::Literal::F32(1.0)); let const_1_vec2f_id = self.writer.get_constant_composite( LookupType::Local(LocalType::Numeric(NumericType::Vector { size: crate::VectorSize::Bi, scalar: crate::Scalar::F32, })), &[const_1_f32_id, const_1_f32_id], ); let margin_right_id = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::FSub, vec2f_type_id, margin_right_id, const_1_vec2f_id, margin_left_id, )); // Clamp the coords to the calculated margins let clamped_coords_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, spirv::GlslStd450Op::NClamp, vec2f_type_id, clamped_coords_id, &[coordinates.value_id, margin_left_id, margin_right_id], )); clamped_coords_id } else { coordinates.value_id }; let sampled_image_id = self.gen_id(); block.body.push(Instruction::sampled_image( sampled_image_type_id, sampled_image_id, image_id, sampler_id, )); let id = self.gen_id(); let depth_id = depth_ref.map(|handle| self.cached[handle]); let mut mask = spirv::ImageOperands::empty(); mask.set(spirv::ImageOperands::CONST_OFFSET, offset.is_some()); let mut main_instruction = match (level, gather) { (_, Some(component)) => { let component_id = self.get_index_constant(component as u32); let mut inst = Instruction::image_gather( sample_result_type_id, id, sampled_image_id, coordinates_id, component_id, depth_id, ); if !mask.is_empty() { inst.add_operand(mask.bits()); } inst } (crate::SampleLevel::Zero, None) => { let mut inst = Instruction::image_sample( sample_result_type_id, id, SampleLod::Explicit, sampled_image_id, coordinates_id, depth_id, ); let zero_id = self.writer.get_constant_scalar(crate::Literal::F32(0.0)); mask |= spirv::ImageOperands::LOD; inst.add_operand(mask.bits()); inst.add_operand(zero_id); inst } (crate::SampleLevel::Auto, None) => { let mut inst = Instruction::image_sample( sample_result_type_id, id, SampleLod::Implicit, sampled_image_id, coordinates_id, depth_id, ); if !mask.is_empty() { inst.add_operand(mask.bits()); } inst } (crate::SampleLevel::Exact(lod_handle), None) => { let mut inst = Instruction::image_sample( sample_result_type_id, id, SampleLod::Explicit, sampled_image_id, coordinates_id, depth_id, ); let mut lod_id = self.cached[lod_handle]; // SPIR-V expects the LOD to be a float for all image classes. // lod_id, however, will be an integer for depth images, // therefore we must do a conversion. if matches!( self.ir_module.types[image_type].inner, crate::TypeInner::Image { class: crate::ImageClass::Depth { .. }, .. } ) { let lod_f32_id = self.gen_id(); let f32_type_id = self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::F32)); let convert_op = match *self.fun_info[lod_handle] .ty .inner_with(&self.ir_module.types) { crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Uint, width: 4, }) => spirv::Op::ConvertUToF, crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Sint, width: 4, }) => spirv::Op::ConvertSToF, _ => unreachable!(), }; block.body.push(Instruction::unary( convert_op, f32_type_id, lod_f32_id, lod_id, )); lod_id = lod_f32_id; } mask |= spirv::ImageOperands::LOD; inst.add_operand(mask.bits()); inst.add_operand(lod_id); inst } (crate::SampleLevel::Bias(bias_handle), None) => { let mut inst = Instruction::image_sample( sample_result_type_id, id, SampleLod::Implicit, sampled_image_id, coordinates_id, depth_id, ); let bias_id = self.cached[bias_handle]; mask |= spirv::ImageOperands::BIAS; inst.add_operand(mask.bits()); inst.add_operand(bias_id); inst } (crate::SampleLevel::Gradient { x, y }, None) => { let mut inst = Instruction::image_sample( sample_result_type_id, id, SampleLod::Explicit, sampled_image_id, coordinates_id, depth_id, ); let x_id = self.cached[x]; let y_id = self.cached[y]; mask |= spirv::ImageOperands::GRAD; inst.add_operand(mask.bits()); inst.add_operand(x_id); inst.add_operand(y_id); inst } }; if let Some(offset_const) = offset { let offset_id = self.cached[offset_const]; main_instruction.add_operand(offset_id); } block.body.push(main_instruction); let id = if needs_sub_access { let sub_id = self.gen_id(); block.body.push(Instruction::composite_extract( result_type_id, sub_id, id, &[0], )); sub_id } else { id }; Ok(id) } /// Generate code for an `ImageQuery` expression. /// /// The arguments are the components of an `Expression::ImageQuery` variant. pub(super) fn write_image_query( &mut self, result_type_id: Word, image: Handle, query: crate::ImageQuery, block: &mut Block, ) -> Result { use crate::{ImageClass as Ic, ImageDimension as Id, ImageQuery as Iq}; let image_id = self.get_handle_id(image); let image_type = self.fun_info[image].ty.handle().unwrap(); let (dim, arrayed, class) = match self.ir_module.types[image_type].inner { crate::TypeInner::Image { dim, arrayed, class, } => (dim, arrayed, class), _ => { return Err(Error::Validation("image type")); } }; self.writer .require_any("image queries", &[spirv::Capability::ImageQuery])?; let id = match query { Iq::Size { level } => { let dim_coords = match dim { Id::D1 => 1, Id::D2 | Id::Cube => 2, Id::D3 => 3, }; let array_coords = usize::from(arrayed); let vector_size = match dim_coords + array_coords { 2 => Some(crate::VectorSize::Bi), 3 => Some(crate::VectorSize::Tri), 4 => Some(crate::VectorSize::Quad), _ => None, }; let vector_numeric_type = match vector_size { Some(size) => NumericType::Vector { size, scalar: crate::Scalar::U32, }, None => NumericType::Scalar(crate::Scalar::U32), }; let extended_size_type_id = self.get_numeric_type_id(vector_numeric_type); let (query_op, level_id) = match class { Ic::Sampled { multi: true, .. } | Ic::Depth { multi: true } | Ic::Storage { .. } => (spirv::Op::ImageQuerySize, None), _ => { let level_id = match level { Some(expr) => self.cached[expr], None => self.get_index_constant(0), }; (spirv::Op::ImageQuerySizeLod, Some(level_id)) } }; // The ID of the vector returned by SPIR-V, which contains the dimensions // as well as the layer count. let id_extended = self.gen_id(); let mut inst = Instruction::image_query( query_op, extended_size_type_id, id_extended, image_id, ); if let Some(expr_id) = level_id { inst.add_operand(expr_id); } block.body.push(inst); if result_type_id != extended_size_type_id { let id = self.gen_id(); let components = match dim { // always pick the first component, and duplicate it for all 3 dimensions Id::Cube => &[0u32, 0][..], _ => &[0u32, 1, 2, 3][..dim_coords], }; block.body.push(Instruction::vector_shuffle( result_type_id, id, id_extended, id_extended, components, )); id } else { id_extended } } Iq::NumLevels => { let query_id = self.gen_id(); block.body.push(Instruction::image_query( spirv::Op::ImageQueryLevels, result_type_id, query_id, image_id, )); query_id } Iq::NumLayers => { let vec_size = match dim { Id::D1 => crate::VectorSize::Bi, Id::D2 | Id::Cube => crate::VectorSize::Tri, Id::D3 => crate::VectorSize::Quad, }; let extended_size_type_id = self.get_numeric_type_id(NumericType::Vector { size: vec_size, scalar: crate::Scalar::U32, }); let id_extended = self.gen_id(); let mut inst = Instruction::image_query( spirv::Op::ImageQuerySizeLod, extended_size_type_id, id_extended, image_id, ); inst.add_operand(self.get_index_constant(0)); block.body.push(inst); let extract_id = self.gen_id(); block.body.push(Instruction::composite_extract( result_type_id, extract_id, id_extended, &[vec_size as u32 - 1], )); extract_id } Iq::NumSamples => { let query_id = self.gen_id(); block.body.push(Instruction::image_query( spirv::Op::ImageQuerySamples, result_type_id, query_id, image_id, )); query_id } }; Ok(id) } pub(super) fn write_image_store( &mut self, image: Handle, coordinate: Handle, array_index: Option>, value: Handle, block: &mut Block, ) -> Result<(), Error> { let image_id = self.get_handle_id(image); let coordinates = self.write_image_coordinates(coordinate, array_index, block)?; let value_id = self.cached[value]; let write = Store { image_id, value_id }; match *self.fun_info[image].ty.inner_with(&self.ir_module.types) { crate::TypeInner::Image { class: crate::ImageClass::Storage { format: crate::StorageFormat::Bgra8Unorm, .. }, .. } => self.writer.require_any( "Bgra8Unorm storage write", &[spirv::Capability::StorageImageWriteWithoutFormat], )?, _ => {} } write.generate( &mut self.writer.id_gen, coordinates.value_id, None, None, block, ); Ok(()) } pub(super) fn write_image_atomic( &mut self, image: Handle, coordinate: Handle, array_index: Option>, fun: crate::AtomicFunction, value: Handle, block: &mut Block, ) -> Result<(), Error> { let image_id = match self.ir_function.originating_global(image) { Some(handle) => self.writer.global_variables[handle].var_id, _ => return Err(Error::Validation("Unexpected image type")), }; let crate::TypeInner::Image { class, .. } = *self.fun_info[image].ty.inner_with(&self.ir_module.types) else { return Err(Error::Validation("Invalid image type")); }; let crate::ImageClass::Storage { format, .. } = class else { return Err(Error::Validation("Invalid image class")); }; let scalar = format.into(); let scalar_type_id = self.get_numeric_type_id(NumericType::Scalar(scalar)); let pointer_type_id = self.get_pointer_type_id(scalar_type_id, spirv::StorageClass::Image); let signed = scalar.kind == crate::ScalarKind::Sint; if scalar.width == 8 { self.writer .require_any("64 bit image atomics", &[spirv::Capability::Int64Atomics])?; } let pointer_id = self.gen_id(); let coordinates = self.write_image_coordinates(coordinate, array_index, block)?; let sample_id = self.writer.get_constant_scalar(crate::Literal::U32(0)); block.body.push(Instruction::image_texel_pointer( pointer_type_id, pointer_id, image_id, coordinates.value_id, sample_id, )); let op = match fun { crate::AtomicFunction::Add => spirv::Op::AtomicIAdd, crate::AtomicFunction::Subtract => spirv::Op::AtomicISub, crate::AtomicFunction::And => spirv::Op::AtomicAnd, crate::AtomicFunction::ExclusiveOr => spirv::Op::AtomicXor, crate::AtomicFunction::InclusiveOr => spirv::Op::AtomicOr, crate::AtomicFunction::Min if signed => spirv::Op::AtomicSMin, crate::AtomicFunction::Min => spirv::Op::AtomicUMin, crate::AtomicFunction::Max if signed => spirv::Op::AtomicSMax, crate::AtomicFunction::Max => spirv::Op::AtomicUMax, crate::AtomicFunction::Exchange { .. } => { return Err(Error::Validation("Exchange atomics are not supported yet")) } }; let result_type_id = self.get_expression_type_id(&self.fun_info[value].ty); let id = self.gen_id(); let space = crate::AddressSpace::Handle; let (semantics, scope) = space.to_spirv_semantics_and_scope(); let scope_constant_id = self.get_scope_constant(scope as u32); let semantics_id = self.get_index_constant(semantics.bits()); let value_id = self.cached[value]; block.body.push(Instruction::image_atomic( op, result_type_id, id, pointer_id, scope_constant_id, semantics_id, value_id, )); Ok(()) } } ================================================ FILE: naga/src/back/spv/index.rs ================================================ /*! Bounds-checking for SPIR-V output. */ use super::{ helpers::{global_needs_wrapper, map_storage_class}, selection::Selection, Block, BlockContext, Error, IdGenerator, Instruction, Word, }; use crate::{ arena::Handle, proc::{index::GuardedIndex, BoundsCheckPolicy}, }; /// The results of performing a bounds check. /// /// On success, [`write_bounds_check`](BlockContext::write_bounds_check) /// returns a value of this type. The caller can assume that the right /// policy has been applied, and simply do what the variant says. #[derive(Debug)] pub(super) enum BoundsCheckResult { /// The index is statically known and in bounds, with the given value. KnownInBounds(u32), /// The given instruction computes the index to be used. /// /// When [`BoundsCheckPolicy::Restrict`] is in force, this is a /// clamped version of the index the user supplied. /// /// When [`BoundsCheckPolicy::Unchecked`] is in force, this is /// simply the index the user supplied. This variant indicates /// that we couldn't prove statically that the index was in /// bounds; otherwise we would have returned [`KnownInBounds`]. /// /// [`KnownInBounds`]: BoundsCheckResult::KnownInBounds Computed(Word), /// The given instruction computes a boolean condition which is true /// if the index is in bounds. /// /// This is returned when [`BoundsCheckPolicy::ReadZeroSkipWrite`] /// is in force. Conditional { /// The access should only be permitted if this value is true. condition_id: Word, /// The access should use this index value. index_id: Word, }, } /// A value that we either know at translation time, or need to compute at runtime. #[derive(Copy, Clone)] pub(super) enum MaybeKnown { /// The value is known at shader translation time. Known(T), /// The value is computed by the instruction with the given id. Computed(Word), } impl BlockContext<'_> { /// Emit code to compute the length of a run-time array. /// /// Given `array`, an expression referring a runtime-sized array, return the /// instruction id for the array's length. /// /// Runtime-sized arrays may only appear in the values of global /// variables, which must have one of the following Naga types: /// /// 1. A runtime-sized array. /// 2. A struct whose last member is a runtime-sized array. /// 3. A binding array of 2. /// /// Thus, the expression `array` has the form of: /// /// - An optional [`AccessIndex`], for case 2, applied to... /// - An optional [`Access`] or [`AccessIndex`], for case 3, applied to... /// - A [`GlobalVariable`]. /// /// The generated SPIR-V takes into account wrapped globals; see /// [`back::spv::GlobalVariable`] for details. /// /// [`GlobalVariable`]: crate::Expression::GlobalVariable /// [`AccessIndex`]: crate::Expression::AccessIndex /// [`Access`]: crate::Expression::Access /// [`base`]: crate::Expression::Access::base /// [`back::spv::GlobalVariable`]: super::GlobalVariable pub(super) fn write_runtime_array_length( &mut self, array: Handle, block: &mut Block, ) -> Result { // The index into the binding array, if any. let binding_array_index_id: Option; // The handle to the Naga IR global we're referring to. let global_handle: Handle; // At the Naga type level, if the runtime-sized array is the final member of a // struct, this is that member's index. // // This does not cover wrappers: if this backend wrapped the Naga global's // type in a synthetic SPIR-V struct (see `global_needs_wrapper`), this is // `None`. let opt_last_member_index: Option; // Inspect `array` and decide whether we have a binding array and/or an // enclosing struct. match self.ir_function.expressions[array] { crate::Expression::AccessIndex { base, index } => { match self.ir_function.expressions[base] { crate::Expression::AccessIndex { base: base_outer, index: index_outer, } => match self.ir_function.expressions[base_outer] { // An `AccessIndex` of an `AccessIndex` must be a // binding array holding structs whose last members are // runtime-sized arrays. crate::Expression::GlobalVariable(handle) => { let index_id = self.get_index_constant(index_outer); binding_array_index_id = Some(index_id); global_handle = handle; opt_last_member_index = Some(index); } _ => { return Err(Error::Validation( "array length expression: AccessIndex(AccessIndex(Global))", )) } }, crate::Expression::Access { base: base_outer, index: index_outer, } => match self.ir_function.expressions[base_outer] { // Similarly, an `AccessIndex` of an `Access` must be a // binding array holding structs whose last members are // runtime-sized arrays. crate::Expression::GlobalVariable(handle) => { let index_id = self.cached[index_outer]; binding_array_index_id = Some(index_id); global_handle = handle; opt_last_member_index = Some(index); } _ => { return Err(Error::Validation( "array length expression: AccessIndex(Access(Global))", )) } }, crate::Expression::GlobalVariable(handle) => { // An outer `AccessIndex` applied directly to a // `GlobalVariable`. Since binding arrays can only contain // structs, this must be referring to the last member of a // struct that is a runtime-sized array. binding_array_index_id = None; global_handle = handle; opt_last_member_index = Some(index); } _ => { return Err(Error::Validation( "array length expression: AccessIndex()", )) } } } crate::Expression::GlobalVariable(handle) => { // A direct reference to a global variable. This must hold the // runtime-sized array directly. binding_array_index_id = None; global_handle = handle; opt_last_member_index = None; } _ => return Err(Error::Validation("array length expression case-4")), }; // The verifier should have checked this, but make sure the inspection above // agrees with the type about whether a binding array is involved. // // Eventually we do want to support `binding_array>`. This check // ensures that whoever relaxes the validator will get an error message from // us, not just bogus SPIR-V. let global = &self.ir_module.global_variables[global_handle]; match ( &self.ir_module.types[global.ty].inner, binding_array_index_id, ) { (&crate::TypeInner::BindingArray { .. }, Some(_)) => {} (_, None) => {} _ => { return Err(Error::Validation( "array length expression: bad binding array inference", )) } } // SPIR-V allows runtime-sized arrays to appear only as the last member of a // struct. Determine this member's index. let gvar = self.writer.global_variables[global_handle].clone(); let global = &self.ir_module.global_variables[global_handle]; let needs_wrapper = global_needs_wrapper(self.ir_module, global); let (last_member_index, gvar_id) = match (opt_last_member_index, needs_wrapper) { (Some(index), false) => { // At the Naga type level, the runtime-sized array appears as the // final member of a struct, whose index is `index`. We didn't need to // wrap this, since the Naga type meets SPIR-V's requirements already. (index, gvar.access_id) } (None, true) => { // At the Naga type level, the runtime-sized array does not appear // within a struct. We wrapped this in an OpTypeStruct with nothing // else in it, so the index is zero. OpArrayLength wants the pointer // to the wrapper struct, so use `gvar.var_id`. (0, gvar.var_id) } _ => { return Err(Error::Validation( "array length expression: bad SPIR-V wrapper struct inference", )); } }; let structure_id = match binding_array_index_id { // We are indexing inside a binding array, generate the access op. Some(index_id) => { let element_type_id = match self.ir_module.types[global.ty].inner { crate::TypeInner::BindingArray { base, size: _ } => { let base_id = self.get_handle_type_id(base); let class = map_storage_class(global.space); self.get_pointer_type_id(base_id, class) } _ => return Err(Error::Validation("array length expression case-5")), }; let structure_id = self.gen_id(); block.body.push(Instruction::access_chain( element_type_id, structure_id, gvar_id, &[index_id], )); structure_id } None => gvar_id, }; let length_id = self.gen_id(); block.body.push(Instruction::array_length( self.writer.get_u32_type_id(), length_id, structure_id, last_member_index, )); Ok(length_id) } /// Compute the length of a subscriptable value. /// /// Given `sequence`, an expression referring to some indexable type, return /// its length. The result may either be computed by SPIR-V instructions, or /// known at shader translation time. /// /// `sequence` may be a `Vector`, `Matrix`, or `Array`, a `Pointer` to any /// of those, or a `ValuePointer`. An array may be fixed-size, dynamically /// sized, or use a specializable constant as its length. fn write_sequence_length( &mut self, sequence: Handle, block: &mut Block, ) -> Result, Error> { let sequence_ty = self.fun_info[sequence].ty.inner_with(&self.ir_module.types); match sequence_ty.indexable_length_resolved(self.ir_module) { Ok(crate::proc::IndexableLength::Known(known_length)) => { Ok(MaybeKnown::Known(known_length)) } Ok(crate::proc::IndexableLength::Dynamic) => { let length_id = self.write_runtime_array_length(sequence, block)?; Ok(MaybeKnown::Computed(length_id)) } Err(err) => { log::error!("Sequence length for {sequence:?} failed: {err}"); Err(Error::Validation("indexable length")) } } } /// Compute the maximum valid index of a subscriptable value. /// /// Given `sequence`, an expression referring to some indexable type, return /// its maximum valid index - one less than its length. The result may /// either be computed, or known at shader translation time. /// /// `sequence` may be a `Vector`, `Matrix`, or `Array`, a `Pointer` to any /// of those, or a `ValuePointer`. An array may be fixed-size, dynamically /// sized, or use a specializable constant as its length. fn write_sequence_max_index( &mut self, sequence: Handle, block: &mut Block, ) -> Result, Error> { match self.write_sequence_length(sequence, block)? { MaybeKnown::Known(known_length) => { // We should have thrown out all attempts to subscript zero-length // sequences during validation, so the following subtraction should never // underflow. assert!(known_length > 0); // Compute the max index from the length now. Ok(MaybeKnown::Known(known_length - 1)) } MaybeKnown::Computed(length_id) => { // Emit code to compute the max index from the length. let const_one_id = self.get_index_constant(1); let max_index_id = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::ISub, self.writer.get_u32_type_id(), max_index_id, length_id, const_one_id, )); Ok(MaybeKnown::Computed(max_index_id)) } } } /// Restrict an index to be in range for a vector, matrix, or array. /// /// This is used to implement `BoundsCheckPolicy::Restrict`. An in-bounds /// index is left unchanged. An out-of-bounds index is replaced with some /// arbitrary in-bounds index. Note,this is not necessarily clamping; for /// example, negative indices might be changed to refer to the last element /// of the sequence, not the first, as clamping would do. /// /// Either return the restricted index value, if known, or add instructions /// to `block` to compute it, and return the id of the result. See the /// documentation for `BoundsCheckResult` for details. /// /// The `sequence` expression may be a `Vector`, `Matrix`, or `Array`, a /// `Pointer` to any of those, or a `ValuePointer`. An array may be /// fixed-size, dynamically sized, or use a specializable constant as its /// length. pub(super) fn write_restricted_index( &mut self, sequence: Handle, index: GuardedIndex, block: &mut Block, ) -> Result { let max_index = self.write_sequence_max_index(sequence, block)?; // If both are known, we can compute the index to be used // right now. if let (GuardedIndex::Known(index), MaybeKnown::Known(max_index)) = (index, max_index) { let restricted = core::cmp::min(index, max_index); return Ok(BoundsCheckResult::KnownInBounds(restricted)); } let index_id = match index { GuardedIndex::Known(value) => self.get_index_constant(value), GuardedIndex::Expression(expr) => self.cached[expr], }; let max_index_id = match max_index { MaybeKnown::Known(value) => self.get_index_constant(value), MaybeKnown::Computed(id) => id, }; // One or the other of the index or length is dynamic, so emit code for // BoundsCheckPolicy::Restrict. let restricted_index_id = self.gen_id(); block.body.push(Instruction::ext_inst_gl_op( self.writer.gl450_ext_inst_id, spirv::GlslStd450Op::UMin, self.writer.get_u32_type_id(), restricted_index_id, &[index_id, max_index_id], )); Ok(BoundsCheckResult::Computed(restricted_index_id)) } /// Write an index bounds comparison to `block`, if needed. /// /// This is used to implement [`BoundsCheckPolicy::ReadZeroSkipWrite`]. /// /// If we're able to determine statically that `index` is in bounds for /// `sequence`, return `KnownInBounds(value)`, where `value` is the actual /// value of the index. (In principle, one could know that the index is in /// bounds without knowing its specific value, but in our simple-minded /// situation, we always know it.) /// /// If instead we must generate code to perform the comparison at run time, /// return `Conditional(comparison_id)`, where `comparison_id` is an /// instruction producing a boolean value that is true if `index` is in /// bounds for `sequence`. /// /// The `sequence` expression may be a `Vector`, `Matrix`, or `Array`, a /// `Pointer` to any of those, or a `ValuePointer`. An array may be /// fixed-size, dynamically sized, or use a specializable constant as its /// length. fn write_index_comparison( &mut self, sequence: Handle, index: GuardedIndex, block: &mut Block, ) -> Result { let length = self.write_sequence_length(sequence, block)?; // If both are known, we can decide whether the index is in // bounds right now. if let (GuardedIndex::Known(index), MaybeKnown::Known(length)) = (index, length) { if index < length { return Ok(BoundsCheckResult::KnownInBounds(index)); } // In theory, when `index` is bad, we could return a new // `KnownOutOfBounds` variant here. But it's simpler just to fall // through and let the bounds check take place. The shader is broken // anyway, so it doesn't make sense to invest in emitting the ideal // code for it. } let index_id = match index { GuardedIndex::Known(value) => self.get_index_constant(value), GuardedIndex::Expression(expr) => self.cached[expr], }; let length_id = match length { MaybeKnown::Known(value) => self.get_index_constant(value), MaybeKnown::Computed(id) => id, }; // Compare the index against the length. let condition_id = self.gen_id(); block.body.push(Instruction::binary( spirv::Op::ULessThan, self.writer.get_bool_type_id(), condition_id, index_id, length_id, )); // Indicate that we did generate the check. Ok(BoundsCheckResult::Conditional { condition_id, index_id, }) } /// Emit a conditional load for `BoundsCheckPolicy::ReadZeroSkipWrite`. /// /// Generate code to load a value of `result_type` if `condition` is true, /// and generate a null value of that type if it is false. Call `emit_load` /// to emit the instructions to perform the load. Return the id of the /// merged value of the two branches. pub(super) fn write_conditional_indexed_load( &mut self, result_type: Word, condition: Word, block: &mut Block, emit_load: F, ) -> Word where F: FnOnce(&mut IdGenerator, &mut Block) -> Word, { // For the out-of-bounds case, we produce a zero value. let null_id = self.writer.get_constant_null(result_type); let mut selection = Selection::start(block, result_type); // As it turns out, we don't actually need a full 'if-then-else' // structure for this: SPIR-V constants are declared up front, so the // 'else' block would have no instructions. Instead we emit something // like this: // // result = zero; // if in_bounds { // result = do the load; // } // use result; // Continue only if the index was in bounds. Otherwise, branch to the // merge block. selection.if_true(self, condition, null_id); // The in-bounds path. Perform the access and the load. let loaded_value = emit_load(&mut self.writer.id_gen, selection.block()); selection.finish(self, loaded_value) } /// Emit code for bounds checks for an array, vector, or matrix access. /// /// This tries to handle all the critical steps for bounds checks: /// /// - First, select the appropriate bounds check policy for `base`, /// depending on its address space. /// /// - Next, analyze `index` to see if its value is known at /// compile time, in which case we can decide statically whether /// the index is in bounds. /// /// - If the index's value is not known at compile time, emit code to: /// /// - restrict its value (for [`BoundsCheckPolicy::Restrict`]), or /// /// - check whether it's in bounds (for /// [`BoundsCheckPolicy::ReadZeroSkipWrite`]). /// /// Return a [`BoundsCheckResult`] indicating how the index should be /// consumed. See that type's documentation for details. pub(super) fn write_bounds_check( &mut self, base: Handle, mut index: GuardedIndex, block: &mut Block, ) -> Result { // If the value of `index` is known at compile time, find it now. index.try_resolve_to_constant(&self.ir_function.expressions, self.ir_module); let policy = self.writer.bounds_check_policies.choose_policy( base, &self.ir_module.types, self.fun_info, ); Ok(match policy { BoundsCheckPolicy::Restrict => self.write_restricted_index(base, index, block)?, BoundsCheckPolicy::ReadZeroSkipWrite => { self.write_index_comparison(base, index, block)? } BoundsCheckPolicy::Unchecked => match index { GuardedIndex::Known(value) => BoundsCheckResult::KnownInBounds(value), GuardedIndex::Expression(expr) => BoundsCheckResult::Computed(self.cached[expr]), }, }) } /// Emit code to subscript a vector by value with a computed index. /// /// Return the id of the element value. /// /// If `base_id_override` is provided, it is used as the vector expression /// to be subscripted into, rather than the cached value of `base`. pub(super) fn write_vector_access( &mut self, result_type_id: Word, base: Handle, base_id_override: Option, index: GuardedIndex, block: &mut Block, ) -> Result { let base_id = base_id_override.unwrap_or_else(|| self.cached[base]); let result_id = match self.write_bounds_check(base, index, block)? { BoundsCheckResult::KnownInBounds(known_index) => { let result_id = self.gen_id(); block.body.push(Instruction::composite_extract( result_type_id, result_id, base_id, &[known_index], )); result_id } BoundsCheckResult::Computed(computed_index_id) => { let result_id = self.gen_id(); block.body.push(Instruction::vector_extract_dynamic( result_type_id, result_id, base_id, computed_index_id, )); result_id } BoundsCheckResult::Conditional { condition_id, index_id, } => { // Run-time bounds checks were required. Emit // conditional load. self.write_conditional_indexed_load( result_type_id, condition_id, block, |id_gen, block| { // The in-bounds path. Generate the access. let element_id = id_gen.next(); block.body.push(Instruction::vector_extract_dynamic( result_type_id, element_id, base_id, index_id, )); element_id }, ) } }; Ok(result_id) } } ================================================ FILE: naga/src/back/spv/instructions.rs ================================================ use alloc::{vec, vec::Vec}; use spirv::{Op, Word}; use super::{block::DebugInfoInner, helpers}; pub(super) enum Signedness { Unsigned = 0, Signed = 1, } pub(super) enum SampleLod { Explicit, Implicit, } pub(super) struct Case { pub value: Word, pub label_id: Word, } impl super::Instruction { // // Debug Instructions // pub(super) fn string(name: &str, id: Word) -> Self { let mut instruction = Self::new(Op::String); instruction.set_result(id); instruction.add_operands(helpers::string_to_words(name)); instruction } pub(super) fn source( source_language: spirv::SourceLanguage, version: u32, source: &Option, ) -> Self { let mut instruction = Self::new(Op::Source); instruction.add_operand(source_language as u32); instruction.add_operands(helpers::bytes_to_words(&version.to_le_bytes())); if let Some(source) = source.as_ref() { instruction.add_operand(source.source_file_id); instruction.add_operands(helpers::string_to_words(source.source_code)); } instruction } pub(super) fn source_continued(source: &[u8]) -> Self { let mut instruction = Self::new(Op::SourceContinued); instruction.add_operands(helpers::str_bytes_to_words(source)); instruction } pub(super) fn source_auto_continued( source_language: spirv::SourceLanguage, version: u32, source: &Option, ) -> Vec { let mut instructions = vec![]; let with_continue = source.as_ref().and_then(|debug_info| { (debug_info.source_code.len() > u16::MAX as usize).then_some(debug_info) }); if let Some(debug_info) = with_continue { let mut instruction = Self::new(Op::Source); instruction.add_operand(source_language as u32); instruction.add_operands(helpers::bytes_to_words(&version.to_le_bytes())); let words = helpers::string_to_byte_chunks(debug_info.source_code, u16::MAX as usize); instruction.add_operand(debug_info.source_file_id); instruction.add_operands(helpers::str_bytes_to_words(words[0])); instructions.push(instruction); for word_bytes in words[1..].iter() { let instruction_continue = Self::source_continued(word_bytes); instructions.push(instruction_continue); } } else { let instruction = Self::source(source_language, version, source); instructions.push(instruction); } instructions } pub(super) fn name(target_id: Word, name: &str) -> Self { let mut instruction = Self::new(Op::Name); instruction.add_operand(target_id); instruction.add_operands(helpers::string_to_words(name)); instruction } pub(super) fn member_name(target_id: Word, member: Word, name: &str) -> Self { let mut instruction = Self::new(Op::MemberName); instruction.add_operand(target_id); instruction.add_operand(member); instruction.add_operands(helpers::string_to_words(name)); instruction } pub(super) fn line(file: Word, line: Word, column: Word) -> Self { let mut instruction = Self::new(Op::Line); instruction.add_operand(file); instruction.add_operand(line); instruction.add_operand(column); instruction } // // Annotation Instructions // pub(super) fn decorate( target_id: Word, decoration: spirv::Decoration, operands: &[Word], ) -> Self { let mut instruction = Self::new(Op::Decorate); instruction.add_operand(target_id); instruction.add_operand(decoration as u32); for operand in operands { instruction.add_operand(*operand) } instruction } pub(super) fn member_decorate( target_id: Word, member_index: Word, decoration: spirv::Decoration, operands: &[Word], ) -> Self { let mut instruction = Self::new(Op::MemberDecorate); instruction.add_operand(target_id); instruction.add_operand(member_index); instruction.add_operand(decoration as u32); for operand in operands { instruction.add_operand(*operand) } instruction } // // Extension Instructions // pub(super) fn extension(name: &str) -> Self { let mut instruction = Self::new(Op::Extension); instruction.add_operands(helpers::string_to_words(name)); instruction } pub(super) fn ext_inst_import(id: Word, name: &str) -> Self { let mut instruction = Self::new(Op::ExtInstImport); instruction.set_result(id); instruction.add_operands(helpers::string_to_words(name)); instruction } pub(super) fn ext_inst_gl_op( set_id: Word, op: spirv::GlslStd450Op, result_type_id: Word, id: Word, operands: &[Word], ) -> Self { Self::ext_inst(set_id, op as u32, result_type_id, id, operands) } pub(super) fn ext_inst( set_id: Word, op: u32, result_type_id: Word, id: Word, operands: &[Word], ) -> Self { let mut instruction = Self::new(Op::ExtInst); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(set_id); instruction.add_operand(op); for operand in operands { instruction.add_operand(*operand) } instruction } // // Mode-Setting Instructions // pub(super) fn memory_model( addressing_model: spirv::AddressingModel, memory_model: spirv::MemoryModel, ) -> Self { let mut instruction = Self::new(Op::MemoryModel); instruction.add_operand(addressing_model as u32); instruction.add_operand(memory_model as u32); instruction } pub(super) fn entry_point( execution_model: spirv::ExecutionModel, entry_point_id: Word, name: &str, interface_ids: &[Word], ) -> Self { let mut instruction = Self::new(Op::EntryPoint); instruction.add_operand(execution_model as u32); instruction.add_operand(entry_point_id); instruction.add_operands(helpers::string_to_words(name)); for interface_id in interface_ids { instruction.add_operand(*interface_id); } instruction } pub(super) fn execution_mode( entry_point_id: Word, execution_mode: spirv::ExecutionMode, args: &[Word], ) -> Self { let mut instruction = Self::new(Op::ExecutionMode); instruction.add_operand(entry_point_id); instruction.add_operand(execution_mode as u32); for arg in args { instruction.add_operand(*arg); } instruction } pub(super) fn capability(capability: spirv::Capability) -> Self { let mut instruction = Self::new(Op::Capability); instruction.add_operand(capability as u32); instruction } // // Type-Declaration Instructions // pub(super) fn type_void(id: Word) -> Self { let mut instruction = Self::new(Op::TypeVoid); instruction.set_result(id); instruction } pub(super) fn type_bool(id: Word) -> Self { let mut instruction = Self::new(Op::TypeBool); instruction.set_result(id); instruction } pub(super) fn type_int(id: Word, width: Word, signedness: Signedness) -> Self { let mut instruction = Self::new(Op::TypeInt); instruction.set_result(id); instruction.add_operand(width); instruction.add_operand(signedness as u32); instruction } pub(super) fn type_float(id: Word, width: Word) -> Self { let mut instruction = Self::new(Op::TypeFloat); instruction.set_result(id); instruction.add_operand(width); instruction } pub(super) fn type_vector( id: Word, component_type_id: Word, component_count: crate::VectorSize, ) -> Self { let mut instruction = Self::new(Op::TypeVector); instruction.set_result(id); instruction.add_operand(component_type_id); instruction.add_operand(component_count as u32); instruction } pub(super) fn type_matrix( id: Word, column_type_id: Word, column_count: crate::VectorSize, ) -> Self { let mut instruction = Self::new(Op::TypeMatrix); instruction.set_result(id); instruction.add_operand(column_type_id); instruction.add_operand(column_count as u32); instruction } pub(super) fn type_coop_matrix( id: Word, scalar_type_id: Word, scope_id: Word, row_count_id: Word, column_count_id: Word, matrix_use_id: Word, ) -> Self { let mut instruction = Self::new(Op::TypeCooperativeMatrixKHR); instruction.set_result(id); instruction.add_operand(scalar_type_id); instruction.add_operand(scope_id); instruction.add_operand(row_count_id); instruction.add_operand(column_count_id); instruction.add_operand(matrix_use_id); instruction } pub(super) fn type_image( id: Word, sampled_type_id: Word, dim: spirv::Dim, flags: super::ImageTypeFlags, image_format: spirv::ImageFormat, ) -> Self { let mut instruction = Self::new(Op::TypeImage); instruction.set_result(id); instruction.add_operand(sampled_type_id); instruction.add_operand(dim as u32); instruction.add_operand(flags.contains(super::ImageTypeFlags::DEPTH) as u32); instruction.add_operand(flags.contains(super::ImageTypeFlags::ARRAYED) as u32); instruction.add_operand(flags.contains(super::ImageTypeFlags::MULTISAMPLED) as u32); instruction.add_operand(if flags.contains(super::ImageTypeFlags::SAMPLED) { 1 } else { 2 }); instruction.add_operand(image_format as u32); instruction } pub(super) fn type_sampler(id: Word) -> Self { let mut instruction = Self::new(Op::TypeSampler); instruction.set_result(id); instruction } pub(super) fn type_acceleration_structure(id: Word) -> Self { let mut instruction = Self::new(Op::TypeAccelerationStructureKHR); instruction.set_result(id); instruction } pub(super) fn type_ray_query(id: Word) -> Self { let mut instruction = Self::new(Op::TypeRayQueryKHR); instruction.set_result(id); instruction } pub(super) fn type_sampled_image(id: Word, image_type_id: Word) -> Self { let mut instruction = Self::new(Op::TypeSampledImage); instruction.set_result(id); instruction.add_operand(image_type_id); instruction } pub(super) fn type_array(id: Word, element_type_id: Word, length_id: Word) -> Self { let mut instruction = Self::new(Op::TypeArray); instruction.set_result(id); instruction.add_operand(element_type_id); instruction.add_operand(length_id); instruction } pub(super) fn type_runtime_array(id: Word, element_type_id: Word) -> Self { let mut instruction = Self::new(Op::TypeRuntimeArray); instruction.set_result(id); instruction.add_operand(element_type_id); instruction } pub(super) fn type_struct(id: Word, member_ids: &[Word]) -> Self { let mut instruction = Self::new(Op::TypeStruct); instruction.set_result(id); for member_id in member_ids { instruction.add_operand(*member_id) } instruction } pub(super) fn type_pointer( id: Word, storage_class: spirv::StorageClass, type_id: Word, ) -> Self { let mut instruction = Self::new(Op::TypePointer); instruction.set_result(id); instruction.add_operand(storage_class as u32); instruction.add_operand(type_id); instruction } pub(super) fn type_function(id: Word, return_type_id: Word, parameter_ids: &[Word]) -> Self { let mut instruction = Self::new(Op::TypeFunction); instruction.set_result(id); instruction.add_operand(return_type_id); for parameter_id in parameter_ids { instruction.add_operand(*parameter_id); } instruction } // // Constant-Creation Instructions // pub(super) fn constant_null(result_type_id: Word, id: Word) -> Self { let mut instruction = Self::new(Op::ConstantNull); instruction.set_type(result_type_id); instruction.set_result(id); instruction } pub(super) fn constant_true(result_type_id: Word, id: Word) -> Self { let mut instruction = Self::new(Op::ConstantTrue); instruction.set_type(result_type_id); instruction.set_result(id); instruction } pub(super) fn constant_false(result_type_id: Word, id: Word) -> Self { let mut instruction = Self::new(Op::ConstantFalse); instruction.set_type(result_type_id); instruction.set_result(id); instruction } pub(super) fn constant_16bit(result_type_id: Word, id: Word, low: Word) -> Self { Self::constant(result_type_id, id, &[low]) } pub(super) fn constant_32bit(result_type_id: Word, id: Word, value: Word) -> Self { Self::constant(result_type_id, id, &[value]) } pub(super) fn constant_64bit(result_type_id: Word, id: Word, low: Word, high: Word) -> Self { Self::constant(result_type_id, id, &[low, high]) } pub(super) fn constant(result_type_id: Word, id: Word, values: &[Word]) -> Self { let mut instruction = Self::new(Op::Constant); instruction.set_type(result_type_id); instruction.set_result(id); for value in values { instruction.add_operand(*value); } instruction } pub(super) fn constant_composite( result_type_id: Word, id: Word, constituent_ids: &[Word], ) -> Self { let mut instruction = Self::new(Op::ConstantComposite); instruction.set_type(result_type_id); instruction.set_result(id); for constituent_id in constituent_ids { instruction.add_operand(*constituent_id); } instruction } // // Memory Instructions // pub(super) fn variable( result_type_id: Word, id: Word, storage_class: spirv::StorageClass, initializer_id: Option, ) -> Self { let mut instruction = Self::new(Op::Variable); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(storage_class as u32); if let Some(initializer_id) = initializer_id { instruction.add_operand(initializer_id); } instruction } pub(super) fn load( result_type_id: Word, id: Word, pointer_id: Word, memory_access: Option, ) -> Self { let mut instruction = Self::new(Op::Load); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(pointer_id); if let Some(memory_access) = memory_access { instruction.add_operand(memory_access.bits()); } instruction } pub(super) fn atomic_load( result_type_id: Word, id: Word, pointer_id: Word, scope_id: Word, semantics_id: Word, ) -> Self { let mut instruction = Self::new(Op::AtomicLoad); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(pointer_id); instruction.add_operand(scope_id); instruction.add_operand(semantics_id); instruction } pub(super) fn store( pointer_id: Word, value_id: Word, memory_access: Option, ) -> Self { let mut instruction = Self::new(Op::Store); instruction.add_operand(pointer_id); instruction.add_operand(value_id); if let Some(memory_access) = memory_access { instruction.add_operand(memory_access.bits()); } instruction } pub(super) fn atomic_store( pointer_id: Word, scope_id: Word, semantics_id: Word, value_id: Word, ) -> Self { let mut instruction = Self::new(Op::AtomicStore); instruction.add_operand(pointer_id); instruction.add_operand(scope_id); instruction.add_operand(semantics_id); instruction.add_operand(value_id); instruction } pub(super) fn access_chain( result_type_id: Word, id: Word, base_id: Word, index_ids: &[Word], ) -> Self { let mut instruction = Self::new(Op::AccessChain); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(base_id); for index_id in index_ids { instruction.add_operand(*index_id); } instruction } pub(super) fn array_length( result_type_id: Word, id: Word, structure_id: Word, array_member: Word, ) -> Self { let mut instruction = Self::new(Op::ArrayLength); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(structure_id); instruction.add_operand(array_member); instruction } // // Function Instructions // pub(super) fn function( return_type_id: Word, id: Word, function_control: spirv::FunctionControl, function_type_id: Word, ) -> Self { let mut instruction = Self::new(Op::Function); instruction.set_type(return_type_id); instruction.set_result(id); instruction.add_operand(function_control.bits()); instruction.add_operand(function_type_id); instruction } pub(super) fn function_parameter(result_type_id: Word, id: Word) -> Self { let mut instruction = Self::new(Op::FunctionParameter); instruction.set_type(result_type_id); instruction.set_result(id); instruction } pub(super) const fn function_end() -> Self { Self::new(Op::FunctionEnd) } pub(super) fn function_call( result_type_id: Word, id: Word, function_id: Word, argument_ids: &[Word], ) -> Self { let mut instruction = Self::new(Op::FunctionCall); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(function_id); for argument_id in argument_ids { instruction.add_operand(*argument_id); } instruction } // // Image Instructions // pub(super) fn sampled_image( result_type_id: Word, id: Word, image: Word, sampler: Word, ) -> Self { let mut instruction = Self::new(Op::SampledImage); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(image); instruction.add_operand(sampler); instruction } pub(super) fn image_sample( result_type_id: Word, id: Word, lod: SampleLod, sampled_image: Word, coordinates: Word, depth_ref: Option, ) -> Self { let op = match (lod, depth_ref) { (SampleLod::Explicit, None) => Op::ImageSampleExplicitLod, (SampleLod::Implicit, None) => Op::ImageSampleImplicitLod, (SampleLod::Explicit, Some(_)) => Op::ImageSampleDrefExplicitLod, (SampleLod::Implicit, Some(_)) => Op::ImageSampleDrefImplicitLod, }; let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(sampled_image); instruction.add_operand(coordinates); if let Some(dref) = depth_ref { instruction.add_operand(dref); } instruction } pub(super) fn image_gather( result_type_id: Word, id: Word, sampled_image: Word, coordinates: Word, component_id: Word, depth_ref: Option, ) -> Self { let op = match depth_ref { None => Op::ImageGather, Some(_) => Op::ImageDrefGather, }; let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(sampled_image); instruction.add_operand(coordinates); if let Some(dref) = depth_ref { instruction.add_operand(dref); } else { instruction.add_operand(component_id); } instruction } pub(super) fn image_fetch_or_read( op: Op, result_type_id: Word, id: Word, image: Word, coordinates: Word, ) -> Self { let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(image); instruction.add_operand(coordinates); instruction } pub(super) fn image_write(image: Word, coordinates: Word, value: Word) -> Self { let mut instruction = Self::new(Op::ImageWrite); instruction.add_operand(image); instruction.add_operand(coordinates); instruction.add_operand(value); instruction } pub(super) fn image_texel_pointer( result_type_id: Word, id: Word, image: Word, coordinates: Word, sample: Word, ) -> Self { let mut instruction = Self::new(Op::ImageTexelPointer); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(image); instruction.add_operand(coordinates); instruction.add_operand(sample); instruction } pub(super) fn image_atomic( op: Op, result_type_id: Word, id: Word, pointer: Word, scope_id: Word, semantics_id: Word, value: Word, ) -> Self { let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(pointer); instruction.add_operand(scope_id); instruction.add_operand(semantics_id); instruction.add_operand(value); instruction } pub(super) fn image_query(op: Op, result_type_id: Word, id: Word, image: Word) -> Self { let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(image); instruction } // // Ray Query Instructions // #[allow(clippy::too_many_arguments)] pub(super) fn ray_query_initialize( query: Word, acceleration_structure: Word, ray_flags: Word, cull_mask: Word, ray_origin: Word, ray_tmin: Word, ray_dir: Word, ray_tmax: Word, ) -> Self { let mut instruction = Self::new(Op::RayQueryInitializeKHR); instruction.add_operand(query); instruction.add_operand(acceleration_structure); instruction.add_operand(ray_flags); instruction.add_operand(cull_mask); instruction.add_operand(ray_origin); instruction.add_operand(ray_tmin); instruction.add_operand(ray_dir); instruction.add_operand(ray_tmax); instruction } pub(super) fn ray_query_proceed(result_type_id: Word, id: Word, query: Word) -> Self { let mut instruction = Self::new(Op::RayQueryProceedKHR); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(query); instruction } pub(super) fn ray_query_generate_intersection(query: Word, hit: Word) -> Self { let mut instruction = Self::new(Op::RayQueryGenerateIntersectionKHR); instruction.add_operand(query); instruction.add_operand(hit); instruction } pub(super) fn ray_query_confirm_intersection(query: Word) -> Self { let mut instruction = Self::new(Op::RayQueryConfirmIntersectionKHR); instruction.add_operand(query); instruction } pub(super) fn ray_query_return_vertex_position( result_type_id: Word, id: Word, query: Word, intersection: Word, ) -> Self { let mut instruction = Self::new(Op::RayQueryGetIntersectionTriangleVertexPositionsKHR); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(query); instruction.add_operand(intersection); instruction } pub(super) fn ray_query_get_intersection( op: Op, result_type_id: Word, id: Word, query: Word, intersection: Word, ) -> Self { let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(query); instruction.add_operand(intersection); instruction } pub(super) fn ray_query_get_t_min(result_type_id: Word, id: Word, query: Word) -> Self { let mut instruction = Self::new(Op::RayQueryGetRayTMinKHR); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(query); instruction } pub(super) fn ray_query_terminate(query: Word) -> Self { let mut instruction = Self::new(Op::RayQueryTerminateKHR); instruction.add_operand(query); instruction } // // Conversion Instructions // pub(super) fn unary(op: Op, result_type_id: Word, id: Word, value: Word) -> Self { let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(value); instruction } // // Composite Instructions // pub(super) fn composite_construct( result_type_id: Word, id: Word, constituent_ids: &[Word], ) -> Self { let mut instruction = Self::new(Op::CompositeConstruct); instruction.set_type(result_type_id); instruction.set_result(id); for constituent_id in constituent_ids { instruction.add_operand(*constituent_id); } instruction } pub(super) fn composite_extract( result_type_id: Word, id: Word, composite_id: Word, indices: &[Word], ) -> Self { let mut instruction = Self::new(Op::CompositeExtract); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(composite_id); for index in indices { instruction.add_operand(*index); } instruction } pub(super) fn vector_extract_dynamic( result_type_id: Word, id: Word, vector_id: Word, index_id: Word, ) -> Self { let mut instruction = Self::new(Op::VectorExtractDynamic); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(vector_id); instruction.add_operand(index_id); instruction } pub(super) fn vector_shuffle( result_type_id: Word, id: Word, v1_id: Word, v2_id: Word, components: &[Word], ) -> Self { let mut instruction = Self::new(Op::VectorShuffle); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(v1_id); instruction.add_operand(v2_id); for &component in components { instruction.add_operand(component); } instruction } // // Arithmetic Instructions // pub(super) fn binary( op: Op, result_type_id: Word, id: Word, operand_1: Word, operand_2: Word, ) -> Self { let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(operand_1); instruction.add_operand(operand_2); instruction } pub(super) fn ternary( op: Op, result_type_id: Word, id: Word, operand_1: Word, operand_2: Word, operand_3: Word, ) -> Self { let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(operand_1); instruction.add_operand(operand_2); instruction.add_operand(operand_3); instruction } pub(super) fn quaternary( op: Op, result_type_id: Word, id: Word, operand_1: Word, operand_2: Word, operand_3: Word, operand_4: Word, ) -> Self { let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(operand_1); instruction.add_operand(operand_2); instruction.add_operand(operand_3); instruction.add_operand(operand_4); instruction } pub(super) fn relational(op: Op, result_type_id: Word, id: Word, expr_id: Word) -> Self { let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(expr_id); instruction } pub(super) fn atomic_binary( op: Op, result_type_id: Word, id: Word, pointer: Word, scope_id: Word, semantics_id: Word, value: Word, ) -> Self { let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(pointer); instruction.add_operand(scope_id); instruction.add_operand(semantics_id); instruction.add_operand(value); instruction } // // Bit Instructions // // // Relational and Logical Instructions // // // Derivative Instructions // pub(super) fn derivative(op: Op, result_type_id: Word, id: Word, expr_id: Word) -> Self { let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(expr_id); instruction } // // Control-Flow Instructions // pub(super) fn phi( result_type_id: Word, result_id: Word, var_parent_pairs: &[(Word, Word)], ) -> Self { let mut instruction = Self::new(Op::Phi); instruction.add_operand(result_type_id); instruction.add_operand(result_id); for &(variable, parent) in var_parent_pairs { instruction.add_operand(variable); instruction.add_operand(parent); } instruction } pub(super) fn selection_merge( merge_id: Word, selection_control: spirv::SelectionControl, ) -> Self { let mut instruction = Self::new(Op::SelectionMerge); instruction.add_operand(merge_id); instruction.add_operand(selection_control.bits()); instruction } pub(super) fn loop_merge( merge_id: Word, continuing_id: Word, selection_control: spirv::SelectionControl, ) -> Self { let mut instruction = Self::new(Op::LoopMerge); instruction.add_operand(merge_id); instruction.add_operand(continuing_id); instruction.add_operand(selection_control.bits()); instruction } pub(super) fn label(id: Word) -> Self { let mut instruction = Self::new(Op::Label); instruction.set_result(id); instruction } pub(super) fn branch(id: Word) -> Self { let mut instruction = Self::new(Op::Branch); instruction.add_operand(id); instruction } // TODO Branch Weights not implemented. pub(super) fn branch_conditional( condition_id: Word, true_label: Word, false_label: Word, ) -> Self { let mut instruction = Self::new(Op::BranchConditional); instruction.add_operand(condition_id); instruction.add_operand(true_label); instruction.add_operand(false_label); instruction } pub(super) fn switch(selector_id: Word, default_id: Word, cases: &[Case]) -> Self { let mut instruction = Self::new(Op::Switch); instruction.add_operand(selector_id); instruction.add_operand(default_id); for case in cases { instruction.add_operand(case.value); instruction.add_operand(case.label_id); } instruction } pub(super) fn select( result_type_id: Word, id: Word, condition_id: Word, accept_id: Word, reject_id: Word, ) -> Self { let mut instruction = Self::new(Op::Select); instruction.add_operand(result_type_id); instruction.add_operand(id); instruction.add_operand(condition_id); instruction.add_operand(accept_id); instruction.add_operand(reject_id); instruction } pub(super) const fn kill() -> Self { Self::new(Op::Kill) } pub(super) const fn return_void() -> Self { Self::new(Op::Return) } pub(super) fn return_value(value_id: Word) -> Self { let mut instruction = Self::new(Op::ReturnValue); instruction.add_operand(value_id); instruction } // // Atomic Instructions // // // Primitive Instructions // // Barriers pub(super) fn control_barrier( exec_scope_id: Word, mem_scope_id: Word, semantics_id: Word, ) -> Self { let mut instruction = Self::new(Op::ControlBarrier); instruction.add_operand(exec_scope_id); instruction.add_operand(mem_scope_id); instruction.add_operand(semantics_id); instruction } pub(super) fn memory_barrier(mem_scope_id: Word, semantics_id: Word) -> Self { let mut instruction = Self::new(Op::MemoryBarrier); instruction.add_operand(mem_scope_id); instruction.add_operand(semantics_id); instruction } // Group Instructions pub(super) fn group_non_uniform_ballot( result_type_id: Word, id: Word, exec_scope_id: Word, predicate: Word, ) -> Self { let mut instruction = Self::new(Op::GroupNonUniformBallot); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(exec_scope_id); instruction.add_operand(predicate); instruction } pub(super) fn group_non_uniform_broadcast_first( result_type_id: Word, id: Word, exec_scope_id: Word, value: Word, ) -> Self { let mut instruction = Self::new(Op::GroupNonUniformBroadcastFirst); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(exec_scope_id); instruction.add_operand(value); instruction } pub(super) fn group_non_uniform_gather( op: Op, result_type_id: Word, id: Word, exec_scope_id: Word, value: Word, index: Word, ) -> Self { let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(exec_scope_id); instruction.add_operand(value); instruction.add_operand(index); instruction } pub(super) fn group_non_uniform_arithmetic( op: Op, result_type_id: Word, id: Word, exec_scope_id: Word, group_op: Option, value: Word, ) -> Self { let mut instruction = Self::new(op); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(exec_scope_id); if let Some(group_op) = group_op { instruction.add_operand(group_op as u32); } instruction.add_operand(value); instruction } pub(super) fn group_non_uniform_quad_swap( result_type_id: Word, id: Word, exec_scope_id: Word, value: Word, direction: Word, ) -> Self { let mut instruction = Self::new(Op::GroupNonUniformQuadSwap); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(exec_scope_id); instruction.add_operand(value); instruction.add_operand(direction); instruction } // Cooperative operations pub(super) fn coop_load( result_type_id: Word, id: Word, pointer_id: Word, layout_id: Word, stride_id: Word, ) -> Self { let mut instruction = Self::new(Op::CooperativeMatrixLoadKHR); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(pointer_id); instruction.add_operand(layout_id); instruction.add_operand(stride_id); instruction } pub(super) fn coop_store(id: Word, pointer_id: Word, layout_id: Word, stride_id: Word) -> Self { let mut instruction = Self::new(Op::CooperativeMatrixStoreKHR); instruction.add_operand(pointer_id); instruction.add_operand(id); instruction.add_operand(layout_id); instruction.add_operand(stride_id); instruction } pub(super) fn coop_mul_add(result_type_id: Word, id: Word, a: Word, b: Word, c: Word) -> Self { let mut instruction = Self::new(Op::CooperativeMatrixMulAddKHR); instruction.set_type(result_type_id); instruction.set_result(id); instruction.add_operand(a); instruction.add_operand(b); instruction.add_operand(c); instruction } } impl From for spirv::ImageFormat { fn from(format: crate::StorageFormat) -> Self { use crate::StorageFormat as Sf; match format { Sf::R8Unorm => Self::R8, Sf::R8Snorm => Self::R8Snorm, Sf::R8Uint => Self::R8ui, Sf::R8Sint => Self::R8i, Sf::R16Uint => Self::R16ui, Sf::R16Sint => Self::R16i, Sf::R16Float => Self::R16f, Sf::Rg8Unorm => Self::Rg8, Sf::Rg8Snorm => Self::Rg8Snorm, Sf::Rg8Uint => Self::Rg8ui, Sf::Rg8Sint => Self::Rg8i, Sf::R32Uint => Self::R32ui, Sf::R32Sint => Self::R32i, Sf::R32Float => Self::R32f, Sf::Rg16Uint => Self::Rg16ui, Sf::Rg16Sint => Self::Rg16i, Sf::Rg16Float => Self::Rg16f, Sf::Rgba8Unorm => Self::Rgba8, Sf::Rgba8Snorm => Self::Rgba8Snorm, Sf::Rgba8Uint => Self::Rgba8ui, Sf::Rgba8Sint => Self::Rgba8i, Sf::Bgra8Unorm => Self::Unknown, Sf::Rgb10a2Uint => Self::Rgb10a2ui, Sf::Rgb10a2Unorm => Self::Rgb10A2, Sf::Rg11b10Ufloat => Self::R11fG11fB10f, Sf::R64Uint => Self::R64ui, Sf::Rg32Uint => Self::Rg32ui, Sf::Rg32Sint => Self::Rg32i, Sf::Rg32Float => Self::Rg32f, Sf::Rgba16Uint => Self::Rgba16ui, Sf::Rgba16Sint => Self::Rgba16i, Sf::Rgba16Float => Self::Rgba16f, Sf::Rgba32Uint => Self::Rgba32ui, Sf::Rgba32Sint => Self::Rgba32i, Sf::Rgba32Float => Self::Rgba32f, Sf::R16Unorm => Self::R16, Sf::R16Snorm => Self::R16Snorm, Sf::Rg16Unorm => Self::Rg16, Sf::Rg16Snorm => Self::Rg16Snorm, Sf::Rgba16Unorm => Self::Rgba16, Sf::Rgba16Snorm => Self::Rgba16Snorm, } } } impl From for spirv::Dim { fn from(dim: crate::ImageDimension) -> Self { use crate::ImageDimension as Id; match dim { Id::D1 => Self::Dim1D, Id::D2 => Self::Dim2D, Id::D3 => Self::Dim3D, Id::Cube => Self::DimCube, } } } impl From for spirv::CooperativeMatrixUse { fn from(role: crate::CooperativeRole) -> Self { match role { crate::CooperativeRole::A => Self::MatrixAKHR, crate::CooperativeRole::B => Self::MatrixBKHR, crate::CooperativeRole::C => Self::MatrixAccumulatorKHR, } } } ================================================ FILE: naga/src/back/spv/layout.rs ================================================ use alloc::{vec, vec::Vec}; use core::iter; use spirv::{Op, Word, MAGIC_NUMBER}; use super::{Instruction, LogicalLayout, PhysicalLayout}; #[cfg(test)] use alloc::format; // https://github.com/KhronosGroup/SPIRV-Headers/pull/195 const GENERATOR: Word = 28; impl PhysicalLayout { pub(super) const fn new(major_version: u8, minor_version: u8) -> Self { let version = ((major_version as u32) << 16) | ((minor_version as u32) << 8); PhysicalLayout { magic_number: MAGIC_NUMBER, version, generator: GENERATOR, bound: 0, instruction_schema: 0x0u32, } } pub(super) fn in_words(&self, sink: &mut impl Extend) { sink.extend(iter::once(self.magic_number)); sink.extend(iter::once(self.version)); sink.extend(iter::once(self.generator)); sink.extend(iter::once(self.bound)); sink.extend(iter::once(self.instruction_schema)); } /// Returns `(major, minor)`. pub(super) const fn lang_version(&self) -> (u8, u8) { let major = (self.version >> 16) as u8; let minor = (self.version >> 8) as u8; (major, minor) } } impl super::reclaimable::Reclaimable for PhysicalLayout { fn reclaim(self) -> Self { PhysicalLayout { magic_number: self.magic_number, version: self.version, generator: self.generator, instruction_schema: self.instruction_schema, bound: 0, } } } impl LogicalLayout { pub(super) fn in_words(&self, sink: &mut impl Extend) { sink.extend(self.capabilities.iter().cloned()); sink.extend(self.extensions.iter().cloned()); sink.extend(self.ext_inst_imports.iter().cloned()); sink.extend(self.memory_model.iter().cloned()); sink.extend(self.entry_points.iter().cloned()); sink.extend(self.execution_modes.iter().cloned()); sink.extend(self.debugs.iter().cloned()); sink.extend(self.annotations.iter().cloned()); sink.extend(self.declarations.iter().cloned()); sink.extend(self.function_declarations.iter().cloned()); sink.extend(self.function_definitions.iter().cloned()); } } impl super::reclaimable::Reclaimable for LogicalLayout { fn reclaim(self) -> Self { Self { capabilities: self.capabilities.reclaim(), extensions: self.extensions.reclaim(), ext_inst_imports: self.ext_inst_imports.reclaim(), memory_model: self.memory_model.reclaim(), entry_points: self.entry_points.reclaim(), execution_modes: self.execution_modes.reclaim(), debugs: self.debugs.reclaim(), annotations: self.annotations.reclaim(), declarations: self.declarations.reclaim(), function_declarations: self.function_declarations.reclaim(), function_definitions: self.function_definitions.reclaim(), } } } impl Instruction { pub(super) const fn new(op: Op) -> Self { Instruction { op, wc: 1, // Always start at 1 for the first word (OP + WC), type_id: None, result_id: None, operands: vec![], } } pub(super) fn set_type(&mut self, id: Word) { assert!(self.type_id.is_none(), "Type can only be set once"); self.type_id = Some(id); self.wc += 1; } pub(super) fn set_result(&mut self, id: Word) { assert!(self.result_id.is_none(), "Result can only be set once"); self.result_id = Some(id); self.wc += 1; } pub(super) fn add_operand(&mut self, operand: Word) { self.operands.push(operand); self.wc += 1; } pub(super) fn add_operands(&mut self, operands: Vec) { for operand in operands.into_iter() { self.add_operand(operand) } } pub(super) fn to_words(&self, sink: &mut impl Extend) { sink.extend(Some((self.wc << 16) | self.op as u32)); sink.extend(self.type_id); sink.extend(self.result_id); sink.extend(self.operands.iter().cloned()); } } impl Instruction { #[cfg(test)] fn validate(&self, words: &[Word]) { let mut inst_index = 0; let (wc, op) = ((words[inst_index] >> 16) as u16, words[inst_index] as u16); inst_index += 1; assert_eq!(wc, words.len() as u16); assert_eq!(op, self.op as u16); if let Some(type_id) = self.type_id { assert_eq!(words[inst_index], type_id); inst_index += 1; } if let Some(result_id) = self.result_id { assert_eq!(words[inst_index], result_id); inst_index += 1; } for (op_index, i) in (inst_index..wc as usize).enumerate() { assert_eq!(words[i], self.operands[op_index]); } } } #[test] fn test_physical_layout_in_words() { let bound = 5; // The least and most significant bytes of `version` must both be zero // according to the SPIR-V spec. let version = 0x0001_0200; let mut output = vec![]; let mut layout = PhysicalLayout::new(1, 2); layout.bound = bound; layout.in_words(&mut output); assert_eq!(&output, &[MAGIC_NUMBER, version, GENERATOR, bound, 0,]); } #[test] fn test_logical_layout_in_words() { let mut output = vec![]; let mut layout = LogicalLayout::default(); let layout_vectors = 11; let mut instructions = Vec::with_capacity(layout_vectors); let vector_names = &[ "Capabilities", "Extensions", "External Instruction Imports", "Memory Model", "Entry Points", "Execution Modes", "Debugs", "Annotations", "Declarations", "Function Declarations", "Function Definitions", ]; for (i, _) in vector_names.iter().enumerate().take(layout_vectors) { let mut dummy_instruction = Instruction::new(Op::Constant); dummy_instruction.set_type((i + 1) as u32); dummy_instruction.set_result((i + 2) as u32); dummy_instruction.add_operand((i + 3) as u32); dummy_instruction.add_operands(super::helpers::string_to_words( format!("This is the vector: {}", vector_names[i]).as_str(), )); instructions.push(dummy_instruction); } instructions[0].to_words(&mut layout.capabilities); instructions[1].to_words(&mut layout.extensions); instructions[2].to_words(&mut layout.ext_inst_imports); instructions[3].to_words(&mut layout.memory_model); instructions[4].to_words(&mut layout.entry_points); instructions[5].to_words(&mut layout.execution_modes); instructions[6].to_words(&mut layout.debugs); instructions[7].to_words(&mut layout.annotations); instructions[8].to_words(&mut layout.declarations); instructions[9].to_words(&mut layout.function_declarations); instructions[10].to_words(&mut layout.function_definitions); layout.in_words(&mut output); let mut index: usize = 0; for instruction in instructions { let wc = instruction.wc as usize; instruction.validate(&output[index..index + wc]); index += wc; } } ================================================ FILE: naga/src/back/spv/mesh_shader.rs ================================================ use alloc::vec::Vec; use spirv::Word; use crate::{ back::spv::{ helpers::BindingDecorations, writer::FunctionInterface, Block, EntryPointContext, Error, Instruction, WriterFlags, }, non_max_u32::NonMaxU32, Handle, }; #[derive(Clone)] pub struct MeshReturnMember { pub ty_id: u32, pub binding: crate::Binding, } struct PerOutputTypeMeshReturnInfo { max_length_constant: Word, array_type_id: Word, struct_members: Vec, // * Most builtins must be in the same block. // * All bindings must be in their own unique block. // * The primitive indices builtin family needs its own block. // * Cull primitive doesn't care about having its own block, but // some older validation layers didn't respect this. builtin_block: Option, bindings: Vec, } pub struct MeshReturnInfo { /// Id of the workgroup variable containing the data to be output out_variable_id: Word, /// All members of the output variable struct type out_members: Vec, /// Id of the input variable for local invocation id local_invocation_index_var_id: Word, /// Total workgroup size (product) workgroup_size: u32, /// Vertex-specific info vertex_info: PerOutputTypeMeshReturnInfo, /// Primitive-specific info primitive_info: PerOutputTypeMeshReturnInfo, /// Array variable for the primitive indices builtin primitive_indices: Option, } impl super::Writer { /// Sets up an output variable that will handle part of the mesh shader output pub(super) fn write_mesh_return_global_variable( &mut self, ty: u32, array_size_id: u32, ) -> Result { let array_ty = self.id_gen.next(); Instruction::type_array(array_ty, ty, array_size_id) .to_words(&mut self.logical_layout.declarations); let ptr_ty = self.get_pointer_type_id(array_ty, spirv::StorageClass::Output); let var_id = self.id_gen.next(); Instruction::variable(ptr_ty, var_id, spirv::StorageClass::Output, None) .to_words(&mut self.logical_layout.declarations); Ok(var_id) } /// This does various setup things to allow mesh shader entry points /// to be properly written, such as creating the output variables pub(super) fn write_entry_point_mesh_shader_info( &mut self, iface: &mut FunctionInterface, local_invocation_index_id: Option, ir_module: &crate::Module, ep_context: &mut EntryPointContext, ) -> Result<(), Error> { let Some(ref mesh_info) = iface.mesh_info else { return Ok(()); }; // Collect the members in the output structs let out_members: Vec = match &ir_module.types[ir_module.global_variables[mesh_info.output_variable].ty] { &crate::Type { inner: crate::TypeInner::Struct { ref members, .. }, .. } => members .iter() .map(|a| MeshReturnMember { ty_id: self.get_handle_type_id(a.ty), binding: a.binding.clone().unwrap(), }) .collect(), _ => unreachable!(), }; let vertex_array_type_id = out_members .iter() .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Vertices)) .unwrap() .ty_id; let primitive_array_type_id = out_members .iter() .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) .unwrap() .ty_id; let vertex_members = match &ir_module.types[mesh_info.vertex_output_type] { &crate::Type { inner: crate::TypeInner::Struct { ref members, .. }, .. } => members .iter() .map(|a| MeshReturnMember { ty_id: self.get_handle_type_id(a.ty), binding: a.binding.clone().unwrap(), }) .collect(), _ => unreachable!(), }; let primitive_members = match &ir_module.types[mesh_info.primitive_output_type] { &crate::Type { inner: crate::TypeInner::Struct { ref members, .. }, .. } => members .iter() .map(|a| MeshReturnMember { ty_id: self.get_handle_type_id(a.ty), binding: a.binding.clone().unwrap(), }) .collect(), _ => unreachable!(), }; // In the final return, we do a giant memcpy, for which this is helpful let local_invocation_index_var_id = match local_invocation_index_id { Some(a) => a, None => { let u32_id = self.get_u32_type_id(); let var = self.id_gen.next(); Instruction::variable( self.get_pointer_type_id(u32_id, spirv::StorageClass::Input), var, spirv::StorageClass::Input, None, ) .to_words(&mut self.logical_layout.declarations); Instruction::decorate( var, spirv::Decoration::BuiltIn, &[spirv::BuiltIn::LocalInvocationIndex as u32], ) .to_words(&mut self.logical_layout.annotations); iface.varying_ids.push(var); var } }; // This is the information that is passed to the function writer // so that it can write the final return logic let mut mesh_return_info = MeshReturnInfo { out_variable_id: self.global_variables[mesh_info.output_variable].var_id, out_members, local_invocation_index_var_id, workgroup_size: self .get_constant_scalar(crate::Literal::U32(iface.workgroup_size.iter().product())), vertex_info: PerOutputTypeMeshReturnInfo { array_type_id: vertex_array_type_id, struct_members: vertex_members, max_length_constant: self .get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)), bindings: Vec::new(), builtin_block: None, }, primitive_info: PerOutputTypeMeshReturnInfo { array_type_id: primitive_array_type_id, struct_members: primitive_members, max_length_constant: self .get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)), bindings: Vec::new(), builtin_block: None, }, primitive_indices: None, }; let vert_array_size_id = self.get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)); let prim_array_size_id = self.get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)); // Create the actual output variables and types. // According to SPIR-V, // * All builtins must be in the same output `Block` (except builtins for different output types like vertex/primitive) // * Each member with `location` must be in its own `Block` decorated `struct` // * Some builtins like CullPrimitiveEXT don't care as much (older validation layers don't know this! Wonderful!) // * Some builtins like the indices ones need to be in their own output variable without a struct wrapper // Write vertex builtin block if mesh_return_info .vertex_info .struct_members .iter() .any(|a| matches!(a.binding, crate::Binding::BuiltIn(..))) { let builtin_block_ty_id = self.id_gen.next(); let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); let mut bi_index = 0; let mut decorations = Vec::new(); for member in &mesh_return_info.vertex_info.struct_members { if let crate::Binding::BuiltIn(_) = member.binding { ins.add_operand(member.ty_id); let binding = self.map_binding( ir_module, iface.stage, spirv::StorageClass::Output, // Unused except in fragment shaders with other conditions, so we can pass null Handle::new(NonMaxU32::new(0).unwrap()), &member.binding, )?; match binding { BindingDecorations::BuiltIn(bi, others) => { decorations.push(Instruction::member_decorate( builtin_block_ty_id, bi_index, spirv::Decoration::BuiltIn, &[bi as Word], )); for other in others { decorations.push(Instruction::member_decorate( builtin_block_ty_id, bi_index, other, &[], )); } } _ => unreachable!(), } bi_index += 1; } } ins.to_words(&mut self.logical_layout.declarations); decorations.push(Instruction::decorate( builtin_block_ty_id, spirv::Decoration::Block, &[], )); for dec in decorations { dec.to_words(&mut self.logical_layout.annotations); } let v = self.write_mesh_return_global_variable(builtin_block_ty_id, vert_array_size_id)?; iface.varying_ids.push(v); if self.flags.contains(WriterFlags::DEBUG) { self.debugs .push(Instruction::name(v, "naga_vertex_builtin_outputs")); } mesh_return_info.vertex_info.builtin_block = Some(v); } // Write primitive builtin block if mesh_return_info .primitive_info .struct_members .iter() .any(|a| { !matches!( a.binding, crate::Binding::BuiltIn( crate::BuiltIn::PointIndex | crate::BuiltIn::LineIndices | crate::BuiltIn::TriangleIndices ) | crate::Binding::Location { .. } ) }) { let builtin_block_ty_id = self.id_gen.next(); let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); let mut bi_index = 0; let mut decorations = Vec::new(); for member in &mesh_return_info.primitive_info.struct_members { if let crate::Binding::BuiltIn(bi) = member.binding { // These need to be in their own block, unlike other builtins if matches!( bi, crate::BuiltIn::PointIndex | crate::BuiltIn::LineIndices | crate::BuiltIn::TriangleIndices, ) { continue; } ins.add_operand(member.ty_id); let binding = self.map_binding( ir_module, iface.stage, spirv::StorageClass::Output, // Unused except in fragment shaders with other conditions, so we can pass null Handle::new(NonMaxU32::new(0).unwrap()), &member.binding, )?; match binding { BindingDecorations::BuiltIn(bi, others) => { decorations.push(Instruction::member_decorate( builtin_block_ty_id, bi_index, spirv::Decoration::BuiltIn, &[bi as Word], )); for other in others { decorations.push(Instruction::member_decorate( builtin_block_ty_id, bi_index, other, &[], )); } } _ => unreachable!(), } bi_index += 1; } } ins.to_words(&mut self.logical_layout.declarations); decorations.push(Instruction::decorate( builtin_block_ty_id, spirv::Decoration::Block, &[], )); for dec in decorations { dec.to_words(&mut self.logical_layout.annotations); } let v = self.write_mesh_return_global_variable(builtin_block_ty_id, prim_array_size_id)?; Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) .to_words(&mut self.logical_layout.annotations); iface.varying_ids.push(v); if self.flags.contains(WriterFlags::DEBUG) { self.debugs .push(Instruction::name(v, "naga_primitive_builtin_outputs")); } mesh_return_info.primitive_info.builtin_block = Some(v); } // Write vertex binding output blocks (1 array per output struct member) for member in &mesh_return_info.vertex_info.struct_members { match member.binding { crate::Binding::Location { location, .. } => { // Create variable let v = self.write_mesh_return_global_variable(member.ty_id, vert_array_size_id)?; // Decorate the variable with Location Instruction::decorate(v, spirv::Decoration::Location, &[location]) .to_words(&mut self.logical_layout.annotations); iface.varying_ids.push(v); mesh_return_info.vertex_info.bindings.push(v); } crate::Binding::BuiltIn(_) => (), } } // Write primitive binding output blocks (1 array per output struct member) // Also write indices output block for member in &mesh_return_info.primitive_info.struct_members { match member.binding { crate::Binding::BuiltIn( crate::BuiltIn::PointIndex | crate::BuiltIn::LineIndices | crate::BuiltIn::TriangleIndices, ) => { // This is written here instead of as part of the builtin block let v = self.write_mesh_return_global_variable(member.ty_id, prim_array_size_id)?; // This shouldn't be marked as PerPrimitiveEXT Instruction::decorate( v, spirv::Decoration::BuiltIn, &[match member.binding.to_built_in().unwrap() { crate::BuiltIn::PointIndex => spirv::BuiltIn::PrimitivePointIndicesEXT, crate::BuiltIn::LineIndices => spirv::BuiltIn::PrimitiveLineIndicesEXT, crate::BuiltIn::TriangleIndices => { spirv::BuiltIn::PrimitiveTriangleIndicesEXT } _ => unreachable!(), } as Word], ) .to_words(&mut self.logical_layout.annotations); iface.varying_ids.push(v); if self.flags.contains(WriterFlags::DEBUG) { self.debugs .push(Instruction::name(v, "naga_primitive_indices_outputs")); } mesh_return_info.primitive_indices = Some(v); } crate::Binding::Location { location, .. } => { // Create variable let v = self.write_mesh_return_global_variable(member.ty_id, prim_array_size_id)?; // Decorate the variable with Location Instruction::decorate(v, spirv::Decoration::Location, &[location]) .to_words(&mut self.logical_layout.annotations); // Decorate it with PerPrimitiveEXT Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) .to_words(&mut self.logical_layout.annotations); iface.varying_ids.push(v); mesh_return_info.primitive_info.bindings.push(v); } crate::Binding::BuiltIn(_) => (), } } // Store this where it can be read later during function write ep_context.mesh_state = Some(mesh_return_info); Ok(()) } pub(super) fn write_entry_point_task_return( &mut self, value_id: Word, body: &mut Vec, task_payload: Word, ) -> Result { // OpEmitMeshTasksEXT must be called right before exiting (after setting other // output variables if there are any) // Extract the vec3 into 3 u32's let values = [self.id_gen.next(), self.id_gen.next(), self.id_gen.next()]; for (i, &value) in values.iter().enumerate() { let instruction = Instruction::composite_extract( self.get_u32_type_id(), value, value_id, &[i as Word], ); body.push(instruction); } let mut instruction = Instruction::new(spirv::Op::EmitMeshTasksEXT); for id in values { instruction.add_operand(id); } // We have to include the task payload in our call instruction.add_operand(task_payload); Ok(instruction) } /// This writes the actual loop #[allow(clippy::too_many_arguments)] fn write_mesh_copy_loop( &mut self, body: &mut Vec, mut loop_body_block: Vec, loop_header: u32, loop_merge: u32, count_id: u32, index_var: u32, return_info: &MeshReturnInfo, ) { let u32_id = self.get_u32_type_id(); let condition_check = self.id_gen.next(); let loop_continue = self.id_gen.next(); let loop_body = self.id_gen.next(); // Loop header { body.push(Instruction::label(loop_header)); body.push(Instruction::loop_merge( loop_merge, loop_continue, spirv::SelectionControl::empty(), )); body.push(Instruction::branch(condition_check)); } // Condition check - check if i is less than num vertices to copy { body.push(Instruction::label(condition_check)); let val_i = self.id_gen.next(); body.push(Instruction::load(u32_id, val_i, index_var, None)); let cond = self.id_gen.next(); body.push(Instruction::binary( spirv::Op::ULessThan, self.get_bool_type_id(), cond, val_i, count_id, )); body.push(Instruction::branch_conditional(cond, loop_body, loop_merge)); } // Loop body { body.push(Instruction::label(loop_body)); body.append(&mut loop_body_block); body.push(Instruction::branch(loop_continue)); } // Loop continue - increment i { body.push(Instruction::label(loop_continue)); let prev_val_i = self.id_gen.next(); body.push(Instruction::load(u32_id, prev_val_i, index_var, None)); let new_val_i = self.id_gen.next(); body.push(Instruction::binary( spirv::Op::IAdd, u32_id, new_val_i, prev_val_i, return_info.workgroup_size, )); body.push(Instruction::store(index_var, new_val_i, None)); body.push(Instruction::branch(loop_header)); } } /// This generates the instructions used to copy all parts of a single output vertex/primitive /// to their individual output locations fn write_mesh_copy_body( &mut self, is_primitive: bool, return_info: &MeshReturnInfo, index_var: u32, vert_array_ptr: u32, prim_array_ptr: u32, ) -> Vec { let u32_type_id = self.get_u32_type_id(); let mut body = Vec::new(); // Current index to copy let val_i = self.id_gen.next(); body.push(Instruction::load(u32_type_id, val_i, index_var, None)); let info = if is_primitive { &return_info.primitive_info } else { &return_info.vertex_info }; let array_ptr = if is_primitive { prim_array_ptr } else { vert_array_ptr }; let mut builtin_index = 0; let mut binding_index = 0; // Write individual members of the vertex for (member_id, member) in info.struct_members.iter().enumerate() { let val_to_copy_ptr = self.id_gen.next(); body.push(Instruction::access_chain( self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Workgroup), val_to_copy_ptr, array_ptr, &[ val_i, self.get_constant_scalar(crate::Literal::U32(member_id as u32)), ], )); let val_to_copy = self.id_gen.next(); body.push(Instruction::load( member.ty_id, val_to_copy, val_to_copy_ptr, None, )); let mut needs_y_flip = false; let ptr_to_copy_to = self.id_gen.next(); // Get a pointer to the struct member to copy match member.binding { crate::Binding::BuiltIn( crate::BuiltIn::PointIndex | crate::BuiltIn::LineIndices | crate::BuiltIn::TriangleIndices, ) => { body.push(Instruction::access_chain( self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), ptr_to_copy_to, return_info.primitive_indices.unwrap(), &[val_i], )); } crate::Binding::BuiltIn(bi) => { body.push(Instruction::access_chain( self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), ptr_to_copy_to, info.builtin_block.unwrap(), &[ val_i, self.get_constant_scalar(crate::Literal::U32(builtin_index)), ], )); needs_y_flip = matches!(bi, crate::BuiltIn::Position { .. }) && self.flags.contains(WriterFlags::ADJUST_COORDINATE_SPACE); builtin_index += 1; } crate::Binding::Location { .. } => { body.push(Instruction::access_chain( self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), ptr_to_copy_to, info.bindings[binding_index], &[val_i], )); binding_index += 1; } } body.push(Instruction::store(ptr_to_copy_to, val_to_copy, None)); // Flip the vertex position y coordinate in some cases // Can't use epilogue flip because can't read from this storage class if needs_y_flip { let prev_y = self.id_gen.next(); body.push(Instruction::composite_extract( self.get_f32_type_id(), prev_y, val_to_copy, &[1], )); let new_y = self.id_gen.next(); body.push(Instruction::unary( spirv::Op::FNegate, self.get_f32_type_id(), new_y, prev_y, )); let new_ptr_to_copy_to = self.id_gen.next(); body.push(Instruction::access_chain( self.get_f32_pointer_type_id(spirv::StorageClass::Output), new_ptr_to_copy_to, ptr_to_copy_to, &[self.get_constant_scalar(crate::Literal::U32(1))], )); body.push(Instruction::store(new_ptr_to_copy_to, new_y, None)); } } body } /// Writes the return call for a mesh shader, which involves copying previously /// written vertices/primitives into the actual output location. pub(super) fn write_mesh_shader_return( &mut self, return_info: &MeshReturnInfo, block: &mut Block, loop_counter_vertices: u32, loop_counter_primitives: u32, local_invocation_index_id: Word, ) -> Result<(), Error> { let u32_id = self.get_u32_type_id(); // Load the actual vertex and primitive counts let mut load_u32_by_member_index = |members: &[MeshReturnMember], bi: crate::BuiltIn, max: u32| { let member_index = members .iter() .position(|a| a.binding == crate::Binding::BuiltIn(bi)) .unwrap() as u32; let ptr_id = self.id_gen.next(); block.body.push(Instruction::access_chain( self.get_pointer_type_id(u32_id, spirv::StorageClass::Workgroup), ptr_id, return_info.out_variable_id, &[self.get_constant_scalar(crate::Literal::U32(member_index))], )); let before_min_id = self.id_gen.next(); block .body .push(Instruction::load(u32_id, before_min_id, ptr_id, None)); // Clamp the values let id = self.id_gen.next(); block.body.push(Instruction::ext_inst_gl_op( self.gl450_ext_inst_id, spirv::GlslStd450Op::UMin, u32_id, id, &[before_min_id, max], )); id }; let vert_count_id = load_u32_by_member_index( &return_info.out_members, crate::BuiltIn::VertexCount, return_info.vertex_info.max_length_constant, ); let prim_count_id = load_u32_by_member_index( &return_info.out_members, crate::BuiltIn::PrimitiveCount, return_info.primitive_info.max_length_constant, ); // Get pointers to the arrays of data to extract let mut get_array_ptr = |bi: crate::BuiltIn, array_type_id: u32| { let id = self.id_gen.next(); block.body.push(Instruction::access_chain( self.get_pointer_type_id(array_type_id, spirv::StorageClass::Workgroup), id, return_info.out_variable_id, &[self.get_constant_scalar(crate::Literal::U32( return_info .out_members .iter() .position(|a| a.binding == crate::Binding::BuiltIn(bi)) .unwrap() as u32, ))], )); id }; let vert_array_ptr = get_array_ptr( crate::BuiltIn::Vertices, return_info.vertex_info.array_type_id, ); let prim_array_ptr = get_array_ptr( crate::BuiltIn::Primitives, return_info.primitive_info.array_type_id, ); // This must be called exactly once before any other mesh outputs are written { let mut ins = Instruction::new(spirv::Op::SetMeshOutputsEXT); ins.add_operand(vert_count_id); ins.add_operand(prim_count_id); block.body.push(ins); } // This is iterating over every returned vertex and splitting // it out into the multiple per-output arrays. let vertex_loop_header = self.id_gen.next(); let prim_loop_header = self.id_gen.next(); let in_between_loops = self.id_gen.next(); let func_end = self.id_gen.next(); block.body.push(Instruction::store( loop_counter_vertices, local_invocation_index_id, None, )); block.body.push(Instruction::branch(vertex_loop_header)); let vertex_copy_body = self.write_mesh_copy_body( false, return_info, loop_counter_vertices, vert_array_ptr, prim_array_ptr, ); // Write vertex copy loop self.write_mesh_copy_loop( &mut block.body, vertex_copy_body, vertex_loop_header, in_between_loops, vert_count_id, loop_counter_vertices, return_info, ); // In between loops, reset the initial index { block.body.push(Instruction::label(in_between_loops)); block.body.push(Instruction::store( loop_counter_primitives, local_invocation_index_id, None, )); block.body.push(Instruction::branch(prim_loop_header)); } let primitive_copy_body = self.write_mesh_copy_body( true, return_info, loop_counter_primitives, vert_array_ptr, prim_array_ptr, ); // Write primitive copy loop self.write_mesh_copy_loop( &mut block.body, primitive_copy_body, prim_loop_header, func_end, prim_count_id, loop_counter_primitives, return_info, ); block.body.push(Instruction::label(func_end)); Ok(()) } pub(super) fn write_mesh_shader_wrapper( &mut self, return_info: &MeshReturnInfo, inner_id: u32, ) -> Result { let out_id = self.id_gen.next(); let mut function = super::Function::default(); let lookup_function_type = super::LookupFunctionType { parameter_type_ids: alloc::vec![], return_type_id: self.void_type, }; let function_type = self.get_function_type(lookup_function_type); function.signature = Some(Instruction::function( self.void_type, out_id, spirv::FunctionControl::empty(), function_type, )); let u32_id = self.get_u32_type_id(); { let mut block = Block::new(self.id_gen.next()); // A general function variable that we guarantee to allow in the final return. It must be // declared at the top of the function. Currently it is used in the memcpy part to keep // track of the current index to copy. let loop_counter_vertices = self.id_gen.next(); let loop_counter_primitives = self.id_gen.next(); block.body.insert( 0, Instruction::variable( self.get_pointer_type_id(u32_id, spirv::StorageClass::Function), loop_counter_vertices, spirv::StorageClass::Function, None, ), ); block.body.insert( 1, Instruction::variable( self.get_pointer_type_id(u32_id, spirv::StorageClass::Function), loop_counter_primitives, spirv::StorageClass::Function, None, ), ); let local_invocation_index_id = self.id_gen.next(); block.body.push(Instruction::load( u32_id, local_invocation_index_id, return_info.local_invocation_index_var_id, None, )); block.body.push(Instruction::function_call( self.void_type, self.id_gen.next(), inner_id, &[], )); self.write_control_barrier(crate::Barrier::WORK_GROUP, &mut block.body); self.write_mesh_shader_return( return_info, &mut block, loop_counter_vertices, loop_counter_primitives, local_invocation_index_id, )?; function.consume(block, Instruction::return_void()); } function.to_words(&mut self.logical_layout.function_definitions); Ok(out_id) } pub(super) fn write_task_shader_wrapper( &mut self, task_payload: Word, inner_id: u32, ) -> Result { let out_id = self.id_gen.next(); let mut function = super::Function::default(); let lookup_function_type = super::LookupFunctionType { parameter_type_ids: alloc::vec![], return_type_id: self.void_type, }; let function_type = self.get_function_type(lookup_function_type); function.signature = Some(Instruction::function( self.void_type, out_id, spirv::FunctionControl::empty(), function_type, )); { let mut block = Block::new(self.id_gen.next()); let result = self.id_gen.next(); block.body.push(Instruction::function_call( self.get_vec3u_type_id(), result, inner_id, &[], )); self.write_control_barrier(crate::Barrier::WORK_GROUP, &mut block.body); let final_value = if let Some(task_limits) = self.task_dispatch_limits { let zero_u32 = self.get_constant_scalar(crate::Literal::U32(0)); let max_per_dim = self.get_constant_scalar(crate::Literal::U32( task_limits.max_mesh_workgroups_per_dim, )); let max_total = self.get_constant_scalar(crate::Literal::U32( task_limits.max_mesh_workgroups_total, )); let combined_struct_type = self.get_tuple_of_u32s_ty_id(); let values = [self.id_gen.next(), self.id_gen.next(), self.id_gen.next()]; for (i, value) in values.into_iter().enumerate() { block.body.push(Instruction::composite_extract( self.get_u32_type_id(), value, result, &[i as u32], )); } let prod_1 = self.id_gen.next(); let overflows = [self.id_gen.next(), self.id_gen.next()]; { let struct_out = self.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::UMulExtended, combined_struct_type, struct_out, values[0], values[1], )); block.body.push(Instruction::composite_extract( self.get_u32_type_id(), prod_1, struct_out, &[0], )); block.body.push(Instruction::composite_extract( self.get_u32_type_id(), overflows[0], struct_out, &[1], )); } let prod_final = self.id_gen.next(); { let struct_out = self.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::UMulExtended, combined_struct_type, struct_out, prod_1, values[2], )); block.body.push(Instruction::composite_extract( self.get_u32_type_id(), prod_final, struct_out, &[0], )); block.body.push(Instruction::composite_extract( self.get_u32_type_id(), overflows[1], struct_out, &[1], )); } let total_too_large = self.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::UGreaterThan, self.get_bool_type_id(), total_too_large, prod_final, max_total, )); let too_large = [self.id_gen.next(), self.id_gen.next(), self.id_gen.next()]; for (i, value) in values.into_iter().enumerate() { block.body.push(Instruction::binary( spirv::Op::UGreaterThan, self.get_bool_type_id(), too_large[i], value, max_per_dim, )); } let overflow_happens = [self.id_gen.next(), self.id_gen.next()]; for (i, value) in overflows.into_iter().enumerate() { block.body.push(Instruction::binary( spirv::Op::INotEqual, self.get_bool_type_id(), overflow_happens[i], value, zero_u32, )); } let mut current_violates_limits = total_too_large; for is_too_large in too_large { let new = self.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::LogicalOr, self.get_bool_type_id(), new, current_violates_limits, is_too_large, )); current_violates_limits = new; } for overflow_happens in overflow_happens { let new = self.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::LogicalOr, self.get_bool_type_id(), new, current_violates_limits, overflow_happens, )); current_violates_limits = new; } let zero_vec3 = self.id_gen.next(); block.body.push(Instruction::composite_construct( self.get_vec3u_type_id(), zero_vec3, &[zero_u32, zero_u32, zero_u32], )); let final_result = self.id_gen.next(); block.body.push(Instruction::select( self.get_vec3u_type_id(), final_result, current_violates_limits, zero_vec3, result, )); final_result } else { result }; let ins = self.write_entry_point_task_return(final_value, &mut block.body, task_payload)?; function.consume(block, ins); } function.to_words(&mut self.logical_layout.function_definitions); Ok(out_id) } } ================================================ FILE: naga/src/back/spv/mod.rs ================================================ /*! Backend for [SPIR-V][spv] (Standard Portable Intermediate Representation). # Layout of values in `uniform` buffers WGSL's ["Internal Layout of Values"][ilov] rules specify the memory layout of each WGSL type. The memory layout is important for data stored in `uniform` and `storage` buffers, especially when exchanging data with CPU code. Both WGSL and Vulkan specify some conditions that a type's memory layout must satisfy in order to use that type in a `uniform` or `storage` buffer. For `storage` buffers, the WGSL and Vulkan restrictions are compatible, but for `uniform` buffers, WGSL allows some types that Vulkan does not, requiring adjustments when emitting SPIR-V for `uniform` buffers. ## Padding in two-row matrices SPIR-V provides detailed control over the layout of matrix types, and is capable of describing the WGSL memory layout. However, Vulkan imposes additional restrictions. Vulkan's ["extended layout"][extended-layout] (also known as std140) rules apply to types used in `uniform` buffers. Under these rules, matrices are defined in terms of arrays of their vector type, and arrays are defined to have an alignment equal to the alignment of their element type rounded up to a multiple of 16. This means that each column of the matrix has a minimum alignment of 16. WGSL, and consequently Naga IR, on the other hand specifies column alignment equal to the alignment of the vector type, without being rounded up to 16. To compensate for this, for any `struct` used as a `uniform` buffer which contains a two-row matrix, we declare an additional "std140 compatible" type in which each column of the matrix has been decomposed into the containing struct. For example, the following WGSL struct type: ```ignore struct Baz { m: mat3x2, } ``` is rendered as the SPIR-V struct type: ```ignore OpTypeStruct %v2float %v2float %v2float ``` This has the effect that struct indices in Naga IR for such types do not correspond to the struct indices used in SPIR-V. A mapping of struct indices for these types is maintained in [`Std140CompatTypeInfo`]. Additionally, any two-row matrices that are declared directly as uniform buffers without being wrapped in a struct are declared as a struct containing a vector member for each column. Any array of a two-row matrix in a uniform buffer is declared as an array of a struct containing a vector member for each column. Any struct or array within a uniform buffer which contains a member or whose base type requires a std140 compatible type declaration, itself requires a std140 compatible type declaration. Whenever a value of such a type is [`loaded`] we insert code to convert the loaded value from the std140 compatible type to the regular type. This occurs in `BlockContext::write_checked_load`, making use of the wrapper function defined by `Writer::write_wrapped_convert_from_std140_compat_type`. For matrices that have been decomposed as separate columns in the containing struct, we load each column separately then composite the matrix type in `BlockContext::maybe_write_load_uniform_matcx2_struct_member`. Whenever a column of a matrix that has been decomposed into its containing struct is [`accessed`] with a constant index we adjust the emitted access chain to access from the containing struct instead, in `BlockContext::write_access_chain`. Whenever a column of a uniform buffer two-row matrix is [`dynamically accessed`] we must first load the matrix type, converting it from its std140 compatible type as described above, then access the column using the wrapper function defined by `Writer::write_wrapped_matcx2_get_column`. This is handled by `BlockContext::maybe_write_uniform_matcx2_dynamic_access`. Note that this approach differs somewhat from the equivalent code in the HLSL backend. For HLSL all structs containing two-row matrices (or arrays of such) have their declarations modified, not just those used as uniform buffers. Two-row matrices and arrays of such only use modified type declarations when used as uniform buffers, or additionally when used as struct member in any context. This avoids the need to convert struct values when loading from uniform buffers, but when loading arrays and matrices from uniform buffers or from any struct the conversion is still required. In contrast, the approach used here always requires converting *any* affected type when loading from a uniform buffer, but consistently *only* when loading from a uniform buffer. As a result this also means we only have to handle loads and not stores, as uniform buffers are read-only. [spv]: https://www.khronos.org/registry/SPIR-V/ [ilov]: https://gpuweb.github.io/gpuweb/wgsl/#internal-value-layout [extended-layout]: https://docs.vulkan.org/spec/latest/chapters/interfaces.html#interfaces-resources-layout [`loaded`]: crate::Expression::Load [`accessed`]: crate::Expression::AccessIndex [`dynamically accessed`]: crate::Expression::Access */ mod block; mod f16_polyfill; mod helpers; mod image; mod index; mod instructions; mod layout; mod mesh_shader; mod ray; mod reclaimable; mod selection; mod subgroup; mod writer; pub use mesh_shader::{MeshReturnInfo, MeshReturnMember}; pub use spirv::{Capability, SourceLanguage}; use alloc::{string::String, vec::Vec}; use core::ops; use spirv::Word; use thiserror::Error; use crate::arena::{Handle, HandleVec}; use crate::back::TaskDispatchLimits; use crate::proc::{BoundsCheckPolicies, TypeResolution}; #[derive(Clone)] struct PhysicalLayout { magic_number: Word, version: Word, generator: Word, bound: Word, instruction_schema: Word, } #[derive(Default)] struct LogicalLayout { capabilities: Vec, extensions: Vec, ext_inst_imports: Vec, memory_model: Vec, entry_points: Vec, execution_modes: Vec, debugs: Vec, annotations: Vec, declarations: Vec, function_declarations: Vec, function_definitions: Vec, } #[derive(Clone)] struct Instruction { op: spirv::Op, wc: u32, type_id: Option, result_id: Option, operands: Vec, } const BITS_PER_BYTE: crate::Bytes = 8; #[derive(Clone, Debug, Error)] pub enum Error { #[error("The requested entry point couldn't be found")] EntryPointNotFound, #[error("target SPIRV-{0}.{1} is not supported")] UnsupportedVersion(u8, u8), #[error("using {0} requires at least one of the capabilities {1:?}, but none are available")] MissingCapabilities(&'static str, Vec), #[error("unimplemented {0}")] FeatureNotImplemented(&'static str), #[error("module is not validated properly: {0}")] Validation(&'static str), #[error("overrides should not be present at this stage")] Override, #[error(transparent)] ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError), #[error("module requires SPIRV-{0}.{1}, which isn't supported")] SpirvVersionTooLow(u8, u8), #[error("mapping of {0:?} is missing")] MissingBinding(crate::ResourceBinding), } #[derive(Default)] struct IdGenerator(Word); impl IdGenerator { const fn next(&mut self) -> Word { self.0 += 1; self.0 } } #[derive(Debug, Clone)] pub struct DebugInfo<'a> { pub source_code: &'a str, pub file_name: &'a str, pub language: SourceLanguage, } /// A SPIR-V block to which we are still adding instructions. /// /// A `Block` represents a SPIR-V block that does not yet have a termination /// instruction like `OpBranch` or `OpReturn`. /// /// The `OpLabel` that starts the block is implicit. It will be emitted based on /// `label_id` when we write the block to a `LogicalLayout`. /// /// To terminate a `Block`, pass the block and the termination instruction to /// `Function::consume`. This takes ownership of the `Block` and transforms it /// into a `TerminatedBlock`. struct Block { label_id: Word, body: Vec, } /// A SPIR-V block that ends with a termination instruction. struct TerminatedBlock { label_id: Word, body: Vec, } impl Block { const fn new(label_id: Word) -> Self { Block { label_id, body: Vec::new(), } } } struct LocalVariable { id: Word, instruction: Instruction, } struct ResultMember { id: Word, type_id: Word, built_in: Option, } struct EntryPointContext { argument_ids: Vec, results: Vec, task_payload_variable_id: Option, mesh_state: Option, } #[derive(Default)] struct Function { signature: Option, parameters: Vec, variables: crate::FastHashMap, LocalVariable>, /// Map from a local variable that is a ray query to its u32 tracker. ray_query_initialization_tracker_variables: crate::FastHashMap, LocalVariable>, /// Map from a local variable that is a ray query to its tracker for the t max. ray_query_t_max_tracker_variables: crate::FastHashMap, LocalVariable>, /// List of local variables used as a counters to ensure that all loops are bounded. force_loop_bounding_vars: Vec, /// A map from a Naga expression to the temporary SPIR-V variable we have /// spilled its value to, if any. /// /// Naga IR lets us apply [`Access`] expressions to expressions whose value /// is an array or matrix---not a pointer to such---but SPIR-V doesn't have /// instructions that can do the same. So when we encounter such code, we /// spill the expression's value to a generated temporary variable. That, we /// can obtain a pointer to, and then use an `OpAccessChain` instruction to /// do whatever series of [`Access`] and [`AccessIndex`] operations we need /// (with bounds checks). Finally, we generate an `OpLoad` to get the final /// value. /// /// [`Access`]: crate::Expression::Access /// [`AccessIndex`]: crate::Expression::AccessIndex spilled_composites: crate::FastIndexMap, LocalVariable>, /// A set of expressions that are either in [`spilled_composites`] or refer /// to some component/element of such. /// /// [`spilled_composites`]: Function::spilled_composites spilled_accesses: crate::arena::HandleSet, /// A map taking each expression to the number of [`Access`] and /// [`AccessIndex`] expressions that uses it as a base value. If an /// expression has no entry, its count is zero: it is never used as a /// [`Access`] or [`AccessIndex`] base. /// /// We use this, together with [`ExpressionInfo::ref_count`], to recognize /// the tips of chains of [`Access`] and [`AccessIndex`] expressions that /// access spilled values --- expressions in [`spilled_composites`]. We /// defer generating code for the chain until we reach its tip, so we can /// handle it with a single instruction. /// /// [`Access`]: crate::Expression::Access /// [`AccessIndex`]: crate::Expression::AccessIndex /// [`ExpressionInfo::ref_count`]: crate::valid::ExpressionInfo /// [`spilled_composites`]: Function::spilled_composites access_uses: crate::FastHashMap, usize>, blocks: Vec, entry_point_context: Option, } impl Function { fn consume(&mut self, mut block: Block, termination: Instruction) { block.body.push(termination); self.blocks.push(TerminatedBlock { label_id: block.label_id, body: block.body, }) } fn parameter_id(&self, index: u32) -> Word { match self.entry_point_context { Some(ref context) => context.argument_ids[index as usize], None => self.parameters[index as usize] .instruction .result_id .unwrap(), } } } /// Characteristics of a SPIR-V `OpTypeImage` type. /// /// SPIR-V requires non-composite types to be unique, including images. Since we /// use `LocalType` for this deduplication, it's essential that `LocalImageType` /// be equal whenever the corresponding `OpTypeImage`s would be. To reduce the /// likelihood of mistakes, we use fields that correspond exactly to the /// operands of an `OpTypeImage` instruction, using the actual SPIR-V types /// where practical. #[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)] struct LocalImageType { sampled_type: crate::Scalar, dim: spirv::Dim, flags: ImageTypeFlags, image_format: spirv::ImageFormat, } bitflags::bitflags! { /// Flags corresponding to the boolean(-ish) parameters to OpTypeImage. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct ImageTypeFlags: u8 { const DEPTH = 0x1; const ARRAYED = 0x2; const MULTISAMPLED = 0x4; const SAMPLED = 0x8; } } impl LocalImageType { /// Construct a `LocalImageType` from the fields of a `TypeInner::Image`. fn from_inner(dim: crate::ImageDimension, arrayed: bool, class: crate::ImageClass) -> Self { let make_flags = |multi: bool, other: ImageTypeFlags| -> ImageTypeFlags { let mut flags = other; flags.set(ImageTypeFlags::ARRAYED, arrayed); flags.set(ImageTypeFlags::MULTISAMPLED, multi); flags }; let dim = spirv::Dim::from(dim); match class { crate::ImageClass::Sampled { kind, multi } => LocalImageType { sampled_type: crate::Scalar { kind, width: 4 }, dim, flags: make_flags(multi, ImageTypeFlags::SAMPLED), image_format: spirv::ImageFormat::Unknown, }, crate::ImageClass::Depth { multi } => LocalImageType { sampled_type: crate::Scalar { kind: crate::ScalarKind::Float, width: 4, }, dim, flags: make_flags(multi, ImageTypeFlags::DEPTH | ImageTypeFlags::SAMPLED), image_format: spirv::ImageFormat::Unknown, }, crate::ImageClass::Storage { format, access: _ } => LocalImageType { sampled_type: format.into(), dim, flags: make_flags(false, ImageTypeFlags::empty()), image_format: format.into(), }, crate::ImageClass::External => unimplemented!(), } } } /// A numeric type, for use in [`LocalType`]. #[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)] enum NumericType { Scalar(crate::Scalar), Vector { size: crate::VectorSize, scalar: crate::Scalar, }, Matrix { columns: crate::VectorSize, rows: crate::VectorSize, scalar: crate::Scalar, }, } impl NumericType { const fn from_inner(inner: &crate::TypeInner) -> Option { match *inner { crate::TypeInner::Scalar(scalar) | crate::TypeInner::Atomic(scalar) => { Some(NumericType::Scalar(scalar)) } crate::TypeInner::Vector { size, scalar } => Some(NumericType::Vector { size, scalar }), crate::TypeInner::Matrix { columns, rows, scalar, } => Some(NumericType::Matrix { columns, rows, scalar, }), _ => None, } } const fn scalar(self) -> crate::Scalar { match self { NumericType::Scalar(scalar) | NumericType::Vector { scalar, .. } | NumericType::Matrix { scalar, .. } => scalar, } } const fn with_scalar(self, scalar: crate::Scalar) -> Self { match self { NumericType::Scalar(_) => NumericType::Scalar(scalar), NumericType::Vector { size, .. } => NumericType::Vector { size, scalar }, NumericType::Matrix { columns, rows, .. } => NumericType::Matrix { columns, rows, scalar, }, } } } /// A cooperative type, for use in [`LocalType`]. #[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)] enum CooperativeType { Matrix { columns: crate::CooperativeSize, rows: crate::CooperativeSize, scalar: crate::Scalar, role: crate::CooperativeRole, }, } impl CooperativeType { const fn from_inner(inner: &crate::TypeInner) -> Option { match *inner { crate::TypeInner::CooperativeMatrix { columns, rows, scalar, role, } => Some(Self::Matrix { columns, rows, scalar, role, }), _ => None, } } } /// A SPIR-V type constructed during code generation. /// /// This is the variant of [`LookupType`] used to represent types that might not /// be available in the arena. Variants are present here for one of two reasons: /// /// - They represent types synthesized during code generation, as explained /// in the documentation for [`LookupType`]. /// /// - They represent types for which SPIR-V forbids duplicate `OpType...` /// instructions, requiring deduplication. /// /// This is not a complete copy of [`TypeInner`]: for example, SPIR-V generation /// never synthesizes new struct types, so `LocalType` has nothing for that. /// /// Each `LocalType` variant should be handled identically to its analogous /// `TypeInner` variant. You can use the [`Writer::localtype_from_inner`] /// function to help with this, by converting everything possible to a /// `LocalType` before inspecting it. /// /// ## `LocalType` equality and SPIR-V `OpType` uniqueness /// /// The definition of `Eq` on `LocalType` is carefully chosen to help us follow /// certain SPIR-V rules. SPIR-V §2.8 requires some classes of `OpType...` /// instructions to be unique; for example, you can't have two `OpTypeInt 32 1` /// instructions in the same module. All 32-bit signed integers must use the /// same type id. /// /// All SPIR-V types that must be unique can be represented as a `LocalType`, /// and two `LocalType`s are always `Eq` if SPIR-V would require them to use the /// same `OpType...` instruction. This lets us avoid duplicates by recording the /// ids of the type instructions we've already generated in a hash table, /// [`Writer::lookup_type`], keyed by `LocalType`. /// /// As another example, [`LocalImageType`], stored in the `LocalType::Image` /// variant, is designed to help us deduplicate `OpTypeImage` instructions. See /// its documentation for details. /// /// SPIR-V does not require pointer types to be unique - but different /// SPIR-V ids are considered to be distinct pointer types. Since Naga /// uses structural type equality, we need to represent each Naga /// equivalence class with a single SPIR-V `OpTypePointer`. /// /// As it always must, the `Hash` implementation respects the `Eq` relation. /// /// [`TypeInner`]: crate::TypeInner #[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)] enum LocalType { /// A numeric type. Numeric(NumericType), Cooperative(CooperativeType), Pointer { base: Word, class: spirv::StorageClass, }, Image(LocalImageType), SampledImage { image_type_id: Word, }, Sampler, BindingArray { base: Handle, size: u32, }, AccelerationStructure, RayQuery, } /// A type encountered during SPIR-V generation. /// /// In the process of writing SPIR-V, we need to synthesize various types for /// intermediate results and such: pointer types, vector/matrix component types, /// or even booleans, which usually appear in SPIR-V code even when they're not /// used by the module source. /// /// However, we can't use `crate::Type` or `crate::TypeInner` for these, as the /// type arena may not contain what we need (it only contains types used /// directly by other parts of the IR), and the IR module is immutable, so we /// can't add anything to it. /// /// So for local use in the SPIR-V writer, we use this type, which holds either /// a handle into the arena, or a [`LocalType`] containing something synthesized /// locally. /// /// This is very similar to the [`proc::TypeResolution`] enum, with `LocalType` /// playing the role of `TypeInner`. However, `LocalType` also has other /// properties needed for SPIR-V generation; see the description of /// [`LocalType`] for details. /// /// [`proc::TypeResolution`]: crate::proc::TypeResolution #[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)] enum LookupType { Handle(Handle), Local(LocalType), } impl From for LookupType { fn from(local: LocalType) -> Self { Self::Local(local) } } #[derive(Debug, PartialEq, Clone, Hash, Eq)] struct LookupFunctionType { parameter_type_ids: Vec, return_type_id: Word, } #[derive(Debug, PartialEq, Clone, Hash, Eq)] enum LookupRayQueryFunction { Initialize, Proceed, GenerateIntersection, ConfirmIntersection, GetVertexPositions { committed: bool }, GetIntersection { committed: bool }, Terminate, } #[derive(Debug)] enum Dimension { Scalar, Vector, Matrix, CooperativeMatrix, } /// Key used to look up an operation which we have wrapped in a helper /// function, which should be called instead of directly emitting code /// for the expression. See [`Writer::wrapped_functions`]. #[derive(Debug, Eq, PartialEq, Hash)] enum WrappedFunction { BinaryOp { op: crate::BinaryOperator, left_type_id: Word, right_type_id: Word, }, ConvertFromStd140CompatType { r#type: Handle, }, MatCx2GetColumn { r#type: Handle, }, } /// A map from evaluated [`Expression`](crate::Expression)s to their SPIR-V ids. /// /// When we emit code to evaluate a given `Expression`, we record the /// SPIR-V id of its value here, under its `Handle` index. /// /// A `CachedExpressions` value can be indexed by a `Handle` value. /// /// [emit]: index.html#expression-evaluation-time-and-scope #[derive(Default)] struct CachedExpressions { ids: HandleVec, } impl CachedExpressions { fn reset(&mut self, length: usize) { self.ids.clear(); self.ids.resize(length, 0); } } impl ops::Index> for CachedExpressions { type Output = Word; fn index(&self, h: Handle) -> &Word { let id = &self.ids[h]; if *id == 0 { unreachable!("Expression {:?} is not cached!", h); } id } } impl ops::IndexMut> for CachedExpressions { fn index_mut(&mut self, h: Handle) -> &mut Word { let id = &mut self.ids[h]; if *id != 0 { unreachable!("Expression {:?} is already cached!", h); } id } } impl reclaimable::Reclaimable for CachedExpressions { fn reclaim(self) -> Self { CachedExpressions { ids: self.ids.reclaim(), } } } #[derive(Eq, Hash, PartialEq)] enum CachedConstant { Literal(crate::proc::HashableLiteral), Composite { ty: LookupType, constituent_ids: Vec, }, ZeroValue(Word), } /// The SPIR-V representation of a [`crate::GlobalVariable`]. /// /// In the Vulkan spec 1.3.296, the section [Descriptor Set Interface][dsi] says: /// /// > Variables identified with the `Uniform` storage class are used to access /// > transparent buffer backed resources. Such variables *must* be: /// > /// > - typed as `OpTypeStruct`, or an array of this type, /// > /// > - identified with a `Block` or `BufferBlock` decoration, and /// > /// > - laid out explicitly using the `Offset`, `ArrayStride`, and `MatrixStride` /// > decorations as specified in "Offset and Stride Assignment". /// /// This is followed by identical language for the `StorageBuffer`, /// except that a `BufferBlock` decoration is not allowed. /// /// When we encounter a global variable in the [`Storage`] or [`Uniform`] /// address spaces whose type is not already [`Struct`], this backend implicitly /// wraps the global variable in a struct: we generate a SPIR-V global variable /// holding an `OpTypeStruct` with a single member, whose type is what the Naga /// global's type would suggest, decorated as required above. /// /// The [`helpers::global_needs_wrapper`] function determines whether a given /// [`crate::GlobalVariable`] needs to be wrapped. /// /// [dsi]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#interfaces-resources-descset /// [`Storage`]: crate::AddressSpace::Storage /// [`Uniform`]: crate::AddressSpace::Uniform /// [`Struct`]: crate::TypeInner::Struct #[derive(Clone)] struct GlobalVariable { /// The SPIR-V id of the `OpVariable` that declares the global. /// /// If this global has been implicitly wrapped in an `OpTypeStruct`, this id /// refers to the wrapper, not the original Naga value it contains. If you /// need the Naga value, use [`access_id`] instead of this field. /// /// If this global is not implicitly wrapped, this is the same as /// [`access_id`]. /// /// This is used to compute the `access_id` pointer in function prologues, /// and used for `ArrayLength` expressions, which need to pass the wrapper /// struct. /// /// [`access_id`]: GlobalVariable::access_id var_id: Word, /// The loaded value of a `AddressSpace::Handle` global variable. /// /// If the current function uses this global variable, this is the id of an /// `OpLoad` instruction in the function's prologue that loads its value. /// (This value is assigned as we write the prologue code of each function.) /// It is then used for all operations on the global, such as `OpImageSample`. handle_id: Word, /// The SPIR-V id of a pointer to this variable's Naga IR value. /// /// If the current function uses this global variable, and it has been /// implicitly wrapped in an `OpTypeStruct`, this is the id of an /// `OpAccessChain` instruction in the function's prologue that refers to /// the wrapped value inside the struct. (This value is assigned as we write /// the prologue code of each function.) If you need the wrapper struct /// itself, use [`var_id`] instead of this field. /// /// If this global is not implicitly wrapped, this is the same as /// [`var_id`]. /// /// [`var_id`]: GlobalVariable::var_id access_id: Word, } impl GlobalVariable { const fn dummy() -> Self { Self { var_id: 0, handle_id: 0, access_id: 0, } } const fn new(id: Word) -> Self { Self { var_id: id, handle_id: 0, access_id: 0, } } /// Prepare `self` for use within a single function. const fn reset_for_function(&mut self) { self.handle_id = 0; self.access_id = 0; } } struct FunctionArgument { /// Actual instruction of the argument. instruction: Instruction, handle_id: Word, } /// Tracks the expressions for which the backend emits the following instructions: /// - OpConstantTrue /// - OpConstantFalse /// - OpConstant /// - OpConstantComposite /// - OpConstantNull struct ExpressionConstnessTracker { inner: crate::arena::HandleSet, } impl ExpressionConstnessTracker { fn from_arena(arena: &crate::Arena) -> Self { let mut inner = crate::arena::HandleSet::for_arena(arena); for (handle, expr) in arena.iter() { let insert = match *expr { crate::Expression::Literal(_) | crate::Expression::ZeroValue(_) | crate::Expression::Constant(_) => true, crate::Expression::Compose { ref components, .. } => { components.iter().all(|&h| inner.contains(h)) } crate::Expression::Splat { value, .. } => inner.contains(value), _ => false, }; if insert { inner.insert(handle); } } Self { inner } } fn is_const(&self, value: Handle) -> bool { self.inner.contains(value) } } /// General information needed to emit SPIR-V for Naga statements. struct BlockContext<'w> { /// The writer handling the module to which this code belongs. writer: &'w mut Writer, /// The [`Module`](crate::Module) for which we're generating code. ir_module: &'w crate::Module, /// The [`Function`](crate::Function) for which we're generating code. ir_function: &'w crate::Function, /// Information module validation produced about /// [`ir_function`](BlockContext::ir_function). fun_info: &'w crate::valid::FunctionInfo, /// The [`spv::Function`](Function) to which we are contributing SPIR-V instructions. function: &'w mut Function, /// SPIR-V ids for expressions we've evaluated. cached: CachedExpressions, /// The `Writer`'s temporary vector, for convenience. temp_list: Vec, /// Tracks the constness of `Expression`s residing in `self.ir_function.expressions` expression_constness: ExpressionConstnessTracker, force_loop_bounding: bool, /// Hash from an expression whose type is a ray query / pointer to a ray query to its tracker. /// Note: this is sparse, so can't be a handle vec ray_query_tracker_expr: crate::FastHashMap, RayQueryTrackers>, } #[derive(Clone, Copy)] struct RayQueryTrackers { // Initialization tracker initialized_tracker: Word, // Tracks the t max from ray query initialize. // Unlike HLSL, spir-v's equivalent getter for the current committed t has UB (instead of just // returning t_max) if there was no previous hit (though in some places it treats the behaviour as // defined), therefore we must track the tmax inputted into ray query initialize. t_max_tracker: Word, } impl BlockContext<'_> { const fn gen_id(&mut self) -> Word { self.writer.id_gen.next() } fn get_type_id(&mut self, lookup_type: LookupType) -> Word { self.writer.get_type_id(lookup_type) } fn get_handle_type_id(&mut self, handle: Handle) -> Word { self.writer.get_handle_type_id(handle) } fn get_expression_type_id(&mut self, tr: &TypeResolution) -> Word { self.writer.get_expression_type_id(tr) } fn get_index_constant(&mut self, index: Word) -> Word { self.writer.get_constant_scalar(crate::Literal::U32(index)) } fn get_scope_constant(&mut self, scope: Word) -> Word { self.writer .get_constant_scalar(crate::Literal::I32(scope as _)) } fn get_pointer_type_id(&mut self, base: Word, class: spirv::StorageClass) -> Word { self.writer.get_pointer_type_id(base, class) } fn get_numeric_type_id(&mut self, numeric: NumericType) -> Word { self.writer.get_numeric_type_id(numeric) } } /// Information about a type for which we have declared a std140 layout /// compatible variant, because the type is used in a uniform but does not /// adhere to std140 requirements. The uniform will be declared using the /// type `type_id`, and the result of any `Load` will be immediately converted /// to the base type. This is used for matrices with 2 rows, as well as any /// arrays or structs containing such matrices. pub struct Std140CompatTypeInfo { /// ID of the std140 compatible type declaration. type_id: Word, /// For structs, a mapping of Naga IR struct member indices to the indices /// used in the generated SPIR-V. For non-struct types this will be empty. member_indices: Vec, } pub struct Writer { physical_layout: PhysicalLayout, logical_layout: LogicalLayout, id_gen: IdGenerator, /// The set of capabilities modules are permitted to use. /// /// This is initialized from `Options::capabilities`. capabilities_available: Option>, /// The set of capabilities used by this module. /// /// If `capabilities_available` is `Some`, then this is always a subset of /// that. capabilities_used: crate::FastIndexSet, /// The set of spirv extensions used. extensions_used: crate::FastIndexSet<&'static str>, debug_strings: Vec, debugs: Vec, annotations: Vec, flags: WriterFlags, bounds_check_policies: BoundsCheckPolicies, zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode, force_loop_bounding: bool, use_storage_input_output_16: bool, void_type: Word, tuple_of_u32s_ty_id: Option, //TODO: convert most of these into vectors, addressable by handle indices lookup_type: crate::FastHashMap, lookup_function: crate::FastHashMap, Word>, lookup_function_type: crate::FastHashMap, /// Operations which have been wrapped in a helper function. The value is /// the ID of the function, which should be called instead of emitting code /// for the operation directly. wrapped_functions: crate::FastHashMap, /// Indexed by const-expression handle indexes constant_ids: HandleVec, cached_constants: crate::FastHashMap, global_variables: HandleVec, std140_compat_uniform_types: crate::FastHashMap, Std140CompatTypeInfo>, fake_missing_bindings: bool, binding_map: BindingMap, // Cached expressions are only meaningful within a BlockContext, but we // retain the table here between functions to save heap allocations. saved_cached: CachedExpressions, gl450_ext_inst_id: Word, // Just a temporary list of SPIR-V ids temp_list: Vec, ray_query_functions: crate::FastHashMap, /// F16 I/O polyfill manager for handling `f16` input/output variables /// when `StorageInputOutput16` capability is not available. io_f16_polyfills: f16_polyfill::F16IoPolyfill, /// Non semantic debug printf extension `OpExtInstImport` debug_printf: Option, pub(crate) ray_query_initialization_tracking: bool, /// Limits to the mesh shader dispatch group a task workgroup can dispatch. /// /// Metal for example limits to 1024 workgroups per task shader dispatch. Dispatching more is /// undefined behavior, so this would validate that to dispatch zero workgroups. task_dispatch_limits: Option, /// If true, naga may generate checks that the primitive indices are valid in the output. /// /// Currently this validation is unimplemented. mesh_shader_primitive_indices_clamp: bool, } bitflags::bitflags! { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct WriterFlags: u32 { /// Include debug labels for everything. const DEBUG = 0x1; /// Flip Y coordinate of [`BuiltIn::Position`] output. /// /// [`BuiltIn::Position`]: crate::BuiltIn::Position const ADJUST_COORDINATE_SPACE = 0x2; /// Emit [`OpName`][op] for input/output locations. /// /// Contrary to spec, some drivers treat it as semantic, not allowing /// any conflicts. /// /// [op]: https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpName const LABEL_VARYINGS = 0x4; /// Emit [`PointSize`] output builtin to vertex shaders, which is /// required for drawing with `PointList` topology. /// /// [`PointSize`]: crate::BuiltIn::PointSize const FORCE_POINT_SIZE = 0x8; /// Clamp [`BuiltIn::FragDepth`] output between 0 and 1. /// /// [`BuiltIn::FragDepth`]: crate::BuiltIn::FragDepth const CLAMP_FRAG_DEPTH = 0x10; /// Instead of silently failing if the arguments to generate a ray query are /// invalid, uses debug printf extension to print to the command line /// /// Note: VK_KHR_shader_non_semantic_info must be enabled. This will have no /// effect if `options.ray_query_initialization_tracking` is set to false. const PRINT_ON_RAY_QUERY_INITIALIZATION_FAIL = 0x20; } } #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct BindingInfo { pub descriptor_set: u32, pub binding: u32, /// If the binding is an unsized binding array, this overrides the size. pub binding_array_size: Option, } // Using `BTreeMap` instead of `HashMap` so that we can hash itself. pub type BindingMap = alloc::collections::BTreeMap; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ZeroInitializeWorkgroupMemoryMode { /// Via `VK_KHR_zero_initialize_workgroup_memory` or Vulkan 1.3 Native, /// Via assignments + barrier Polyfill, None, } #[derive(Debug, Clone)] pub struct Options<'a> { /// (Major, Minor) target version of the SPIR-V. pub lang_version: (u8, u8), /// Configuration flags for the writer. pub flags: WriterFlags, /// Don't panic on missing bindings. Instead use fake values for `Binding` /// and `DescriptorSet` decorations. This may result in invalid SPIR-V. pub fake_missing_bindings: bool, /// Map of resources to information about the binding. pub binding_map: BindingMap, /// If given, the set of capabilities modules are allowed to use. Code that /// requires capabilities beyond these is rejected with an error. /// /// If this is `None`, all capabilities are permitted. pub capabilities: Option>, /// How should generate code handle array, vector, matrix, or image texel /// indices that are out of range? pub bounds_check_policies: BoundsCheckPolicies, /// Dictates the way workgroup variables should be zero initialized pub zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode, /// If set, loops will have code injected into them, forcing the compiler /// to think the number of iterations is bounded. pub force_loop_bounding: bool, /// if set, ray queries will get a variable to track their state to prevent /// misuse. pub ray_query_initialization_tracking: bool, /// Whether to use the `StorageInputOutput16` capability for `f16` shader I/O. /// When false, `f16` I/O is polyfilled using `f32` types with conversions. pub use_storage_input_output_16: bool, pub debug_info: Option>, pub task_dispatch_limits: Option, pub mesh_shader_primitive_indices_clamp: bool, } impl Default for Options<'_> { fn default() -> Self { let mut flags = WriterFlags::ADJUST_COORDINATE_SPACE | WriterFlags::LABEL_VARYINGS | WriterFlags::CLAMP_FRAG_DEPTH; if cfg!(debug_assertions) { flags |= WriterFlags::DEBUG; } Options { lang_version: (1, 0), flags, fake_missing_bindings: true, binding_map: BindingMap::default(), capabilities: None, bounds_check_policies: BoundsCheckPolicies::default(), zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode::Polyfill, force_loop_bounding: true, ray_query_initialization_tracking: true, use_storage_input_output_16: true, debug_info: None, task_dispatch_limits: None, mesh_shader_primitive_indices_clamp: true, } } } // A subset of options meant to be changed per pipeline. #[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct PipelineOptions { /// The stage of the entry point. pub shader_stage: crate::ShaderStage, /// The name of the entry point. /// /// If no entry point that matches is found while creating a [`Writer`], a error will be thrown. pub entry_point: String, } pub fn write_vec( module: &crate::Module, info: &crate::valid::ModuleInfo, options: &Options, pipeline_options: Option<&PipelineOptions>, ) -> Result, Error> { let mut words: Vec = Vec::new(); let mut w = Writer::new(options)?; w.write( module, info, pipeline_options, &options.debug_info, &mut words, )?; Ok(words) } pub fn supported_capabilities() -> crate::valid::Capabilities { use crate::valid::Capabilities as Caps; Caps::IMMEDIATES | Caps::FLOAT64 | Caps::PRIMITIVE_INDEX | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY | Caps::BUFFER_BINDING_ARRAY | Caps::STORAGE_TEXTURE_BINDING_ARRAY | Caps::STORAGE_BUFFER_BINDING_ARRAY | Caps::ACCELERATION_STRUCTURE_BINDING_ARRAY | Caps::CLIP_DISTANCES // No cull distance | Caps::STORAGE_TEXTURE_16BIT_NORM_FORMATS | Caps::MULTIVIEW | Caps::EARLY_DEPTH_TEST | Caps::MULTISAMPLED_SHADING | Caps::RAY_QUERY | Caps::DUAL_SOURCE_BLENDING | Caps::CUBE_ARRAY_TEXTURES | Caps::SHADER_INT64 | Caps::SUBGROUP | Caps::SUBGROUP_BARRIER | Caps::SUBGROUP_VERTEX_STAGE | Caps::SHADER_INT64_ATOMIC_MIN_MAX | Caps::SHADER_INT64_ATOMIC_ALL_OPS | Caps::SHADER_FLOAT32_ATOMIC | Caps::TEXTURE_ATOMIC | Caps::TEXTURE_INT64_ATOMIC | Caps::RAY_HIT_VERTEX_POSITION | Caps::SHADER_FLOAT16 // No TEXTURE_EXTERNAL | Caps::SHADER_FLOAT16_IN_FLOAT32 | Caps::SHADER_BARYCENTRICS | Caps::MESH_SHADER | Caps::MESH_SHADER_POINT_TOPOLOGY | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING // No BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING | Caps::STORAGE_TEXTURE_BINDING_ARRAY_NON_UNIFORM_INDEXING | Caps::STORAGE_BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING | Caps::COOPERATIVE_MATRIX | Caps::PER_VERTEX // No RAY_TRACING_PIPELINE | Caps::DRAW_INDEX | Caps::MEMORY_DECORATION_COHERENT | Caps::MEMORY_DECORATION_VOLATILE } ================================================ FILE: naga/src/back/spv/ray/mod.rs ================================================ /*! Module for code shared between ray queries and ray tracing pipeline code. Ray tracing pipelines are not yet implemented, so this is empty. */ pub mod query; ================================================ FILE: naga/src/back/spv/ray/query.rs ================================================ /*! Generating SPIR-V for ray query operations. */ use alloc::{vec, vec::Vec}; use super::super::{ Block, BlockContext, Function, FunctionArgument, Instruction, LocalType, LookupFunctionType, LookupRayQueryFunction, NumericType, Writer, WriterFlags, }; use crate::{arena::Handle, back::RayQueryPoint}; /// helper function to check if a particular flag is set in a u32. fn write_ray_flags_contains_flags( writer: &mut Writer, block: &mut Block, id: spirv::Word, flag: u32, ) -> spirv::Word { let bit_id = writer.get_constant_scalar(crate::Literal::U32(flag)); let zero_id = writer.get_constant_scalar(crate::Literal::U32(0)); let u32_type_id = writer.get_u32_type_id(); let bool_ty = writer.get_bool_type_id(); let and_id = writer.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::BitwiseAnd, u32_type_id, and_id, id, bit_id, )); let eq_id = writer.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::INotEqual, bool_ty, eq_id, and_id, zero_id, )); eq_id } impl Writer { /// writes a logical and of two scalar booleans fn write_logical_and( &mut self, block: &mut Block, one: spirv::Word, two: spirv::Word, ) -> spirv::Word { let id = self.id_gen.next(); let bool_id = self.get_bool_type_id(); block.body.push(Instruction::binary( spirv::Op::LogicalAnd, bool_id, id, one, two, )); id } fn write_reduce_and(&mut self, block: &mut Block, mut bools: Vec) -> spirv::Word { // The combined `and`ed together of all of the bools up to this point. let mut current_combined = bools.pop().unwrap(); for boolean in bools { current_combined = self.write_logical_and(block, current_combined, boolean) } current_combined } // returns the id of the function, the function, and ids for its arguments. fn write_function_signature( &mut self, arg_types: &[spirv::Word], return_ty: spirv::Word, ) -> (spirv::Word, Function, Vec) { let func_ty = self.get_function_type(LookupFunctionType { parameter_type_ids: Vec::from(arg_types), return_type_id: return_ty, }); let mut function = Function::default(); let func_id = self.id_gen.next(); function.signature = Some(Instruction::function( return_ty, func_id, spirv::FunctionControl::empty(), func_ty, )); let mut arg_ids = Vec::with_capacity(arg_types.len()); for (idx, &arg_ty) in arg_types.iter().enumerate() { let id = self.id_gen.next(); let instruction = Instruction::function_parameter(arg_ty, id); function.parameters.push(FunctionArgument { instruction, handle_id: idx as u32, }); arg_ids.push(id); } (func_id, function, arg_ids) } pub(in super::super) fn write_ray_query_get_intersection_function( &mut self, is_committed: bool, ir_module: &crate::Module, ) -> spirv::Word { if let Some(&word) = self.ray_query_functions .get(&LookupRayQueryFunction::GetIntersection { committed: is_committed, }) { return word; } let ray_intersection = ir_module.special_types.ray_intersection.unwrap(); let intersection_type_id = self.get_handle_type_id(ray_intersection); let intersection_pointer_type_id = self.get_pointer_type_id(intersection_type_id, spirv::StorageClass::Function); let flag_type_id = self.get_u32_type_id(); let flag_pointer_type_id = self.get_pointer_type_id(flag_type_id, spirv::StorageClass::Function); let transform_type_id = self.get_numeric_type_id(NumericType::Matrix { columns: crate::VectorSize::Quad, rows: crate::VectorSize::Tri, scalar: crate::Scalar::F32, }); let transform_pointer_type_id = self.get_pointer_type_id(transform_type_id, spirv::StorageClass::Function); let barycentrics_type_id = self.get_numeric_type_id(NumericType::Vector { size: crate::VectorSize::Bi, scalar: crate::Scalar::F32, }); let barycentrics_pointer_type_id = self.get_pointer_type_id(barycentrics_type_id, spirv::StorageClass::Function); let bool_type_id = self.get_bool_type_id(); let bool_pointer_type_id = self.get_pointer_type_id(bool_type_id, spirv::StorageClass::Function); let scalar_type_id = self.get_f32_type_id(); let float_pointer_type_id = self.get_f32_pointer_type_id(spirv::StorageClass::Function); let argument_type_id = self.get_ray_query_pointer_id(); let (func_id, mut function, arg_ids) = self.write_function_signature( &[argument_type_id, flag_pointer_type_id], intersection_type_id, ); let query_id = arg_ids[0]; let intersection_tracker_id = arg_ids[1]; let label_id = self.id_gen.next(); let mut block = Block::new(label_id); let blank_intersection = self.get_constant_null(intersection_type_id); let blank_intersection_id = self.id_gen.next(); // This must be before everything else in the function. block.body.push(Instruction::variable( intersection_pointer_type_id, blank_intersection_id, spirv::StorageClass::Function, Some(blank_intersection), )); let intersection_id = self.get_constant_scalar(crate::Literal::U32(if is_committed { spirv::RayQueryIntersection::RayQueryCommittedIntersectionKHR } else { spirv::RayQueryIntersection::RayQueryCandidateIntersectionKHR } as _)); let loaded_ray_query_tracker_id = self.id_gen.next(); block.body.push(Instruction::load( flag_type_id, loaded_ray_query_tracker_id, intersection_tracker_id, None, )); let proceeded_id = write_ray_flags_contains_flags( self, &mut block, loaded_ray_query_tracker_id, RayQueryPoint::PROCEED.bits(), ); let finished_proceed_id = write_ray_flags_contains_flags( self, &mut block, loaded_ray_query_tracker_id, RayQueryPoint::FINISHED_TRAVERSAL.bits(), ); let proceed_finished_correct_id = if is_committed { finished_proceed_id } else { let not_finished_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::LogicalNot, bool_type_id, not_finished_id, finished_proceed_id, )); not_finished_id }; let is_valid_id = self.write_logical_and(&mut block, proceed_finished_correct_id, proceeded_id); let valid_id = self.id_gen.next(); let mut valid_block = Block::new(valid_id); let final_label_id = self.id_gen.next(); let mut final_block = Block::new(final_label_id); block.body.push(Instruction::selection_merge( final_label_id, spirv::SelectionControl::NONE, )); function.consume( block, Instruction::branch_conditional(is_valid_id, valid_id, final_label_id), ); let raw_kind_id = self.id_gen.next(); valid_block .body .push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionTypeKHR, flag_type_id, raw_kind_id, query_id, intersection_id, )); let kind_id = if is_committed { // Nothing to do: the IR value matches `spirv::RayQueryCommittedIntersectionType` raw_kind_id } else { // Remap from the candidate kind to IR let condition_id = self.id_gen.next(); let committed_triangle_kind_id = self.get_constant_scalar(crate::Literal::U32( spirv::RayQueryCandidateIntersectionType::RayQueryCandidateIntersectionTriangleKHR as _, )); valid_block.body.push(Instruction::binary( spirv::Op::IEqual, self.get_bool_type_id(), condition_id, raw_kind_id, committed_triangle_kind_id, )); let kind_id = self.id_gen.next(); valid_block.body.push(Instruction::select( flag_type_id, kind_id, condition_id, self.get_constant_scalar(crate::Literal::U32( crate::RayQueryIntersection::Triangle as _, )), self.get_constant_scalar(crate::Literal::U32( crate::RayQueryIntersection::Aabb as _, )), )); kind_id }; let idx_id = self.get_index_constant(0); let access_idx = self.id_gen.next(); valid_block.body.push(Instruction::access_chain( flag_pointer_type_id, access_idx, blank_intersection_id, &[idx_id], )); valid_block .body .push(Instruction::store(access_idx, kind_id, None)); let not_none_comp_id = self.id_gen.next(); let none_id = self.get_constant_scalar(crate::Literal::U32(crate::RayQueryIntersection::None as _)); valid_block.body.push(Instruction::binary( spirv::Op::INotEqual, self.get_bool_type_id(), not_none_comp_id, kind_id, none_id, )); let not_none_label_id = self.id_gen.next(); let mut not_none_block = Block::new(not_none_label_id); let outer_merge_label_id = self.id_gen.next(); let outer_merge_block = Block::new(outer_merge_label_id); valid_block.body.push(Instruction::selection_merge( outer_merge_label_id, spirv::SelectionControl::NONE, )); function.consume( valid_block, Instruction::branch_conditional( not_none_comp_id, not_none_label_id, outer_merge_label_id, ), ); let instance_custom_index_id = self.id_gen.next(); not_none_block .body .push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionInstanceCustomIndexKHR, flag_type_id, instance_custom_index_id, query_id, intersection_id, )); let instance_id = self.id_gen.next(); not_none_block .body .push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionInstanceIdKHR, flag_type_id, instance_id, query_id, intersection_id, )); let sbt_record_offset_id = self.id_gen.next(); not_none_block .body .push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR, flag_type_id, sbt_record_offset_id, query_id, intersection_id, )); let geometry_index_id = self.id_gen.next(); not_none_block .body .push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionGeometryIndexKHR, flag_type_id, geometry_index_id, query_id, intersection_id, )); let primitive_index_id = self.id_gen.next(); not_none_block .body .push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionPrimitiveIndexKHR, flag_type_id, primitive_index_id, query_id, intersection_id, )); //Note: there is also `OpRayQueryGetIntersectionCandidateAABBOpaqueKHR`, // but it's not a property of an intersection. let object_to_world_id = self.id_gen.next(); not_none_block .body .push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionObjectToWorldKHR, transform_type_id, object_to_world_id, query_id, intersection_id, )); let world_to_object_id = self.id_gen.next(); not_none_block .body .push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionWorldToObjectKHR, transform_type_id, world_to_object_id, query_id, intersection_id, )); // instance custom index let idx_id = self.get_index_constant(2); let access_idx = self.id_gen.next(); not_none_block.body.push(Instruction::access_chain( flag_pointer_type_id, access_idx, blank_intersection_id, &[idx_id], )); not_none_block.body.push(Instruction::store( access_idx, instance_custom_index_id, None, )); // instance let idx_id = self.get_index_constant(3); let access_idx = self.id_gen.next(); not_none_block.body.push(Instruction::access_chain( flag_pointer_type_id, access_idx, blank_intersection_id, &[idx_id], )); not_none_block .body .push(Instruction::store(access_idx, instance_id, None)); let idx_id = self.get_index_constant(4); let access_idx = self.id_gen.next(); not_none_block.body.push(Instruction::access_chain( flag_pointer_type_id, access_idx, blank_intersection_id, &[idx_id], )); not_none_block .body .push(Instruction::store(access_idx, sbt_record_offset_id, None)); let idx_id = self.get_index_constant(5); let access_idx = self.id_gen.next(); not_none_block.body.push(Instruction::access_chain( flag_pointer_type_id, access_idx, blank_intersection_id, &[idx_id], )); not_none_block .body .push(Instruction::store(access_idx, geometry_index_id, None)); let idx_id = self.get_index_constant(6); let access_idx = self.id_gen.next(); not_none_block.body.push(Instruction::access_chain( flag_pointer_type_id, access_idx, blank_intersection_id, &[idx_id], )); not_none_block .body .push(Instruction::store(access_idx, primitive_index_id, None)); let idx_id = self.get_index_constant(9); let access_idx = self.id_gen.next(); not_none_block.body.push(Instruction::access_chain( transform_pointer_type_id, access_idx, blank_intersection_id, &[idx_id], )); not_none_block .body .push(Instruction::store(access_idx, object_to_world_id, None)); let idx_id = self.get_index_constant(10); let access_idx = self.id_gen.next(); not_none_block.body.push(Instruction::access_chain( transform_pointer_type_id, access_idx, blank_intersection_id, &[idx_id], )); not_none_block .body .push(Instruction::store(access_idx, world_to_object_id, None)); let tri_comp_id = self.id_gen.next(); let tri_id = self.get_constant_scalar(crate::Literal::U32( crate::RayQueryIntersection::Triangle as _, )); not_none_block.body.push(Instruction::binary( spirv::Op::IEqual, self.get_bool_type_id(), tri_comp_id, kind_id, tri_id, )); let tri_label_id = self.id_gen.next(); let mut tri_block = Block::new(tri_label_id); let merge_label_id = self.id_gen.next(); let merge_block = Block::new(merge_label_id); // t { let block = if is_committed { &mut not_none_block } else { &mut tri_block }; let t_id = self.id_gen.next(); block.body.push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionTKHR, scalar_type_id, t_id, query_id, intersection_id, )); let idx_id = self.get_index_constant(1); let access_idx = self.id_gen.next(); block.body.push(Instruction::access_chain( float_pointer_type_id, access_idx, blank_intersection_id, &[idx_id], )); block.body.push(Instruction::store(access_idx, t_id, None)); } not_none_block.body.push(Instruction::selection_merge( merge_label_id, spirv::SelectionControl::NONE, )); function.consume( not_none_block, Instruction::branch_conditional(not_none_comp_id, tri_label_id, merge_label_id), ); let barycentrics_id = self.id_gen.next(); tri_block.body.push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionBarycentricsKHR, barycentrics_type_id, barycentrics_id, query_id, intersection_id, )); let front_face_id = self.id_gen.next(); tri_block.body.push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionFrontFaceKHR, bool_type_id, front_face_id, query_id, intersection_id, )); let idx_id = self.get_index_constant(7); let access_idx = self.id_gen.next(); tri_block.body.push(Instruction::access_chain( barycentrics_pointer_type_id, access_idx, blank_intersection_id, &[idx_id], )); tri_block .body .push(Instruction::store(access_idx, barycentrics_id, None)); let idx_id = self.get_index_constant(8); let access_idx = self.id_gen.next(); tri_block.body.push(Instruction::access_chain( bool_pointer_type_id, access_idx, blank_intersection_id, &[idx_id], )); tri_block .body .push(Instruction::store(access_idx, front_face_id, None)); function.consume(tri_block, Instruction::branch(merge_label_id)); function.consume(merge_block, Instruction::branch(outer_merge_label_id)); function.consume(outer_merge_block, Instruction::branch(final_label_id)); let loaded_blank_intersection_id = self.id_gen.next(); final_block.body.push(Instruction::load( intersection_type_id, loaded_blank_intersection_id, blank_intersection_id, None, )); function.consume( final_block, Instruction::return_value(loaded_blank_intersection_id), ); function.to_words(&mut self.logical_layout.function_definitions); self.ray_query_functions.insert( LookupRayQueryFunction::GetIntersection { committed: is_committed, }, func_id, ); func_id } fn write_ray_query_initialize(&mut self, ir_module: &crate::Module) -> spirv::Word { if let Some(&word) = self .ray_query_functions .get(&LookupRayQueryFunction::Initialize) { return word; } let ray_query_type_id = self.get_ray_query_pointer_id(); let acceleration_structure_type_id = self.get_localtype_id(LocalType::AccelerationStructure); let ray_desc_type_id = self.get_handle_type_id( ir_module .special_types .ray_desc .expect("ray desc should be set if ray queries are being initialized"), ); let u32_ty = self.get_u32_type_id(); let u32_ptr_ty = self.get_pointer_type_id(u32_ty, spirv::StorageClass::Function); let f32_type_id = self.get_f32_type_id(); let f32_ptr_ty = self.get_pointer_type_id(f32_type_id, spirv::StorageClass::Function); let bool_type_id = self.get_bool_type_id(); let bool_vec3_type_id = self.get_vec3_bool_type_id(); let (func_id, mut function, arg_ids) = self.write_function_signature( &[ ray_query_type_id, acceleration_structure_type_id, ray_desc_type_id, u32_ptr_ty, f32_ptr_ty, ], self.void_type, ); let query_id = arg_ids[0]; let acceleration_structure_id = arg_ids[1]; let desc_id = arg_ids[2]; let init_tracker_id = arg_ids[3]; let t_max_tracker_id = arg_ids[4]; let label_id = self.id_gen.next(); let mut block = Block::new(label_id); let flag_type_id = self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::U32)); //Note: composite extract indices and types must match `generate_ray_desc_type` let ray_flags_id = self.id_gen.next(); block.body.push(Instruction::composite_extract( flag_type_id, ray_flags_id, desc_id, &[0], )); let cull_mask_id = self.id_gen.next(); block.body.push(Instruction::composite_extract( flag_type_id, cull_mask_id, desc_id, &[1], )); let tmin_id = self.id_gen.next(); block.body.push(Instruction::composite_extract( f32_type_id, tmin_id, desc_id, &[2], )); let tmax_id = self.id_gen.next(); block.body.push(Instruction::composite_extract( f32_type_id, tmax_id, desc_id, &[3], )); block .body .push(Instruction::store(t_max_tracker_id, tmax_id, None)); let vector_type_id = self.get_numeric_type_id(NumericType::Vector { size: crate::VectorSize::Tri, scalar: crate::Scalar::F32, }); let ray_origin_id = self.id_gen.next(); block.body.push(Instruction::composite_extract( vector_type_id, ray_origin_id, desc_id, &[4], )); let ray_dir_id = self.id_gen.next(); block.body.push(Instruction::composite_extract( vector_type_id, ray_dir_id, desc_id, &[5], )); let valid_id = self.ray_query_initialization_tracking.then(||{ let tmin_le_tmax_id = self.id_gen.next(); // Check both that tmin is less than or equal to tmax (https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#VUID-RuntimeSpirv-OpRayQueryInitializeKHR-06350) // and implicitly that neither tmin or tmax are NaN (https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#VUID-RuntimeSpirv-OpRayQueryInitializeKHR-06351) // because this checks if tmin and tmax are ordered too (i.e: not NaN). block.body.push(Instruction::binary( spirv::Op::FOrdLessThanEqual, bool_type_id, tmin_le_tmax_id, tmin_id, tmax_id, )); // Check that tmin is greater than or equal to 0 (and // therefore also tmax is too because it is greater than // or equal to tmin) (https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#VUID-RuntimeSpirv-OpRayQueryInitializeKHR-06349). let tmin_ge_zero_id = self.id_gen.next(); let zero_id = self.get_constant_scalar(crate::Literal::F32(0.0)); block.body.push(Instruction::binary( spirv::Op::FOrdGreaterThanEqual, bool_type_id, tmin_ge_zero_id, tmin_id, zero_id, )); // Check that ray origin is finite (https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#VUID-RuntimeSpirv-OpRayQueryInitializeKHR-06348) let ray_origin_infinite_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::IsInf, bool_vec3_type_id, ray_origin_infinite_id, ray_origin_id, )); let any_ray_origin_infinite_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::Any, bool_type_id, any_ray_origin_infinite_id, ray_origin_infinite_id, )); let ray_origin_nan_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::IsNan, bool_vec3_type_id, ray_origin_nan_id, ray_origin_id, )); let any_ray_origin_nan_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::Any, bool_type_id, any_ray_origin_nan_id, ray_origin_nan_id, )); let ray_origin_not_finite_id = self.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::LogicalOr, bool_type_id, ray_origin_not_finite_id, any_ray_origin_nan_id, any_ray_origin_infinite_id, )); let all_ray_origin_finite_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::LogicalNot, bool_type_id, all_ray_origin_finite_id, ray_origin_not_finite_id, )); // Check that ray direction is finite (https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#VUID-RuntimeSpirv-OpRayQueryInitializeKHR-06348) let ray_dir_infinite_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::IsInf, bool_vec3_type_id, ray_dir_infinite_id, ray_dir_id, )); let any_ray_dir_infinite_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::Any, bool_type_id, any_ray_dir_infinite_id, ray_dir_infinite_id, )); let ray_dir_nan_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::IsNan, bool_vec3_type_id, ray_dir_nan_id, ray_dir_id, )); let any_ray_dir_nan_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::Any, bool_type_id, any_ray_dir_nan_id, ray_dir_nan_id, )); let ray_dir_not_finite_id = self.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::LogicalOr, bool_type_id, ray_dir_not_finite_id, any_ray_dir_nan_id, any_ray_dir_infinite_id, )); let all_ray_dir_finite_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::LogicalNot, bool_type_id, all_ray_dir_finite_id, ray_dir_not_finite_id, )); /// Writes spirv to check that less than two booleans are true /// /// For each boolean: removes it, `and`s it with all others (i.e for all possible combinations of two booleans in the list checks to see if both are true). /// Then `or`s all of these checks together. This produces whether two or more booleans are true. fn write_less_than_2_true( writer: &mut Writer, block: &mut Block, mut bools: Vec, ) -> spirv::Word { assert!(bools.len() > 1, "Must have multiple booleans!"); let bool_ty = writer.get_bool_type_id(); let mut each_two_true = Vec::new(); while let Some(last_bool) = bools.pop() { for &bool in &bools { let both_true_id = writer.write_logical_and( block, last_bool, bool, ); each_two_true.push(both_true_id); } } let mut all_or_id = each_two_true.pop().expect("since this must have multiple booleans, there must be at least one thing in `each_two_true`"); for two_true in each_two_true { let new_all_or_id = writer.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::LogicalOr, bool_ty, new_all_or_id, all_or_id, two_true, )); all_or_id = new_all_or_id; } let less_than_two_id = writer.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::LogicalNot, bool_ty, less_than_two_id, all_or_id, )); less_than_two_id } // Check that at most one of skip triangles and skip AABBs is // present (https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#VUID-RuntimeSpirv-OpRayQueryInitializeKHR-06889) let contains_skip_triangles = write_ray_flags_contains_flags( self, &mut block, ray_flags_id, crate::RayFlag::SKIP_TRIANGLES.bits(), ); let contains_skip_aabbs = write_ray_flags_contains_flags( self, &mut block, ray_flags_id, crate::RayFlag::SKIP_AABBS.bits(), ); let not_contain_skip_triangles_aabbs = write_less_than_2_true( self, &mut block, vec![contains_skip_triangles, contains_skip_aabbs], ); // Check that at most one of skip triangles (taken from above check), // cull back facing, and cull front face is present (https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#VUID-RuntimeSpirv-OpRayQueryInitializeKHR-06890) let contains_cull_back = write_ray_flags_contains_flags( self, &mut block, ray_flags_id, crate::RayFlag::CULL_BACK_FACING.bits(), ); let contains_cull_front = write_ray_flags_contains_flags( self, &mut block, ray_flags_id, crate::RayFlag::CULL_FRONT_FACING.bits(), ); let not_contain_skip_triangles_cull = write_less_than_2_true( self, &mut block, vec![ contains_skip_triangles, contains_cull_back, contains_cull_front, ], ); // Check that at most one of force opaque, force not opaque, cull opaque, // and cull not opaque are present (https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#VUID-RuntimeSpirv-OpRayQueryInitializeKHR-06891) let contains_opaque = write_ray_flags_contains_flags( self, &mut block, ray_flags_id, crate::RayFlag::FORCE_OPAQUE.bits(), ); let contains_no_opaque = write_ray_flags_contains_flags( self, &mut block, ray_flags_id, crate::RayFlag::FORCE_NO_OPAQUE.bits(), ); let contains_cull_opaque = write_ray_flags_contains_flags( self, &mut block, ray_flags_id, crate::RayFlag::CULL_OPAQUE.bits(), ); let contains_cull_no_opaque = write_ray_flags_contains_flags( self, &mut block, ray_flags_id, crate::RayFlag::CULL_NO_OPAQUE.bits(), ); let not_contain_multiple_opaque = write_less_than_2_true( self, &mut block, vec![ contains_opaque, contains_no_opaque, contains_cull_opaque, contains_cull_no_opaque, ], ); // Combine all checks into a single flag saying whether the call is valid or not. self.write_reduce_and( &mut block, vec![ tmin_le_tmax_id, tmin_ge_zero_id, all_ray_origin_finite_id, all_ray_dir_finite_id, not_contain_skip_triangles_aabbs, not_contain_skip_triangles_cull, not_contain_multiple_opaque, ], ) }); let merge_label_id = self.id_gen.next(); let merge_block = Block::new(merge_label_id); // NOTE: this block will be unreachable if initialization tracking is disabled. let invalid_label_id = self.id_gen.next(); let mut invalid_block = Block::new(invalid_label_id); let valid_label_id = self.id_gen.next(); let mut valid_block = Block::new(valid_label_id); match valid_id { Some(all_valid_id) => { block.body.push(Instruction::selection_merge( merge_label_id, spirv::SelectionControl::NONE, )); function.consume( block, Instruction::branch_conditional(all_valid_id, valid_label_id, invalid_label_id), ); } None => { function.consume(block, Instruction::branch(valid_label_id)); } } valid_block.body.push(Instruction::ray_query_initialize( query_id, acceleration_structure_id, ray_flags_id, cull_mask_id, ray_origin_id, tmin_id, ray_dir_id, tmax_id, )); let const_initialized = self.get_constant_scalar(crate::Literal::U32(RayQueryPoint::INITIALIZED.bits())); valid_block .body .push(Instruction::store(init_tracker_id, const_initialized, None)); function.consume(valid_block, Instruction::branch(merge_label_id)); if self .flags .contains(WriterFlags::PRINT_ON_RAY_QUERY_INITIALIZATION_FAIL) { self.write_debug_printf( &mut invalid_block, "Naga ignored invalid arguments to rayQueryInitialize with flags: %u t_min: %f t_max: %f origin: %v4f dir: %v4f", &[ ray_flags_id, tmin_id, tmax_id, ray_origin_id, ray_dir_id, ], ); } function.consume(invalid_block, Instruction::branch(merge_label_id)); function.consume(merge_block, Instruction::return_void()); function.to_words(&mut self.logical_layout.function_definitions); self.ray_query_functions .insert(LookupRayQueryFunction::Initialize, func_id); func_id } fn write_ray_query_proceed(&mut self) -> spirv::Word { if let Some(&word) = self .ray_query_functions .get(&LookupRayQueryFunction::Proceed) { return word; } let ray_query_type_id = self.get_ray_query_pointer_id(); let u32_ty = self.get_u32_type_id(); let u32_ptr_ty = self.get_pointer_type_id(u32_ty, spirv::StorageClass::Function); let bool_type_id = self.get_bool_type_id(); let bool_ptr_ty = self.get_pointer_type_id(bool_type_id, spirv::StorageClass::Function); let (func_id, mut function, arg_ids) = self.write_function_signature(&[ray_query_type_id, u32_ptr_ty], bool_type_id); let query_id = arg_ids[0]; let init_tracker_id = arg_ids[1]; let block_id = self.id_gen.next(); let mut block = Block::new(block_id); // TODO: perhaps this could be replaced with an OpPhi? let proceeded_id = self.id_gen.next(); let const_false = self.get_constant_scalar(crate::Literal::Bool(false)); block.body.push(Instruction::variable( bool_ptr_ty, proceeded_id, spirv::StorageClass::Function, Some(const_false), )); let initialized_tracker_id = self.id_gen.next(); block.body.push(Instruction::load( u32_ty, initialized_tracker_id, init_tracker_id, None, )); let merge_id = self.id_gen.next(); let mut merge_block = Block::new(merge_id); let valid_block_id = self.id_gen.next(); let mut valid_block = Block::new(valid_block_id); let instruction = if self.ray_query_initialization_tracking { let is_initialized = write_ray_flags_contains_flags( self, &mut block, initialized_tracker_id, RayQueryPoint::INITIALIZED.bits(), ); block.body.push(Instruction::selection_merge( merge_id, spirv::SelectionControl::NONE, )); Instruction::branch_conditional(is_initialized, valid_block_id, merge_id) } else { Instruction::branch(valid_block_id) }; function.consume(block, instruction); let has_proceeded = self.id_gen.next(); valid_block.body.push(Instruction::ray_query_proceed( bool_type_id, has_proceeded, query_id, )); valid_block .body .push(Instruction::store(proceeded_id, has_proceeded, None)); let add_flag_finished = self.get_constant_scalar(crate::Literal::U32( (RayQueryPoint::PROCEED | RayQueryPoint::FINISHED_TRAVERSAL).bits(), )); let add_flag_continuing = self.get_constant_scalar(crate::Literal::U32(RayQueryPoint::PROCEED.bits())); let add_flags_id = self.id_gen.next(); valid_block.body.push(Instruction::select( u32_ty, add_flags_id, has_proceeded, add_flag_continuing, add_flag_finished, )); let final_flags = self.id_gen.next(); valid_block.body.push(Instruction::binary( spirv::Op::BitwiseOr, u32_ty, final_flags, initialized_tracker_id, add_flags_id, )); valid_block .body .push(Instruction::store(init_tracker_id, final_flags, None)); function.consume(valid_block, Instruction::branch(merge_id)); let loaded_proceeded_id = self.id_gen.next(); merge_block.body.push(Instruction::load( bool_type_id, loaded_proceeded_id, proceeded_id, None, )); function.consume(merge_block, Instruction::return_value(loaded_proceeded_id)); function.to_words(&mut self.logical_layout.function_definitions); self.ray_query_functions .insert(LookupRayQueryFunction::Proceed, func_id); func_id } fn write_ray_query_generate_intersection(&mut self) -> spirv::Word { if let Some(&word) = self .ray_query_functions .get(&LookupRayQueryFunction::GenerateIntersection) { return word; } let ray_query_type_id = self.get_ray_query_pointer_id(); let u32_ty = self.get_u32_type_id(); let u32_ptr_ty = self.get_pointer_type_id(u32_ty, spirv::StorageClass::Function); let f32_type_id = self.get_f32_type_id(); let f32_ptr_type_id = self.get_pointer_type_id(f32_type_id, spirv::StorageClass::Function); let bool_type_id = self.get_bool_type_id(); let (func_id, mut function, arg_ids) = self.write_function_signature( &[ray_query_type_id, u32_ptr_ty, f32_type_id, f32_ptr_type_id], self.void_type, ); let query_id = arg_ids[0]; let init_tracker_id = arg_ids[1]; let depth_id = arg_ids[2]; let t_max_tracker_id = arg_ids[3]; let block_id = self.id_gen.next(); let mut block = Block::new(block_id); let current_t = self.id_gen.next(); block.body.push(Instruction::variable( f32_ptr_type_id, current_t, spirv::StorageClass::Function, None, )); let current_t = self.id_gen.next(); block.body.push(Instruction::variable( f32_ptr_type_id, current_t, spirv::StorageClass::Function, None, )); let valid_id = self.id_gen.next(); let mut valid_block = Block::new(valid_id); let final_label_id = self.id_gen.next(); let final_block = Block::new(final_label_id); let instruction = if self.ray_query_initialization_tracking { let initialized_tracker_id = self.id_gen.next(); block.body.push(Instruction::load( u32_ty, initialized_tracker_id, init_tracker_id, None, )); let proceeded_id = write_ray_flags_contains_flags( self, &mut block, initialized_tracker_id, RayQueryPoint::PROCEED.bits(), ); let finished_proceed_id = write_ray_flags_contains_flags( self, &mut block, initialized_tracker_id, RayQueryPoint::FINISHED_TRAVERSAL.bits(), ); // Can't find anything to suggest double calling this function is invalid. let not_finished_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::LogicalNot, bool_type_id, not_finished_id, finished_proceed_id, )); let is_valid_id = self.write_logical_and(&mut block, not_finished_id, proceeded_id); block.body.push(Instruction::selection_merge( final_label_id, spirv::SelectionControl::NONE, )); Instruction::branch_conditional(is_valid_id, valid_id, final_label_id) } else { Instruction::branch(valid_id) }; function.consume(block, instruction); let intersection_id = self.get_constant_scalar(crate::Literal::U32( spirv::RayQueryIntersection::RayQueryCandidateIntersectionKHR as _, )); let committed_intersection_id = self.get_constant_scalar(crate::Literal::U32( spirv::RayQueryIntersection::RayQueryCommittedIntersectionKHR as _, )); let raw_kind_id = self.id_gen.next(); valid_block .body .push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionTypeKHR, u32_ty, raw_kind_id, query_id, intersection_id, )); let candidate_aabb_id = self.get_constant_scalar(crate::Literal::U32( spirv::RayQueryCandidateIntersectionType::RayQueryCandidateIntersectionAABBKHR as _, )); let intersection_aabb_id = self.id_gen.next(); valid_block.body.push(Instruction::binary( spirv::Op::IEqual, bool_type_id, intersection_aabb_id, raw_kind_id, candidate_aabb_id, )); // Check that the provided t value is between t min and the current committed // t value, (https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#VUID-RuntimeSpirv-OpRayQueryGenerateIntersectionKHR-06353) // Get the tmin let t_min_id = self.id_gen.next(); valid_block.body.push(Instruction::ray_query_get_t_min( f32_type_id, t_min_id, query_id, )); // Get the current committed t, or tmax if no hit. // Basically emulate HLSL's (easier) version // Pseudo-code: // ````wgsl // // start of function // var current_t:f32; // ... // let committed_type_id = RayQueryGetIntersectionTypeKHR(query_id); // if committed_type_id == Committed_None { // current_t = load(t_max_tracker); // } else { // current_t = RayQueryGetIntersectionTKHR(query_id); // } // ... // ```` let committed_type_id = self.id_gen.next(); valid_block .body .push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionTypeKHR, u32_ty, committed_type_id, query_id, committed_intersection_id, )); let no_committed = self.id_gen.next(); valid_block.body.push(Instruction::binary( spirv::Op::IEqual, bool_type_id, no_committed, committed_type_id, self.get_constant_scalar(crate::Literal::U32( spirv::RayQueryCommittedIntersectionType::RayQueryCommittedIntersectionNoneKHR as _, )), )); let next_valid_block_id = self.id_gen.next(); let no_committed_block_id = self.id_gen.next(); let mut no_committed_block = Block::new(no_committed_block_id); let committed_block_id = self.id_gen.next(); let mut committed_block = Block::new(committed_block_id); valid_block.body.push(Instruction::selection_merge( next_valid_block_id, spirv::SelectionControl::NONE, )); function.consume( valid_block, Instruction::branch_conditional( no_committed, no_committed_block_id, committed_block_id, ), ); // Assign t_max to current_t let t_max_id = self.id_gen.next(); no_committed_block.body.push(Instruction::load( f32_type_id, t_max_id, t_max_tracker_id, None, )); no_committed_block .body .push(Instruction::store(current_t, t_max_id, None)); function.consume(no_committed_block, Instruction::branch(next_valid_block_id)); // Assign t_current to current_t let latest_t_id = self.id_gen.next(); committed_block .body .push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionTKHR, f32_type_id, latest_t_id, query_id, intersection_id, )); committed_block .body .push(Instruction::store(current_t, latest_t_id, None)); function.consume(committed_block, Instruction::branch(next_valid_block_id)); let mut valid_block = Block::new(next_valid_block_id); let t_ge_t_min = self.id_gen.next(); valid_block.body.push(Instruction::binary( spirv::Op::FOrdGreaterThanEqual, bool_type_id, t_ge_t_min, depth_id, t_min_id, )); let t_current = self.id_gen.next(); valid_block .body .push(Instruction::load(f32_type_id, t_current, current_t, None)); let t_le_t_current = self.id_gen.next(); valid_block.body.push(Instruction::binary( spirv::Op::FOrdLessThanEqual, bool_type_id, t_le_t_current, depth_id, t_current, )); let t_in_range = self.id_gen.next(); valid_block.body.push(Instruction::binary( spirv::Op::LogicalAnd, bool_type_id, t_in_range, t_ge_t_min, t_le_t_current, )); let call_valid_id = self.id_gen.next(); valid_block.body.push(Instruction::binary( spirv::Op::LogicalAnd, bool_type_id, call_valid_id, t_in_range, intersection_aabb_id, )); let generate_label_id = self.id_gen.next(); let mut generate_block = Block::new(generate_label_id); let merge_label_id = self.id_gen.next(); let merge_block = Block::new(merge_label_id); valid_block.body.push(Instruction::selection_merge( merge_label_id, spirv::SelectionControl::NONE, )); function.consume( valid_block, Instruction::branch_conditional(call_valid_id, generate_label_id, merge_label_id), ); generate_block .body .push(Instruction::ray_query_generate_intersection( query_id, depth_id, )); function.consume(generate_block, Instruction::branch(merge_label_id)); function.consume(merge_block, Instruction::branch(final_label_id)); function.consume(final_block, Instruction::return_void()); function.to_words(&mut self.logical_layout.function_definitions); self.ray_query_functions .insert(LookupRayQueryFunction::GenerateIntersection, func_id); func_id } fn write_ray_query_confirm_intersection(&mut self) -> spirv::Word { if let Some(&word) = self .ray_query_functions .get(&LookupRayQueryFunction::ConfirmIntersection) { return word; } let ray_query_type_id = self.get_ray_query_pointer_id(); let u32_ty = self.get_u32_type_id(); let u32_ptr_ty = self.get_pointer_type_id(u32_ty, spirv::StorageClass::Function); let bool_type_id = self.get_bool_type_id(); let (func_id, mut function, arg_ids) = self.write_function_signature(&[ray_query_type_id, u32_ptr_ty], self.void_type); let query_id = arg_ids[0]; let init_tracker_id = arg_ids[1]; let block_id = self.id_gen.next(); let mut block = Block::new(block_id); let valid_id = self.id_gen.next(); let mut valid_block = Block::new(valid_id); let final_label_id = self.id_gen.next(); let final_block = Block::new(final_label_id); let instruction = if self.ray_query_initialization_tracking { let initialized_tracker_id = self.id_gen.next(); block.body.push(Instruction::load( u32_ty, initialized_tracker_id, init_tracker_id, None, )); let proceeded_id = write_ray_flags_contains_flags( self, &mut block, initialized_tracker_id, RayQueryPoint::PROCEED.bits(), ); let finished_proceed_id = write_ray_flags_contains_flags( self, &mut block, initialized_tracker_id, RayQueryPoint::FINISHED_TRAVERSAL.bits(), ); // Although it seems strange to call this twice, I (Vecvec) can't find anything to suggest double calling this function is invalid. let not_finished_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::LogicalNot, bool_type_id, not_finished_id, finished_proceed_id, )); let is_valid_id = self.write_logical_and(&mut block, not_finished_id, proceeded_id); block.body.push(Instruction::selection_merge( final_label_id, spirv::SelectionControl::NONE, )); Instruction::branch_conditional(is_valid_id, valid_id, final_label_id) } else { Instruction::branch(valid_id) }; function.consume(block, instruction); let intersection_id = self.get_constant_scalar(crate::Literal::U32( spirv::RayQueryIntersection::RayQueryCandidateIntersectionKHR as _, )); let raw_kind_id = self.id_gen.next(); valid_block .body .push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionTypeKHR, u32_ty, raw_kind_id, query_id, intersection_id, )); let candidate_tri_id = self.get_constant_scalar(crate::Literal::U32( spirv::RayQueryCandidateIntersectionType::RayQueryCandidateIntersectionTriangleKHR as _, )); let intersection_tri_id = self.id_gen.next(); valid_block.body.push(Instruction::binary( spirv::Op::IEqual, bool_type_id, intersection_tri_id, raw_kind_id, candidate_tri_id, )); let generate_label_id = self.id_gen.next(); let mut generate_block = Block::new(generate_label_id); let merge_label_id = self.id_gen.next(); let merge_block = Block::new(merge_label_id); valid_block.body.push(Instruction::selection_merge( merge_label_id, spirv::SelectionControl::NONE, )); function.consume( valid_block, Instruction::branch_conditional(intersection_tri_id, generate_label_id, merge_label_id), ); generate_block .body .push(Instruction::ray_query_confirm_intersection(query_id)); function.consume(generate_block, Instruction::branch(merge_label_id)); function.consume(merge_block, Instruction::branch(final_label_id)); function.consume(final_block, Instruction::return_void()); self.ray_query_functions .insert(LookupRayQueryFunction::ConfirmIntersection, func_id); function.to_words(&mut self.logical_layout.function_definitions); func_id } fn write_ray_query_get_vertex_positions( &mut self, is_committed: bool, ir_module: &crate::Module, ) -> spirv::Word { if let Some(&word) = self.ray_query_functions .get(&LookupRayQueryFunction::GetVertexPositions { committed: is_committed, }) { return word; } let (committed_ty, committed_tri_ty) = if is_committed { ( spirv::RayQueryIntersection::RayQueryCommittedIntersectionKHR as u32, spirv::RayQueryCommittedIntersectionType::RayQueryCommittedIntersectionTriangleKHR as u32, ) } else { ( spirv::RayQueryIntersection::RayQueryCandidateIntersectionKHR as u32, spirv::RayQueryCandidateIntersectionType::RayQueryCandidateIntersectionTriangleKHR as u32, ) }; let ray_query_type_id = self.get_ray_query_pointer_id(); let u32_ty = self.get_u32_type_id(); let u32_ptr_ty = self.get_pointer_type_id(u32_ty, spirv::StorageClass::Function); let rq_get_vertex_positions_ty_id = self.get_handle_type_id( *ir_module .special_types .ray_vertex_return .as_ref() .expect("must be generated when reading in get vertex position"), ); let ptr_return_ty = self.get_pointer_type_id(rq_get_vertex_positions_ty_id, spirv::StorageClass::Function); let bool_type_id = self.get_bool_type_id(); let (func_id, mut function, arg_ids) = self.write_function_signature( &[ray_query_type_id, u32_ptr_ty], rq_get_vertex_positions_ty_id, ); let query_id = arg_ids[0]; let init_tracker_id = arg_ids[1]; let block_id = self.id_gen.next(); let mut block = Block::new(block_id); let return_id = self.id_gen.next(); block.body.push(Instruction::variable( ptr_return_ty, return_id, spirv::StorageClass::Function, Some(self.get_constant_null(rq_get_vertex_positions_ty_id)), )); let valid_id = self.id_gen.next(); let mut valid_block = Block::new(valid_id); let final_label_id = self.id_gen.next(); let mut final_block = Block::new(final_label_id); let instruction = if self.ray_query_initialization_tracking { let initialized_tracker_id = self.id_gen.next(); block.body.push(Instruction::load( u32_ty, initialized_tracker_id, init_tracker_id, None, )); let proceeded_id = write_ray_flags_contains_flags( self, &mut block, initialized_tracker_id, RayQueryPoint::PROCEED.bits(), ); let finished_proceed_id = write_ray_flags_contains_flags( self, &mut block, initialized_tracker_id, RayQueryPoint::FINISHED_TRAVERSAL.bits(), ); let correct_finish_id = if is_committed { finished_proceed_id } else { let not_finished_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::LogicalNot, bool_type_id, not_finished_id, finished_proceed_id, )); not_finished_id }; let is_valid_id = self.write_logical_and(&mut block, correct_finish_id, proceeded_id); block.body.push(Instruction::selection_merge( final_label_id, spirv::SelectionControl::NONE, )); Instruction::branch_conditional(is_valid_id, valid_id, final_label_id) } else { Instruction::branch(valid_id) }; function.consume(block, instruction); let intersection_id = self.get_constant_scalar(crate::Literal::U32(committed_ty)); let raw_kind_id = self.id_gen.next(); valid_block .body .push(Instruction::ray_query_get_intersection( spirv::Op::RayQueryGetIntersectionTypeKHR, u32_ty, raw_kind_id, query_id, intersection_id, )); let candidate_tri_id = self.get_constant_scalar(crate::Literal::U32(committed_tri_ty)); let intersection_tri_id = self.id_gen.next(); valid_block.body.push(Instruction::binary( spirv::Op::IEqual, bool_type_id, intersection_tri_id, raw_kind_id, candidate_tri_id, )); let generate_label_id = self.id_gen.next(); let mut vertex_return_block = Block::new(generate_label_id); let merge_label_id = self.id_gen.next(); let merge_block = Block::new(merge_label_id); valid_block.body.push(Instruction::selection_merge( merge_label_id, spirv::SelectionControl::NONE, )); function.consume( valid_block, Instruction::branch_conditional(intersection_tri_id, generate_label_id, merge_label_id), ); let vertices_id = self.id_gen.next(); vertex_return_block .body .push(Instruction::ray_query_return_vertex_position( rq_get_vertex_positions_ty_id, vertices_id, query_id, intersection_id, )); vertex_return_block .body .push(Instruction::store(return_id, vertices_id, None)); function.consume(vertex_return_block, Instruction::branch(merge_label_id)); function.consume(merge_block, Instruction::branch(final_label_id)); let loaded_pos_id = self.id_gen.next(); final_block.body.push(Instruction::load( rq_get_vertex_positions_ty_id, loaded_pos_id, return_id, None, )); function.consume(final_block, Instruction::return_value(loaded_pos_id)); self.ray_query_functions.insert( LookupRayQueryFunction::GetVertexPositions { committed: is_committed, }, func_id, ); function.to_words(&mut self.logical_layout.function_definitions); func_id } fn write_ray_query_terminate(&mut self) -> spirv::Word { if let Some(&word) = self .ray_query_functions .get(&LookupRayQueryFunction::Terminate) { return word; } let ray_query_type_id = self.get_ray_query_pointer_id(); let u32_ty = self.get_u32_type_id(); let u32_ptr_ty = self.get_pointer_type_id(u32_ty, spirv::StorageClass::Function); let bool_type_id = self.get_bool_type_id(); let (func_id, mut function, arg_ids) = self.write_function_signature(&[ray_query_type_id, u32_ptr_ty], self.void_type); let query_id = arg_ids[0]; let init_tracker_id = arg_ids[1]; let block_id = self.id_gen.next(); let mut block = Block::new(block_id); let initialized_tracker_id = self.id_gen.next(); block.body.push(Instruction::load( u32_ty, initialized_tracker_id, init_tracker_id, None, )); let merge_id = self.id_gen.next(); let merge_block = Block::new(merge_id); let valid_block_id = self.id_gen.next(); let mut valid_block = Block::new(valid_block_id); let instruction = if self.ray_query_initialization_tracking { let has_proceeded = write_ray_flags_contains_flags( self, &mut block, initialized_tracker_id, RayQueryPoint::PROCEED.bits(), ); let finished_proceed_id = write_ray_flags_contains_flags( self, &mut block, initialized_tracker_id, RayQueryPoint::FINISHED_TRAVERSAL.bits(), ); let not_finished_id = self.id_gen.next(); block.body.push(Instruction::unary( spirv::Op::LogicalNot, bool_type_id, not_finished_id, finished_proceed_id, )); let valid_call = self.write_logical_and(&mut block, not_finished_id, has_proceeded); block.body.push(Instruction::selection_merge( merge_id, spirv::SelectionControl::NONE, )); Instruction::branch_conditional(valid_call, valid_block_id, merge_id) } else { Instruction::branch(valid_block_id) }; function.consume(block, instruction); valid_block .body .push(Instruction::ray_query_terminate(query_id)); function.consume(valid_block, Instruction::branch(merge_id)); function.consume(merge_block, Instruction::return_void()); function.to_words(&mut self.logical_layout.function_definitions); self.ray_query_functions .insert(LookupRayQueryFunction::Proceed, func_id); func_id } } impl BlockContext<'_> { pub(in super::super) fn write_ray_query_function( &mut self, query: Handle, function: &crate::RayQueryFunction, block: &mut Block, ) { let query_id = self.cached[query]; let tracker_ids = *self .ray_query_tracker_expr .get(&query) .expect("not a cached ray query"); match *function { crate::RayQueryFunction::Initialize { acceleration_structure, descriptor, } => { let desc_id = self.cached[descriptor]; let acc_struct_id = self.get_handle_id(acceleration_structure); let func = self.writer.write_ray_query_initialize(self.ir_module); let func_id = self.gen_id(); block.body.push(Instruction::function_call( self.writer.void_type, func_id, func, &[ query_id, acc_struct_id, desc_id, tracker_ids.initialized_tracker, tracker_ids.t_max_tracker, ], )); } crate::RayQueryFunction::Proceed { result } => { let id = self.gen_id(); self.cached[result] = id; let bool_ty = self.writer.get_bool_type_id(); let func_id = self.writer.write_ray_query_proceed(); block.body.push(Instruction::function_call( bool_ty, id, func_id, &[query_id, tracker_ids.initialized_tracker], )); } crate::RayQueryFunction::GenerateIntersection { hit_t } => { let hit_id = self.cached[hit_t]; let func_id = self.writer.write_ray_query_generate_intersection(); let func_call_id = self.gen_id(); block.body.push(Instruction::function_call( self.writer.void_type, func_call_id, func_id, &[ query_id, tracker_ids.initialized_tracker, hit_id, tracker_ids.t_max_tracker, ], )); } crate::RayQueryFunction::ConfirmIntersection => { let func_id = self.writer.write_ray_query_confirm_intersection(); let func_call_id = self.gen_id(); block.body.push(Instruction::function_call( self.writer.void_type, func_call_id, func_id, &[query_id, tracker_ids.initialized_tracker], )); } crate::RayQueryFunction::Terminate => { let id = self.gen_id(); let func_id = self.writer.write_ray_query_terminate(); block.body.push(Instruction::function_call( self.writer.void_type, id, func_id, &[query_id, tracker_ids.initialized_tracker], )); } } } pub(in super::super) fn write_ray_query_return_vertex_position( &mut self, query: Handle, block: &mut Block, is_committed: bool, ) -> spirv::Word { let fn_id = self .writer .write_ray_query_get_vertex_positions(is_committed, self.ir_module); let query_id = self.cached[query]; let tracker_id = *self .ray_query_tracker_expr .get(&query) .expect("not a cached ray query"); let rq_get_vertex_positions_ty_id = self.get_handle_type_id( *self .ir_module .special_types .ray_vertex_return .as_ref() .expect("must be generated when reading in get vertex position"), ); let func_call_id = self.gen_id(); block.body.push(Instruction::function_call( rq_get_vertex_positions_ty_id, func_call_id, fn_id, &[query_id, tracker_id.initialized_tracker], )); func_call_id } } ================================================ FILE: naga/src/back/spv/reclaimable.rs ================================================ /*! Reusing collections' previous allocations. */ use alloc::vec::Vec; /// A value that can be reset to its initial state, retaining its current allocations. /// /// Naga attempts to lower the cost of SPIR-V generation by allowing clients to /// reuse the same `Writer` for multiple Module translations. Reusing a `Writer` /// means that the `Vec`s, `HashMap`s, and other heap-allocated structures the /// `Writer` uses internally begin the translation with heap-allocated buffers /// ready to use. /// /// But this approach introduces the risk of `Writer` state leaking from one /// module to the next. When a developer adds fields to `Writer` or its internal /// types, they must remember to reset their contents between modules. /// /// One trick to ensure that every field has been accounted for is to use Rust's /// struct literal syntax to construct a new, reset value. If a developer adds a /// field, but neglects to update the reset code, the compiler will complain /// that a field is missing from the literal. This trait's `recycle` method /// takes `self` by value, and returns `Self` by value, encouraging the use of /// struct literal expressions in its implementation. pub trait Reclaimable { /// Clear `self`, retaining its current memory allocations. /// /// Shrink the buffer if it's currently much larger than was actually used. /// This prevents a module with exceptionally large allocations from causing /// the `Writer` to retain more memory than it needs indefinitely. fn reclaim(self) -> Self; } // Stock values for various collections. impl Reclaimable for Vec { fn reclaim(mut self) -> Self { self.clear(); self } } impl Reclaimable for hashbrown::HashMap { fn reclaim(mut self) -> Self { self.clear(); self } } impl Reclaimable for hashbrown::HashSet { fn reclaim(mut self) -> Self { self.clear(); self } } impl Reclaimable for indexmap::IndexSet { fn reclaim(mut self) -> Self { self.clear(); self } } impl Reclaimable for alloc::collections::BTreeMap { fn reclaim(mut self) -> Self { self.clear(); self } } impl Reclaimable for crate::arena::HandleVec { fn reclaim(mut self) -> Self { self.clear(); self } } ================================================ FILE: naga/src/back/spv/selection.rs ================================================ /*! Generate SPIR-V conditional structures. Builders for `if` structures with `and`s. The types in this module track the information needed to emit SPIR-V code for complex conditional structures, like those whose conditions involve short-circuiting 'and' and 'or' structures. These track labels and can emit `OpPhi` instructions to merge values produced along different paths. This currently only supports exactly the forms Naga uses, so it doesn't support `or` or `else`, and only supports zero or one merged values. Naga needs to emit code roughly like this: ```ignore value = DEFAULT; if COND1 && COND2 { value = THEN_VALUE; } // use value ``` Assuming `ctx` and `block` are a mutable references to a [`BlockContext`] and the current [`Block`], and `merge_type` is the SPIR-V type for the merged value `value`, we can build SPIR-V for the code above like so: ```ignore let cond = Selection::start(block, merge_type); // ... compute `cond1` ... cond.if_true(ctx, cond1, DEFAULT); // ... compute `cond2` ... cond.if_true(ctx, cond2, DEFAULT); // ... compute THEN_VALUE let merged_value = cond.finish(ctx, THEN_VALUE); ``` After this, `merged_value` is either `DEFAULT` or `THEN_VALUE`, depending on the path by which the merged block was reached. This takes care of writing all branch instructions, including an `OpSelectionMerge` annotation in the header block; starting new blocks and assigning them labels; and emitting the `OpPhi` that gathers together the right sources for the merged values, for every path through the selection construct. When there is no merged value to produce, you can pass `()` for `merge_type` and the merge values. In this case no `OpPhi` instructions are produced, and the `finish` method returns `()`. To enforce proper nesting, a `Selection` takes ownership of the `&mut Block` pointer for the duration of its lifetime. To obtain the block for generating code in the selection's body, call the `Selection::block` method. */ use alloc::{vec, vec::Vec}; use spirv::Word; use super::{Block, BlockContext, Instruction}; /// A private struct recording what we know about the selection construct so far. pub(super) struct Selection<'b, M: MergeTuple> { /// The block pointer we're emitting code into. block: &'b mut Block, /// The label of the selection construct's merge block, or `None` if we /// haven't yet written the `OpSelectionMerge` merge instruction. merge_label: Option, /// A set of `(VALUES, PARENT)` pairs, used to build `OpPhi` instructions in /// the merge block. Each `PARENT` is the label of a predecessor block of /// the merge block. The corresponding `VALUES` holds the ids of the values /// that `PARENT` contributes to the merged values. /// /// We emit all branches to the merge block, so we know all its /// predecessors. And we refuse to emit a branch unless we're given the /// values the branching block contributes to the merge, so we always have /// everything we need to emit the correct phis, by construction. values: Vec<(M, Word)>, /// The types of the values in each element of `values`. merge_types: M, } impl<'b, M: MergeTuple> Selection<'b, M> { /// Start a new selection construct. /// /// The `block` argument indicates the selection's header block. /// /// The `merge_types` argument should be a `Word` or tuple of `Word`s, each /// value being the SPIR-V result type id of an `OpPhi` instruction that /// will be written to the selection's merge block when this selection's /// [`finish`] method is called. This argument may also be `()`, for /// selections that produce no values. /// /// (This function writes no code to `block` itself; it simply constructs a /// fresh `Selection`.) /// /// [`finish`]: Selection::finish pub(super) const fn start(block: &'b mut Block, merge_types: M) -> Self { Selection { block, merge_label: None, values: vec![], merge_types, } } pub(super) const fn block(&mut self) -> &mut Block { self.block } /// Branch to a successor block if `cond` is true, otherwise merge. /// /// If `cond` is false, branch to the merge block, using `values` as the /// merged values. Otherwise, proceed to a new block. /// /// The `values` argument must be the same shape as the `merge_types` /// argument passed to `Selection::start`. pub(super) fn if_true(&mut self, ctx: &mut BlockContext, cond: Word, values: M) { self.values.push((values, self.block.label_id)); let merge_label = self.make_merge_label(ctx); let next_label = ctx.gen_id(); ctx.function.consume( core::mem::replace(self.block, Block::new(next_label)), Instruction::branch_conditional(cond, next_label, merge_label), ); } /// Emit an unconditional branch to the merge block, and compute merged /// values. /// /// Use `final_values` as the merged values contributed by the current /// block, and transition to the merge block, emitting `OpPhi` instructions /// to produce the merged values. This must be the same shape as the /// `merge_types` argument passed to [`Selection::start`]. /// /// Return the SPIR-V ids of the merged values. This value has the same /// shape as the `merge_types` argument passed to `Selection::start`. pub(super) fn finish(self, ctx: &mut BlockContext, final_values: M) -> M { match self { Selection { merge_label: None, .. } => { // We didn't actually emit any branches, so `self.values` must // be empty, and `final_values` are the only sources we have for // the merged values. Easy peasy. final_values } Selection { block, merge_label: Some(merge_label), mut values, merge_types, } => { // Emit the final branch and transition to the merge block. values.push((final_values, block.label_id)); ctx.function.consume( core::mem::replace(block, Block::new(merge_label)), Instruction::branch(merge_label), ); // Now that we're in the merge block, build the phi instructions. merge_types.write_phis(ctx, block, &values) } } } /// Return the id of the merge block, writing a merge instruction if needed. fn make_merge_label(&mut self, ctx: &mut BlockContext) -> Word { match self.merge_label { None => { let merge_label = ctx.gen_id(); self.block.body.push(Instruction::selection_merge( merge_label, spirv::SelectionControl::NONE, )); self.merge_label = Some(merge_label); merge_label } Some(merge_label) => merge_label, } } } /// A trait to help `Selection` manage any number of merged values. /// /// Some selection constructs, like a `ReadZeroSkipWrite` bounds check on a /// [`Load`] expression, produce a single merged value. Others produce no merged /// value, like a bounds check on a [`Store`] statement. /// /// To let `Selection` work nicely with both cases, we let the merge type /// argument passed to [`Selection::start`] be any type that implements this /// `MergeTuple` trait. `MergeTuple` is then implemented for `()`, `Word`, /// `(Word, Word)`, and so on. /// /// A `MergeTuple` type can represent either a bunch of SPIR-V types or values; /// the `merge_types` argument to `Selection::start` are type ids, whereas the /// `values` arguments to the [`if_true`] and [`finish`] methods are value ids. /// The set of merged value returned by `finish` is a tuple of value ids. /// /// In fact, since Naga only uses zero- and single-valued selection constructs /// at present, we only implement `MergeTuple` for `()` and `Word`. But if you /// add more cases, feel free to add more implementations. Once const generics /// are available, we could have a single implementation of `MergeTuple` for all /// lengths of arrays, and be done with it. /// /// [`Load`]: crate::Expression::Load /// [`Store`]: crate::Statement::Store /// [`if_true`]: Selection::if_true /// [`finish`]: Selection::finish pub(super) trait MergeTuple: Sized { /// Write OpPhi instructions for the given set of predecessors. /// /// The `predecessors` vector should be a vector of `(LABEL, VALUES)` pairs, /// where each `VALUES` holds the values contributed by the branch from /// `LABEL`, which should be one of the current block's predecessors. fn write_phis( self, ctx: &mut BlockContext, block: &mut Block, predecessors: &[(Self, Word)], ) -> Self; } /// Selections that produce a single merged value. /// /// For example, `ImageLoad` with `BoundsCheckPolicy::ReadZeroSkipWrite` either /// returns a texel value or zeros. impl MergeTuple for Word { fn write_phis( self, ctx: &mut BlockContext, block: &mut Block, predecessors: &[(Word, Word)], ) -> Word { let merged_value = ctx.gen_id(); block .body .push(Instruction::phi(self, merged_value, predecessors)); merged_value } } /// Selections that produce no merged values. /// /// For example, `ImageStore` under `BoundsCheckPolicy::ReadZeroSkipWrite` /// either does the store or skips it, but in neither case does it produce a /// value. impl MergeTuple for () { /// No phis need to be generated. fn write_phis(self, _: &mut BlockContext, _: &mut Block, _: &[((), Word)]) {} } ================================================ FILE: naga/src/back/spv/subgroup.rs ================================================ use super::{Block, BlockContext, Error, Instruction, NumericType}; use crate::{arena::Handle, TypeInner}; impl BlockContext<'_> { pub(super) fn write_subgroup_ballot( &mut self, predicate: &Option>, result: Handle, block: &mut Block, ) -> Result<(), Error> { self.writer.require_any( "GroupNonUniformBallot", &[spirv::Capability::GroupNonUniformBallot], )?; let vec4_u32_type_id = self.get_numeric_type_id(NumericType::Vector { size: crate::VectorSize::Quad, scalar: crate::Scalar::U32, }); let exec_scope_id = self.get_index_constant(spirv::Scope::Subgroup as u32); let predicate = if let Some(predicate) = *predicate { self.cached[predicate] } else { self.writer.get_constant_scalar(crate::Literal::Bool(true)) }; let id = self.gen_id(); block.body.push(Instruction::group_non_uniform_ballot( vec4_u32_type_id, id, exec_scope_id, predicate, )); self.cached[result] = id; Ok(()) } pub(super) fn write_subgroup_operation( &mut self, op: &crate::SubgroupOperation, collective_op: &crate::CollectiveOperation, argument: Handle, result: Handle, block: &mut Block, ) -> Result<(), Error> { use crate::SubgroupOperation as sg; match *op { sg::All | sg::Any => { self.writer.require_any( "GroupNonUniformVote", &[spirv::Capability::GroupNonUniformVote], )?; } _ => { self.writer.require_any( "GroupNonUniformArithmetic", &[spirv::Capability::GroupNonUniformArithmetic], )?; } } let id = self.gen_id(); let result_ty = &self.fun_info[result].ty; let result_type_id = self.get_expression_type_id(result_ty); let result_ty_inner = result_ty.inner_with(&self.ir_module.types); let (is_scalar, scalar) = match *result_ty_inner { TypeInner::Scalar(kind) => (true, kind), TypeInner::Vector { scalar: kind, .. } => (false, kind), _ => unimplemented!(), }; use crate::ScalarKind as sk; let spirv_op = match (scalar.kind, *op) { (sk::Bool, sg::All) if is_scalar => spirv::Op::GroupNonUniformAll, (sk::Bool, sg::Any) if is_scalar => spirv::Op::GroupNonUniformAny, (_, sg::All | sg::Any) => unimplemented!(), (sk::Sint | sk::Uint, sg::Add) => spirv::Op::GroupNonUniformIAdd, (sk::Float, sg::Add) => spirv::Op::GroupNonUniformFAdd, (sk::Sint | sk::Uint, sg::Mul) => spirv::Op::GroupNonUniformIMul, (sk::Float, sg::Mul) => spirv::Op::GroupNonUniformFMul, (sk::Sint, sg::Max) => spirv::Op::GroupNonUniformSMax, (sk::Uint, sg::Max) => spirv::Op::GroupNonUniformUMax, (sk::Float, sg::Max) => spirv::Op::GroupNonUniformFMax, (sk::Sint, sg::Min) => spirv::Op::GroupNonUniformSMin, (sk::Uint, sg::Min) => spirv::Op::GroupNonUniformUMin, (sk::Float, sg::Min) => spirv::Op::GroupNonUniformFMin, (_, sg::Add | sg::Mul | sg::Min | sg::Max) => unimplemented!(), (sk::Sint | sk::Uint, sg::And) => spirv::Op::GroupNonUniformBitwiseAnd, (sk::Sint | sk::Uint, sg::Or) => spirv::Op::GroupNonUniformBitwiseOr, (sk::Sint | sk::Uint, sg::Xor) => spirv::Op::GroupNonUniformBitwiseXor, (sk::Bool, sg::And) => spirv::Op::GroupNonUniformLogicalAnd, (sk::Bool, sg::Or) => spirv::Op::GroupNonUniformLogicalOr, (sk::Bool, sg::Xor) => spirv::Op::GroupNonUniformLogicalXor, (_, sg::And | sg::Or | sg::Xor) => unimplemented!(), }; let exec_scope_id = self.get_index_constant(spirv::Scope::Subgroup as u32); use crate::CollectiveOperation as c; let group_op = match *op { sg::All | sg::Any => None, _ => Some(match *collective_op { c::Reduce => spirv::GroupOperation::Reduce, c::InclusiveScan => spirv::GroupOperation::InclusiveScan, c::ExclusiveScan => spirv::GroupOperation::ExclusiveScan, }), }; let arg_id = self.cached[argument]; block.body.push(Instruction::group_non_uniform_arithmetic( spirv_op, result_type_id, id, exec_scope_id, group_op, arg_id, )); self.cached[result] = id; Ok(()) } pub(super) fn write_subgroup_gather( &mut self, mode: &crate::GatherMode, argument: Handle, result: Handle, block: &mut Block, ) -> Result<(), Error> { match *mode { crate::GatherMode::BroadcastFirst => { self.writer.require_any( "GroupNonUniformBallot", &[spirv::Capability::GroupNonUniformBallot], )?; } crate::GatherMode::Shuffle(_) | crate::GatherMode::ShuffleXor(_) | crate::GatherMode::Broadcast(_) => { self.writer.require_any( "GroupNonUniformShuffle", &[spirv::Capability::GroupNonUniformShuffle], )?; } crate::GatherMode::ShuffleDown(_) | crate::GatherMode::ShuffleUp(_) => { self.writer.require_any( "GroupNonUniformShuffleRelative", &[spirv::Capability::GroupNonUniformShuffleRelative], )?; } crate::GatherMode::QuadBroadcast(_) | crate::GatherMode::QuadSwap(_) => { self.writer.require_any( "GroupNonUniformQuad", &[spirv::Capability::GroupNonUniformQuad], )?; } } let id = self.gen_id(); let result_ty = &self.fun_info[result].ty; let result_type_id = self.get_expression_type_id(result_ty); let exec_scope_id = self.get_index_constant(spirv::Scope::Subgroup as u32); let arg_id = self.cached[argument]; match *mode { crate::GatherMode::BroadcastFirst => { block .body .push(Instruction::group_non_uniform_broadcast_first( result_type_id, id, exec_scope_id, arg_id, )); } crate::GatherMode::Broadcast(index) | crate::GatherMode::Shuffle(index) | crate::GatherMode::ShuffleDown(index) | crate::GatherMode::ShuffleUp(index) | crate::GatherMode::ShuffleXor(index) | crate::GatherMode::QuadBroadcast(index) => { let index_id = self.cached[index]; let op = match *mode { crate::GatherMode::BroadcastFirst => unreachable!(), // Use shuffle to emit broadcast to allow the index to // be dynamically uniform on Vulkan 1.1. The argument to // OpGroupNonUniformBroadcast must be a constant pre SPIR-V // 1.5 (vulkan 1.2) crate::GatherMode::Broadcast(_) => spirv::Op::GroupNonUniformShuffle, crate::GatherMode::Shuffle(_) => spirv::Op::GroupNonUniformShuffle, crate::GatherMode::ShuffleDown(_) => spirv::Op::GroupNonUniformShuffleDown, crate::GatherMode::ShuffleUp(_) => spirv::Op::GroupNonUniformShuffleUp, crate::GatherMode::ShuffleXor(_) => spirv::Op::GroupNonUniformShuffleXor, crate::GatherMode::QuadBroadcast(_) => spirv::Op::GroupNonUniformQuadBroadcast, crate::GatherMode::QuadSwap(_) => unreachable!(), }; block.body.push(Instruction::group_non_uniform_gather( op, result_type_id, id, exec_scope_id, arg_id, index_id, )); } crate::GatherMode::QuadSwap(direction) => { let direction = self.get_index_constant(match direction { crate::Direction::X => 0, crate::Direction::Y => 1, crate::Direction::Diagonal => 2, }); block.body.push(Instruction::group_non_uniform_quad_swap( result_type_id, id, exec_scope_id, arg_id, direction, )); } } self.cached[result] = id; Ok(()) } } ================================================ FILE: naga/src/back/spv/writer.rs ================================================ use alloc::{format, string::String, vec, vec::Vec}; use arrayvec::ArrayVec; use hashbrown::hash_map::Entry; use spirv::Word; use super::{ block::DebugInfoInner, helpers::{contains_builtin, global_needs_wrapper, map_storage_class}, Block, BlockContext, CachedConstant, CachedExpressions, CooperativeType, DebugInfo, EntryPointContext, Error, Function, FunctionArgument, GlobalVariable, IdGenerator, Instruction, LocalImageType, LocalType, LocalVariable, LogicalLayout, LookupFunctionType, LookupType, NumericType, Options, PhysicalLayout, PipelineOptions, ResultMember, Writer, WriterFlags, BITS_PER_BYTE, }; use crate::{ arena::{Handle, HandleVec, UniqueArena}, back::spv::{ helpers::{is_uniform_matcx2_struct_member_access, BindingDecorations}, BindingInfo, Std140CompatTypeInfo, WrappedFunction, }, common::ForDebugWithTypes as _, proc::{Alignment, TypeResolution}, valid::{FunctionInfo, ModuleInfo}, }; pub struct FunctionInterface<'a> { pub varying_ids: &'a mut Vec, pub stage: crate::ShaderStage, pub task_payload: Option>, pub mesh_info: Option, pub workgroup_size: [u32; 3], } impl Function { pub(super) fn to_words(&self, sink: &mut impl Extend) { self.signature.as_ref().unwrap().to_words(sink); for argument in self.parameters.iter() { argument.instruction.to_words(sink); } for (index, block) in self.blocks.iter().enumerate() { Instruction::label(block.label_id).to_words(sink); if index == 0 { for local_var in self.variables.values() { local_var.instruction.to_words(sink); } for local_var in self.ray_query_initialization_tracker_variables.values() { local_var.instruction.to_words(sink); } for local_var in self.ray_query_t_max_tracker_variables.values() { local_var.instruction.to_words(sink); } for local_var in self.force_loop_bounding_vars.iter() { local_var.instruction.to_words(sink); } for internal_var in self.spilled_composites.values() { internal_var.instruction.to_words(sink); } } for instruction in block.body.iter() { instruction.to_words(sink); } } Instruction::function_end().to_words(sink); } } impl Writer { pub fn new(options: &Options) -> Result { let (major, minor) = options.lang_version; if major != 1 { return Err(Error::UnsupportedVersion(major, minor)); } let mut capabilities_used = crate::FastIndexSet::default(); capabilities_used.insert(spirv::Capability::Shader); let mut id_gen = IdGenerator::default(); let gl450_ext_inst_id = id_gen.next(); let void_type = id_gen.next(); Ok(Writer { physical_layout: PhysicalLayout::new(major, minor), logical_layout: LogicalLayout::default(), id_gen, capabilities_available: options.capabilities.clone(), capabilities_used, extensions_used: crate::FastIndexSet::default(), debug_strings: vec![], debugs: vec![], annotations: vec![], flags: options.flags, bounds_check_policies: options.bounds_check_policies, zero_initialize_workgroup_memory: options.zero_initialize_workgroup_memory, force_loop_bounding: options.force_loop_bounding, ray_query_initialization_tracking: options.ray_query_initialization_tracking, use_storage_input_output_16: options.use_storage_input_output_16, void_type, tuple_of_u32s_ty_id: None, lookup_type: crate::FastHashMap::default(), lookup_function: crate::FastHashMap::default(), lookup_function_type: crate::FastHashMap::default(), wrapped_functions: crate::FastHashMap::default(), constant_ids: HandleVec::new(), cached_constants: crate::FastHashMap::default(), global_variables: HandleVec::new(), std140_compat_uniform_types: crate::FastHashMap::default(), fake_missing_bindings: options.fake_missing_bindings, binding_map: options.binding_map.clone(), saved_cached: CachedExpressions::default(), gl450_ext_inst_id, temp_list: Vec::new(), ray_query_functions: crate::FastHashMap::default(), io_f16_polyfills: super::f16_polyfill::F16IoPolyfill::new( options.use_storage_input_output_16, ), debug_printf: None, task_dispatch_limits: options.task_dispatch_limits, mesh_shader_primitive_indices_clamp: options.mesh_shader_primitive_indices_clamp, }) } pub fn set_options(&mut self, options: &Options) -> Result<(), Error> { let (major, minor) = options.lang_version; if major != 1 { return Err(Error::UnsupportedVersion(major, minor)); } self.physical_layout = PhysicalLayout::new(major, minor); self.capabilities_available = options.capabilities.clone(); self.flags = options.flags; self.bounds_check_policies = options.bounds_check_policies; self.zero_initialize_workgroup_memory = options.zero_initialize_workgroup_memory; self.force_loop_bounding = options.force_loop_bounding; self.use_storage_input_output_16 = options.use_storage_input_output_16; self.binding_map = options.binding_map.clone(); self.io_f16_polyfills = super::f16_polyfill::F16IoPolyfill::new(options.use_storage_input_output_16); self.task_dispatch_limits = options.task_dispatch_limits; self.mesh_shader_primitive_indices_clamp = options.mesh_shader_primitive_indices_clamp; Ok(()) } /// Returns `(major, minor)` of the SPIR-V language version. pub const fn lang_version(&self) -> (u8, u8) { self.physical_layout.lang_version() } /// Reset `Writer` to its initial state, retaining any allocations. /// /// Why not just implement `Reclaimable` for `Writer`? By design, /// `Reclaimable::reclaim` requires ownership of the value, not just /// `&mut`; see the trait documentation. But we need to use this method /// from functions like `Writer::write`, which only have `&mut Writer`. /// Workarounds include unsafe code (`core::ptr::read`, then `write`, ugh) /// or something like a `Default` impl that returns an oddly-initialized /// `Writer`, which is worse. fn reset(&mut self) { use super::reclaimable::Reclaimable; use core::mem::take; let mut id_gen = IdGenerator::default(); let gl450_ext_inst_id = id_gen.next(); let void_type = id_gen.next(); // Every field of the old writer that is not determined by the `Options` // passed to `Writer::new` should be reset somehow. let fresh = Writer { // Copied from the old Writer: flags: self.flags, bounds_check_policies: self.bounds_check_policies, zero_initialize_workgroup_memory: self.zero_initialize_workgroup_memory, force_loop_bounding: self.force_loop_bounding, ray_query_initialization_tracking: self.ray_query_initialization_tracking, use_storage_input_output_16: self.use_storage_input_output_16, capabilities_available: take(&mut self.capabilities_available), fake_missing_bindings: self.fake_missing_bindings, binding_map: take(&mut self.binding_map), task_dispatch_limits: self.task_dispatch_limits, mesh_shader_primitive_indices_clamp: self.mesh_shader_primitive_indices_clamp, // Initialized afresh: id_gen, void_type, tuple_of_u32s_ty_id: None, gl450_ext_inst_id, // Reclaimed: capabilities_used: take(&mut self.capabilities_used).reclaim(), extensions_used: take(&mut self.extensions_used).reclaim(), physical_layout: self.physical_layout.clone().reclaim(), logical_layout: take(&mut self.logical_layout).reclaim(), debug_strings: take(&mut self.debug_strings).reclaim(), debugs: take(&mut self.debugs).reclaim(), annotations: take(&mut self.annotations).reclaim(), lookup_type: take(&mut self.lookup_type).reclaim(), lookup_function: take(&mut self.lookup_function).reclaim(), lookup_function_type: take(&mut self.lookup_function_type).reclaim(), wrapped_functions: take(&mut self.wrapped_functions).reclaim(), constant_ids: take(&mut self.constant_ids).reclaim(), cached_constants: take(&mut self.cached_constants).reclaim(), global_variables: take(&mut self.global_variables).reclaim(), std140_compat_uniform_types: take(&mut self.std140_compat_uniform_types).reclaim(), saved_cached: take(&mut self.saved_cached).reclaim(), temp_list: take(&mut self.temp_list).reclaim(), ray_query_functions: take(&mut self.ray_query_functions).reclaim(), io_f16_polyfills: take(&mut self.io_f16_polyfills).reclaim(), debug_printf: None, }; *self = fresh; self.capabilities_used.insert(spirv::Capability::Shader); } /// Indicate that the code requires any one of the listed capabilities. /// /// If nothing in `capabilities` appears in the available capabilities /// specified in the [`Options`] from which this `Writer` was created, /// return an error. The `what` string is used in the error message to /// explain what provoked the requirement. (If no available capabilities were /// given, assume everything is available.) /// /// The first acceptable capability will be added to this `Writer`'s /// [`capabilities_used`] table, and an `OpCapability` emitted for it in the /// result. For this reason, more specific capabilities should be listed /// before more general. /// /// [`capabilities_used`]: Writer::capabilities_used pub(super) fn require_any( &mut self, what: &'static str, capabilities: &[spirv::Capability], ) -> Result<(), Error> { match *capabilities { [] => Ok(()), [first, ..] => { // Find the first acceptable capability, or return an error if // there is none. let selected = match self.capabilities_available { None => first, Some(ref available) => { match capabilities .iter() // need explicit type for hashbrown::HashSet::contains fn call to keep rustc happy .find(|cap| available.contains::(cap)) { Some(&cap) => cap, None => { return Err(Error::MissingCapabilities(what, capabilities.to_vec())) } } } }; self.capabilities_used.insert(selected); Ok(()) } } } /// Indicate that the code requires all of the listed capabilities. /// /// If all entries of `capabilities` appear in the available capabilities /// specified in the [`Options`] from which this `Writer` was created /// (including the case where [`Options::capabilities`] is `None`), add /// them all to this `Writer`'s [`capabilities_used`] table, and return /// `Ok(())`. If at least one of the listed capabilities is not available, /// do not add anything to the `capabilities_used` table, and return the /// first unavailable requested capability, wrapped in `Err()`. /// /// This method is does not return an [`enum@Error`] in case of failure /// because it may be used in cases where the caller can recover (e.g., /// with a polyfill) if the requested capabilities are not available. In /// this case, it would be unnecessary work to find *all* the unavailable /// requested capabilities, and to allocate a `Vec` for them, just so we /// could return an [`Error::MissingCapabilities`]). /// /// [`capabilities_used`]: Writer::capabilities_used pub(super) fn require_all( &mut self, capabilities: &[spirv::Capability], ) -> Result<(), spirv::Capability> { if let Some(ref available) = self.capabilities_available { for requested in capabilities { if !available.contains(requested) { return Err(*requested); } } } for requested in capabilities { self.capabilities_used.insert(*requested); } Ok(()) } /// Indicate that the code uses the given extension. pub(super) fn use_extension(&mut self, extension: &'static str) { self.extensions_used.insert(extension); } pub(super) fn get_type_id(&mut self, lookup_ty: LookupType) -> Word { match self.lookup_type.entry(lookup_ty) { Entry::Occupied(e) => *e.get(), Entry::Vacant(e) => { let local = match lookup_ty { LookupType::Handle(_handle) => unreachable!("Handles are populated at start"), LookupType::Local(local) => local, }; let id = self.id_gen.next(); e.insert(id); self.write_type_declaration_local(id, local); id } } } pub(super) fn get_handle_type_id(&mut self, handle: Handle) -> Word { self.get_type_id(LookupType::Handle(handle)) } pub(super) fn get_expression_lookup_type(&mut self, tr: &TypeResolution) -> LookupType { match *tr { TypeResolution::Handle(ty_handle) => LookupType::Handle(ty_handle), TypeResolution::Value(ref inner) => { let inner_local_type = self.localtype_from_inner(inner).unwrap(); LookupType::Local(inner_local_type) } } } pub(super) fn get_expression_type_id(&mut self, tr: &TypeResolution) -> Word { let lookup_ty = self.get_expression_lookup_type(tr); self.get_type_id(lookup_ty) } pub(super) fn get_localtype_id(&mut self, local: LocalType) -> Word { self.get_type_id(LookupType::Local(local)) } pub(super) fn get_pointer_type_id(&mut self, base: Word, class: spirv::StorageClass) -> Word { self.get_type_id(LookupType::Local(LocalType::Pointer { base, class })) } pub(super) fn get_handle_pointer_type_id( &mut self, base: Handle, class: spirv::StorageClass, ) -> Word { let base_id = self.get_handle_type_id(base); self.get_pointer_type_id(base_id, class) } pub(super) fn get_ray_query_pointer_id(&mut self) -> Word { let rq_id = self.get_type_id(LookupType::Local(LocalType::RayQuery)); self.get_pointer_type_id(rq_id, spirv::StorageClass::Function) } /// Return a SPIR-V type for a pointer to `resolution`. /// /// The given `resolution` must be one that we can represent /// either as a `LocalType::Pointer` or `LocalType::LocalPointer`. pub(super) fn get_resolution_pointer_id( &mut self, resolution: &TypeResolution, class: spirv::StorageClass, ) -> Word { let resolution_type_id = self.get_expression_type_id(resolution); self.get_pointer_type_id(resolution_type_id, class) } pub(super) fn get_numeric_type_id(&mut self, numeric: NumericType) -> Word { self.get_type_id(LocalType::Numeric(numeric).into()) } pub(super) fn get_u32_type_id(&mut self) -> Word { self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::U32)) } pub(super) fn get_f32_type_id(&mut self) -> Word { self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::F32)) } pub(super) fn get_vec2u_type_id(&mut self) -> Word { self.get_numeric_type_id(NumericType::Vector { size: crate::VectorSize::Bi, scalar: crate::Scalar::U32, }) } pub(super) fn get_vec2f_type_id(&mut self) -> Word { self.get_numeric_type_id(NumericType::Vector { size: crate::VectorSize::Bi, scalar: crate::Scalar::F32, }) } pub(super) fn get_vec3u_type_id(&mut self) -> Word { self.get_numeric_type_id(NumericType::Vector { size: crate::VectorSize::Tri, scalar: crate::Scalar::U32, }) } pub(super) fn get_f32_pointer_type_id(&mut self, class: spirv::StorageClass) -> Word { let f32_id = self.get_f32_type_id(); self.get_pointer_type_id(f32_id, class) } pub(super) fn get_vec2u_pointer_type_id(&mut self, class: spirv::StorageClass) -> Word { let vec2u_id = self.get_numeric_type_id(NumericType::Vector { size: crate::VectorSize::Bi, scalar: crate::Scalar::U32, }); self.get_pointer_type_id(vec2u_id, class) } pub(super) fn get_bool_type_id(&mut self) -> Word { self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::BOOL)) } pub(super) fn get_vec2_bool_type_id(&mut self) -> Word { self.get_numeric_type_id(NumericType::Vector { size: crate::VectorSize::Bi, scalar: crate::Scalar::BOOL, }) } pub(super) fn get_vec3_bool_type_id(&mut self) -> Word { self.get_numeric_type_id(NumericType::Vector { size: crate::VectorSize::Tri, scalar: crate::Scalar::BOOL, }) } /// Used for "mulhi" to get the upper bits of multiplication. /// /// More specifically, `OpUMulExtended` multiplies 2 numbers and returns the lower and upper bits of the result /// as a user-defined struct type with 2 u32s. This defines that struct. pub(super) fn get_tuple_of_u32s_ty_id(&mut self) -> Word { if let Some(val) = self.tuple_of_u32s_ty_id { val } else { let id = self.id_gen.next(); let u32_id = self.get_u32_type_id(); let ins = Instruction::type_struct(id, &[u32_id, u32_id]); ins.to_words(&mut self.logical_layout.declarations); self.tuple_of_u32s_ty_id = Some(id); id } } pub(super) fn decorate(&mut self, id: Word, decoration: spirv::Decoration, operands: &[Word]) { self.annotations .push(Instruction::decorate(id, decoration, operands)); } /// Return `inner` as a `LocalType`, if that's possible. /// /// If `inner` can be represented as a `LocalType`, return /// `Some(local_type)`. /// /// Otherwise, return `None`. In this case, the type must always be looked /// up using a `LookupType::Handle`. fn localtype_from_inner(&mut self, inner: &crate::TypeInner) -> Option { Some(match *inner { crate::TypeInner::Scalar(_) | crate::TypeInner::Atomic(_) | crate::TypeInner::Vector { .. } | crate::TypeInner::Matrix { .. } => { // We expect `NumericType::from_inner` to handle all // these cases, so unwrap. LocalType::Numeric(NumericType::from_inner(inner).unwrap()) } crate::TypeInner::CooperativeMatrix { .. } => { LocalType::Cooperative(CooperativeType::from_inner(inner).unwrap()) } crate::TypeInner::Pointer { base, space } => { let base_type_id = self.get_handle_type_id(base); LocalType::Pointer { base: base_type_id, class: map_storage_class(space), } } crate::TypeInner::ValuePointer { size, scalar, space, } => { let base_numeric_type = match size { Some(size) => NumericType::Vector { size, scalar }, None => NumericType::Scalar(scalar), }; LocalType::Pointer { base: self.get_numeric_type_id(base_numeric_type), class: map_storage_class(space), } } crate::TypeInner::Image { dim, arrayed, class, } => LocalType::Image(LocalImageType::from_inner(dim, arrayed, class)), crate::TypeInner::Sampler { comparison: _ } => LocalType::Sampler, crate::TypeInner::AccelerationStructure { .. } => LocalType::AccelerationStructure, crate::TypeInner::RayQuery { .. } => LocalType::RayQuery, crate::TypeInner::Array { .. } | crate::TypeInner::Struct { .. } | crate::TypeInner::BindingArray { .. } => return None, }) } /// Resolve the [`BindingInfo`] for a [`crate::ResourceBinding`] from the /// provided [`Writer::binding_map`]. /// /// If the specified resource is not present in the binding map this will /// return an error, unless [`Writer::fake_missing_bindings`] is set. fn resolve_resource_binding( &self, res_binding: &crate::ResourceBinding, ) -> Result { match self.binding_map.get(res_binding) { Some(target) => Ok(*target), None if self.fake_missing_bindings => Ok(BindingInfo { descriptor_set: res_binding.group, binding: res_binding.binding, binding_array_size: None, }), None => Err(Error::MissingBinding(*res_binding)), } } /// Emits code for any wrapper functions required by the expressions in ir_function. /// The IDs of any emitted functions will be stored in [`Self::wrapped_functions`]. fn write_wrapped_functions( &mut self, ir_function: &crate::Function, info: &FunctionInfo, ir_module: &crate::Module, ) -> Result<(), Error> { log::trace!("Generating wrapped functions for {:?}", ir_function.name); for (expr_handle, expr) in ir_function.expressions.iter() { match *expr { crate::Expression::Binary { op, left, right } => { let expr_ty_inner = info[expr_handle].ty.inner_with(&ir_module.types); if let Some(expr_ty) = NumericType::from_inner(expr_ty_inner) { match (op, expr_ty.scalar().kind) { // Division and modulo are undefined behaviour when the // dividend is the minimum representable value and the divisor // is negative one, or when the divisor is zero. These wrapped // functions override the divisor to one in these cases, // matching the WGSL spec. ( crate::BinaryOperator::Divide | crate::BinaryOperator::Modulo, crate::ScalarKind::Sint | crate::ScalarKind::Uint, ) => { self.write_wrapped_binary_op( op, expr_ty, &info[left].ty, &info[right].ty, )?; } _ => {} } } } crate::Expression::Load { pointer } => { if let crate::TypeInner::Pointer { base: pointer_type, space: crate::AddressSpace::Uniform, } = *info[pointer].ty.inner_with(&ir_module.types) { if self.std140_compat_uniform_types.contains_key(&pointer_type) { // Loading a std140 compat type requires the wrapper function // to convert to the regular type. self.write_wrapped_convert_from_std140_compat_type( ir_module, pointer_type, )?; } } } crate::Expression::Access { base, .. } => { if let crate::TypeInner::Pointer { base: base_type, space: crate::AddressSpace::Uniform, } = *info[base].ty.inner_with(&ir_module.types) { // Dynamic accesses of a two-row matrix's columns require a // wrapper function. if let crate::TypeInner::Matrix { rows: crate::VectorSize::Bi, .. } = ir_module.types[base_type].inner { self.write_wrapped_matcx2_get_column(ir_module, base_type)?; // If the matrix is *not* directly a member of a struct, then // we additionally require a wrapper function to convert from // the std140 compat type to the regular type. if !is_uniform_matcx2_struct_member_access( ir_function, info, ir_module, base, ) { self.write_wrapped_convert_from_std140_compat_type( ir_module, base_type, )?; } } } } _ => {} } } Ok(()) } /// Write a SPIR-V function that performs the operator `op` with Naga IR semantics. /// /// Define a function that performs an integer division or modulo operation, /// except that using a divisor of zero or causing signed overflow with a /// divisor of -1 returns the numerator unchanged, rather than exhibiting /// undefined behavior. /// /// Store the generated function's id in the [`wrapped_functions`] table. /// /// The operator `op` must be either [`Divide`] or [`Modulo`]. /// /// # Panics /// /// The `return_type`, `left_type` or `right_type` arguments must all be /// integer scalars or vectors. If not, this function panics. /// /// [`wrapped_functions`]: Writer::wrapped_functions /// [`Divide`]: crate::BinaryOperator::Divide /// [`Modulo`]: crate::BinaryOperator::Modulo fn write_wrapped_binary_op( &mut self, op: crate::BinaryOperator, return_type: NumericType, left_type: &TypeResolution, right_type: &TypeResolution, ) -> Result<(), Error> { let return_type_id = self.get_localtype_id(LocalType::Numeric(return_type)); let left_type_id = self.get_expression_type_id(left_type); let right_type_id = self.get_expression_type_id(right_type); // Check if we've already emitted this function. let wrapped = WrappedFunction::BinaryOp { op, left_type_id, right_type_id, }; let function_id = match self.wrapped_functions.entry(wrapped) { Entry::Occupied(_) => return Ok(()), Entry::Vacant(e) => *e.insert(self.id_gen.next()), }; let scalar = return_type.scalar(); if self.flags.contains(WriterFlags::DEBUG) { let function_name = match op { crate::BinaryOperator::Divide => "naga_div", crate::BinaryOperator::Modulo => "naga_mod", _ => unreachable!(), }; self.debugs .push(Instruction::name(function_id, function_name)); } let mut function = Function::default(); let function_type_id = self.get_function_type(LookupFunctionType { parameter_type_ids: vec![left_type_id, right_type_id], return_type_id, }); function.signature = Some(Instruction::function( return_type_id, function_id, spirv::FunctionControl::empty(), function_type_id, )); let lhs_id = self.id_gen.next(); let rhs_id = self.id_gen.next(); if self.flags.contains(WriterFlags::DEBUG) { self.debugs.push(Instruction::name(lhs_id, "lhs")); self.debugs.push(Instruction::name(rhs_id, "rhs")); } let left_par = Instruction::function_parameter(left_type_id, lhs_id); let right_par = Instruction::function_parameter(right_type_id, rhs_id); for instruction in [left_par, right_par] { function.parameters.push(FunctionArgument { instruction, handle_id: 0, }); } let label_id = self.id_gen.next(); let mut block = Block::new(label_id); let bool_type = return_type.with_scalar(crate::Scalar::BOOL); let bool_type_id = self.get_numeric_type_id(bool_type); let maybe_splat_const = |writer: &mut Self, const_id| match return_type { NumericType::Scalar(_) => const_id, NumericType::Vector { size, .. } => { let constituent_ids = [const_id; crate::VectorSize::MAX]; writer.get_constant_composite( LookupType::Local(LocalType::Numeric(return_type)), &constituent_ids[..size as usize], ) } NumericType::Matrix { .. } => unreachable!(), }; let const_zero_id = self.get_constant_scalar_with(0, scalar)?; let composite_zero_id = maybe_splat_const(self, const_zero_id); let rhs_eq_zero_id = self.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::IEqual, bool_type_id, rhs_eq_zero_id, rhs_id, composite_zero_id, )); let divisor_selector_id = match scalar.kind { crate::ScalarKind::Sint => { let (const_min_id, const_neg_one_id) = match scalar.width { 4 => Ok(( self.get_constant_scalar(crate::Literal::I32(i32::MIN)), self.get_constant_scalar(crate::Literal::I32(-1i32)), )), 8 => Ok(( self.get_constant_scalar(crate::Literal::I64(i64::MIN)), self.get_constant_scalar(crate::Literal::I64(-1i64)), )), _ => Err(Error::Validation("Unexpected scalar width")), }?; let composite_min_id = maybe_splat_const(self, const_min_id); let composite_neg_one_id = maybe_splat_const(self, const_neg_one_id); let lhs_eq_int_min_id = self.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::IEqual, bool_type_id, lhs_eq_int_min_id, lhs_id, composite_min_id, )); let rhs_eq_neg_one_id = self.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::IEqual, bool_type_id, rhs_eq_neg_one_id, rhs_id, composite_neg_one_id, )); let lhs_eq_int_min_and_rhs_eq_neg_one_id = self.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::LogicalAnd, bool_type_id, lhs_eq_int_min_and_rhs_eq_neg_one_id, lhs_eq_int_min_id, rhs_eq_neg_one_id, )); let rhs_eq_zero_or_lhs_eq_int_min_and_rhs_eq_neg_one_id = self.id_gen.next(); block.body.push(Instruction::binary( spirv::Op::LogicalOr, bool_type_id, rhs_eq_zero_or_lhs_eq_int_min_and_rhs_eq_neg_one_id, rhs_eq_zero_id, lhs_eq_int_min_and_rhs_eq_neg_one_id, )); rhs_eq_zero_or_lhs_eq_int_min_and_rhs_eq_neg_one_id } crate::ScalarKind::Uint => rhs_eq_zero_id, _ => unreachable!(), }; let const_one_id = self.get_constant_scalar_with(1, scalar)?; let composite_one_id = maybe_splat_const(self, const_one_id); let divisor_id = self.id_gen.next(); block.body.push(Instruction::select( right_type_id, divisor_id, divisor_selector_id, composite_one_id, rhs_id, )); let op = match (op, scalar.kind) { (crate::BinaryOperator::Divide, crate::ScalarKind::Sint) => spirv::Op::SDiv, (crate::BinaryOperator::Divide, crate::ScalarKind::Uint) => spirv::Op::UDiv, (crate::BinaryOperator::Modulo, crate::ScalarKind::Sint) => spirv::Op::SRem, (crate::BinaryOperator::Modulo, crate::ScalarKind::Uint) => spirv::Op::UMod, _ => unreachable!(), }; let return_id = self.id_gen.next(); block.body.push(Instruction::binary( op, return_type_id, return_id, lhs_id, divisor_id, )); function.consume(block, Instruction::return_value(return_id)); function.to_words(&mut self.logical_layout.function_definitions); Ok(()) } /// Writes a wrapper function to convert from a std140 compat type to its /// corresponding regular type. /// /// See [`Self::write_std140_compat_type_declaration`] for more details. fn write_wrapped_convert_from_std140_compat_type( &mut self, ir_module: &crate::Module, r#type: Handle, ) -> Result<(), Error> { // Check if we've already emitted this function. let wrapped = WrappedFunction::ConvertFromStd140CompatType { r#type }; let function_id = match self.wrapped_functions.entry(wrapped) { Entry::Occupied(_) => return Ok(()), Entry::Vacant(e) => *e.insert(self.id_gen.next()), }; if self.flags.contains(WriterFlags::DEBUG) { self.debugs.push(Instruction::name( function_id, &format!("{:?}_from_std140", r#type.for_debug(&ir_module.types)), )); } let param_type_id = self.std140_compat_uniform_types[&r#type].type_id; let return_type_id = self.get_handle_type_id(r#type); let mut function = Function::default(); let function_type_id = self.get_function_type(LookupFunctionType { parameter_type_ids: vec![param_type_id], return_type_id, }); function.signature = Some(Instruction::function( return_type_id, function_id, spirv::FunctionControl::empty(), function_type_id, )); let param_id = self.id_gen.next(); function.parameters.push(FunctionArgument { instruction: Instruction::function_parameter(param_type_id, param_id), handle_id: 0, }); let label_id = self.id_gen.next(); let mut block = Block::new(label_id); let result_id = match ir_module.types[r#type].inner { // Param is struct containing a vector member for each of the // matrix's columns. Extract each column from the struct then // composite into a matrix. crate::TypeInner::Matrix { columns, rows: rows @ crate::VectorSize::Bi, scalar, } => { let column_type_id = self.get_numeric_type_id(NumericType::Vector { size: rows, scalar }); let mut column_ids: ArrayVec = ArrayVec::new(); for column in 0..columns as u32 { let column_id = self.id_gen.next(); block.body.push(Instruction::composite_extract( column_type_id, column_id, param_id, &[column], )); column_ids.push(column_id); } let result_id = self.id_gen.next(); block.body.push(Instruction::composite_construct( return_type_id, result_id, &column_ids, )); result_id } // Param is an array where the base type is the std140 compatible // type corresponding to `base`. Iterate through each element and // call its conversion function, then composite into a new array. crate::TypeInner::Array { base, size, .. } => { // Ensure the conversion function for the array's base type is // declared. self.write_wrapped_convert_from_std140_compat_type(ir_module, base)?; let element_type_id = self.get_handle_type_id(base); let std140_element_type_id = self.std140_compat_uniform_types[&base].type_id; let element_conversion_function_id = self.wrapped_functions [&WrappedFunction::ConvertFromStd140CompatType { r#type: base }]; let mut element_ids = Vec::new(); let size = match size.resolve(ir_module.to_ctx())? { crate::proc::IndexableLength::Known(size) => size, crate::proc::IndexableLength::Dynamic => { return Err(Error::Validation( "Uniform buffers cannot contain dynamic arrays", )) } }; for i in 0..size { let std140_element_id = self.id_gen.next(); block.body.push(Instruction::composite_extract( std140_element_type_id, std140_element_id, param_id, &[i], )); let element_id = self.id_gen.next(); block.body.push(Instruction::function_call( element_type_id, element_id, element_conversion_function_id, &[std140_element_id], )); element_ids.push(element_id); } let result_id = self.id_gen.next(); block.body.push(Instruction::composite_construct( return_type_id, result_id, &element_ids, )); result_id } // Param is a struct where each two-row matrix member has been // decomposed in to separate vector members for each column. // Other members use their std140 compatible type if one exists, or // else their regular type. Iterate through each member, converting // or composing any matrices if required, then finally compose into // the struct. crate::TypeInner::Struct { ref members, .. } => { let mut member_ids = Vec::new(); let mut next_index = 0; for member in members { let member_id = self.id_gen.next(); let member_type_id = self.get_handle_type_id(member.ty); match ir_module.types[member.ty].inner { crate::TypeInner::Matrix { columns, rows: rows @ crate::VectorSize::Bi, scalar, } => { let mut column_ids: ArrayVec = ArrayVec::new(); let column_type_id = self .get_numeric_type_id(NumericType::Vector { size: rows, scalar }); for _ in 0..columns as u32 { let column_id = self.id_gen.next(); block.body.push(Instruction::composite_extract( column_type_id, column_id, param_id, &[next_index], )); column_ids.push(column_id); next_index += 1; } block.body.push(Instruction::composite_construct( member_type_id, member_id, &column_ids, )); } _ => { // Ensure the conversion function for the member's // type is declared. self.write_wrapped_convert_from_std140_compat_type( ir_module, member.ty, )?; match self.std140_compat_uniform_types.get(&member.ty) { Some(std140_type_info) => { let std140_member_id = self.id_gen.next(); block.body.push(Instruction::composite_extract( std140_type_info.type_id, std140_member_id, param_id, &[next_index], )); let function_id = self.wrapped_functions [&WrappedFunction::ConvertFromStd140CompatType { r#type: member.ty, }]; block.body.push(Instruction::function_call( member_type_id, member_id, function_id, &[std140_member_id], )); next_index += 1; } None => { let member_id = self.id_gen.next(); block.body.push(Instruction::composite_extract( member_type_id, member_id, param_id, &[next_index], )); next_index += 1; } } } } member_ids.push(member_id); } let result_id = self.id_gen.next(); block.body.push(Instruction::composite_construct( return_type_id, result_id, &member_ids, )); result_id } _ => unreachable!(), }; function.consume(block, Instruction::return_value(result_id)); function.to_words(&mut self.logical_layout.function_definitions); Ok(()) } /// Writes a wrapper function to get an `OpTypeVector` column from an /// `OpTypeMatrix` with a dynamic index. /// /// This is used when accessing a column of a [`TypeInner::Matrix`] through /// a [`Uniform`] address space pointer. In such cases, the matrix will have /// been declared in SPIR-V using an alternative type where each column is a /// member of a containing struct. SPIR-V is unable to dynamically access /// struct members, so instead we load the matrix then call this function to /// access a column from the loaded value. /// /// [`TypeInner::Matrix`]: crate::TypeInner::Matrix /// [`Uniform`]: crate::AddressSpace::Uniform fn write_wrapped_matcx2_get_column( &mut self, ir_module: &crate::Module, r#type: Handle, ) -> Result<(), Error> { let wrapped = WrappedFunction::MatCx2GetColumn { r#type }; let function_id = match self.wrapped_functions.entry(wrapped) { Entry::Occupied(_) => return Ok(()), Entry::Vacant(e) => *e.insert(self.id_gen.next()), }; if self.flags.contains(WriterFlags::DEBUG) { self.debugs.push(Instruction::name( function_id, &format!("{:?}_get_column", r#type.for_debug(&ir_module.types)), )); } let crate::TypeInner::Matrix { columns, rows: rows @ crate::VectorSize::Bi, scalar, } = ir_module.types[r#type].inner else { unreachable!(); }; let mut function = Function::default(); let matrix_type_id = self.get_handle_type_id(r#type); let column_index_type_id = self.get_u32_type_id(); let column_type_id = self.get_numeric_type_id(NumericType::Vector { size: rows, scalar }); let matrix_param_id = self.id_gen.next(); let column_index_param_id = self.id_gen.next(); function.parameters.push(FunctionArgument { instruction: Instruction::function_parameter(matrix_type_id, matrix_param_id), handle_id: 0, }); function.parameters.push(FunctionArgument { instruction: Instruction::function_parameter( column_index_type_id, column_index_param_id, ), handle_id: 0, }); let function_type_id = self.get_function_type(LookupFunctionType { parameter_type_ids: vec![matrix_type_id, column_index_type_id], return_type_id: column_type_id, }); function.signature = Some(Instruction::function( column_type_id, function_id, spirv::FunctionControl::empty(), function_type_id, )); let label_id = self.id_gen.next(); let mut block = Block::new(label_id); // Create a switch case for each column in the matrix, where each case // extracts its column from the matrix. Finally we use OpPhi to return // the correct column. let merge_id = self.id_gen.next(); block.body.push(Instruction::selection_merge( merge_id, spirv::SelectionControl::NONE, )); let cases = (0..columns as u32) .map(|i| super::instructions::Case { value: i, label_id: self.id_gen.next(), }) .collect::>(); // Which label we branch to in the default (column index out-of-bounds) // case depends on our bounds check policy. let default_id = match self.bounds_check_policies.index { // For `Restrict`, treat the same as the final column. crate::proc::BoundsCheckPolicy::Restrict => cases.last().unwrap().label_id, // For `ReadZeroSkipWrite`, branch directly to the merge block. This // will be handled in the `OpPhi` below to produce a zero value. crate::proc::BoundsCheckPolicy::ReadZeroSkipWrite => merge_id, // For `Unchecked` we create a new block containing an // `OpUnreachable`. crate::proc::BoundsCheckPolicy::Unchecked => self.id_gen.next(), }; function.consume( block, Instruction::switch(column_index_param_id, default_id, &cases), ); // Emit a block for each case, and produce a list of variable and parent // block IDs that will be used in an `OpPhi` below to select the right // value. let mut var_parent_pairs = cases .into_iter() .map(|case| { let mut block = Block::new(case.label_id); let column_id = self.id_gen.next(); block.body.push(Instruction::composite_extract( column_type_id, column_id, matrix_param_id, &[case.value], )); function.consume(block, Instruction::branch(merge_id)); (column_id, case.label_id) }) // Need capacity for up to 4 columns plus possibly a default case. .collect::>(); // Emit a block or append the variable and parent `OpPhi` pair for the // column index out-of-bounds case, if required. match self.bounds_check_policies.index { // Don't need to do anything for `Restrict` as we have branched from // the final column case's block. crate::proc::BoundsCheckPolicy::Restrict => {} // For `ReadZeroSkipWrite` we have branched directly from the block // containing the `OpSwitch`. The `OpPhi` should produce a zero // value. crate::proc::BoundsCheckPolicy::ReadZeroSkipWrite => { var_parent_pairs.push((self.get_constant_null(column_type_id), label_id)); } // For `Unchecked` create a new block containing `OpUnreachable`. // This does not need to be handled by the `OpPhi`. crate::proc::BoundsCheckPolicy::Unchecked => { function.consume( Block::new(default_id), Instruction::new(spirv::Op::Unreachable), ); } } let mut block = Block::new(merge_id); let result_id = self.id_gen.next(); block.body.push(Instruction::phi( column_type_id, result_id, &var_parent_pairs, )); function.consume(block, Instruction::return_value(result_id)); function.to_words(&mut self.logical_layout.function_definitions); Ok(()) } fn write_function( &mut self, ir_function: &crate::Function, info: &FunctionInfo, ir_module: &crate::Module, mut interface: Option, debug_info: &Option, ) -> Result { self.write_wrapped_functions(ir_function, info, ir_module)?; log::trace!("Generating code for {:?}", ir_function.name); let mut function = Function::default(); let prelude_id = self.id_gen.next(); let mut prelude = Block::new(prelude_id); let mut ep_context = EntryPointContext { argument_ids: Vec::new(), results: Vec::new(), task_payload_variable_id: if let Some(ref i) = interface { i.task_payload.map(|a| self.global_variables[a].var_id) } else { None }, mesh_state: None, }; let mut parameter_type_ids = Vec::with_capacity(ir_function.arguments.len()); let mut local_invocation_index_var_id = None; let mut local_invocation_index_id = None; for argument in ir_function.arguments.iter() { let class = spirv::StorageClass::Input; let handle_ty = ir_module.types[argument.ty].inner.is_handle(); let argument_type_id = if handle_ty { self.get_handle_pointer_type_id(argument.ty, spirv::StorageClass::UniformConstant) } else { self.get_handle_type_id(argument.ty) }; if let Some(ref mut iface) = interface { let id = if let Some(ref binding) = argument.binding { let name = argument.name.as_deref(); let varying_id = self.write_varying( ir_module, iface.stage, class, name, argument.ty, binding, )?; iface.varying_ids.push(varying_id); let id = self.load_io_with_f16_polyfill( &mut prelude.body, varying_id, argument_type_id, ); if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationIndex) { local_invocation_index_id = Some(id); local_invocation_index_var_id = Some(varying_id); } id } else if let crate::TypeInner::Struct { ref members, .. } = ir_module.types[argument.ty].inner { let struct_id = self.id_gen.next(); let mut constituent_ids = Vec::with_capacity(members.len()); for member in members { let type_id = self.get_handle_type_id(member.ty); let name = member.name.as_deref(); let binding = member.binding.as_ref().unwrap(); let varying_id = self.write_varying( ir_module, iface.stage, class, name, member.ty, binding, )?; iface.varying_ids.push(varying_id); let id = self.load_io_with_f16_polyfill(&mut prelude.body, varying_id, type_id); constituent_ids.push(id); if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationIndex) { local_invocation_index_id = Some(id); local_invocation_index_var_id = Some(varying_id); } } prelude.body.push(Instruction::composite_construct( argument_type_id, struct_id, &constituent_ids, )); struct_id } else { unreachable!("Missing argument binding on an entry point"); }; ep_context.argument_ids.push(id); } else { let argument_id = self.id_gen.next(); let instruction = Instruction::function_parameter(argument_type_id, argument_id); if self.flags.contains(WriterFlags::DEBUG) { if let Some(ref name) = argument.name { self.debugs.push(Instruction::name(argument_id, name)); } } function.parameters.push(FunctionArgument { instruction, handle_id: if handle_ty { let id = self.id_gen.next(); prelude.body.push(Instruction::load( self.get_handle_type_id(argument.ty), id, argument_id, None, )); id } else { 0 }, }); parameter_type_ids.push(argument_type_id); }; } let return_type_id = match ir_function.result { Some(ref result) => { if let Some(ref mut iface) = interface { let mut has_point_size = false; let class = spirv::StorageClass::Output; if let Some(ref binding) = result.binding { has_point_size |= *binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); let type_id = self.get_handle_type_id(result.ty); let varying_id = if *binding == crate::Binding::BuiltIn(crate::BuiltIn::MeshTaskSize) { 0 } else { let varying_id = self.write_varying( ir_module, iface.stage, class, None, result.ty, binding, )?; iface.varying_ids.push(varying_id); varying_id }; ep_context.results.push(ResultMember { id: varying_id, type_id, built_in: binding.to_built_in(), }); } else if let crate::TypeInner::Struct { ref members, .. } = ir_module.types[result.ty].inner { for member in members { let type_id = self.get_handle_type_id(member.ty); let name = member.name.as_deref(); let binding = member.binding.as_ref().unwrap(); has_point_size |= *binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); // This isn't an actual builtin in SPIR-V. It can only appear as the // output of a task shader and the output is used when writing the // entry point return, in which case the id is ignored anyway. let varying_id = if *binding == crate::Binding::BuiltIn(crate::BuiltIn::MeshTaskSize) { 0 } else { let varying_id = self.write_varying( ir_module, iface.stage, class, name, member.ty, binding, )?; iface.varying_ids.push(varying_id); varying_id }; ep_context.results.push(ResultMember { id: varying_id, type_id, built_in: binding.to_built_in(), }); } } else { unreachable!("Missing result binding on an entry point"); } if self.flags.contains(WriterFlags::FORCE_POINT_SIZE) && iface.stage == crate::ShaderStage::Vertex && !has_point_size { // add point size artificially let varying_id = self.id_gen.next(); let pointer_type_id = self.get_f32_pointer_type_id(class); Instruction::variable(pointer_type_id, varying_id, class, None) .to_words(&mut self.logical_layout.declarations); self.decorate( varying_id, spirv::Decoration::BuiltIn, &[spirv::BuiltIn::PointSize as u32], ); iface.varying_ids.push(varying_id); let default_value_id = self.get_constant_scalar(crate::Literal::F32(1.0)); prelude .body .push(Instruction::store(varying_id, default_value_id, None)); } if iface.stage == crate::ShaderStage::Task { self.get_vec3u_type_id() } else { self.void_type } } else { self.get_handle_type_id(result.ty) } } None => self.void_type, }; if let Some(ref mut iface) = interface { if let Some(task_payload) = iface.task_payload { iface .varying_ids .push(self.global_variables[task_payload].var_id); } self.write_entry_point_mesh_shader_info( iface, local_invocation_index_var_id, ir_module, &mut ep_context, )?; } let lookup_function_type = LookupFunctionType { parameter_type_ids, return_type_id, }; let function_id = self.id_gen.next(); if self.flags.contains(WriterFlags::DEBUG) { if let Some(ref name) = ir_function.name { self.debugs.push(Instruction::name(function_id, name)); } } let function_type = self.get_function_type(lookup_function_type); function.signature = Some(Instruction::function( return_type_id, function_id, spirv::FunctionControl::empty(), function_type, )); if interface.is_some() { function.entry_point_context = Some(ep_context); } // fill up the `GlobalVariable::access_id` for gv in self.global_variables.iter_mut() { gv.reset_for_function(); } for (handle, var) in ir_module.global_variables.iter() { if info[handle].is_empty() { continue; } let mut gv = self.global_variables[handle].clone(); if let Some(ref mut iface) = interface { // Have to include global variables in the interface if self.physical_layout.version >= 0x10400 && iface.task_payload != Some(handle) { iface.varying_ids.push(gv.var_id); } } match ir_module.types[var.ty].inner { // Any that are binding arrays we skip as we cannot load the array, we must load the result after indexing. crate::TypeInner::BindingArray { .. } => { gv.access_id = gv.var_id; } _ => { // Handle globals are pre-emitted and should be loaded automatically. if var.space == crate::AddressSpace::Handle { let var_type_id = self.get_handle_type_id(var.ty); let id = self.id_gen.next(); prelude .body .push(Instruction::load(var_type_id, id, gv.var_id, None)); gv.access_id = gv.var_id; gv.handle_id = id; } else if global_needs_wrapper(ir_module, var) { let class = map_storage_class(var.space); let pointer_type_id = match self.std140_compat_uniform_types.get(&var.ty) { Some(std140_type_info) if var.space == crate::AddressSpace::Uniform => { self.get_pointer_type_id(std140_type_info.type_id, class) } _ => self.get_handle_pointer_type_id(var.ty, class), }; let index_id = self.get_index_constant(0); let id = self.id_gen.next(); prelude.body.push(Instruction::access_chain( pointer_type_id, id, gv.var_id, &[index_id], )); gv.access_id = id; } else { // by default, the variable ID is accessed as is gv.access_id = gv.var_id; }; } } // work around borrow checking in the presence of `self.xxx()` calls self.global_variables[handle] = gv; } // Create a `BlockContext` for generating SPIR-V for the function's // body. let mut context = BlockContext { ir_module, ir_function, fun_info: info, function: &mut function, // Re-use the cached expression table from prior functions. cached: core::mem::take(&mut self.saved_cached), // Steal the Writer's temp list for a bit. temp_list: core::mem::take(&mut self.temp_list), force_loop_bounding: self.force_loop_bounding, writer: self, expression_constness: super::ExpressionConstnessTracker::from_arena( &ir_function.expressions, ), ray_query_tracker_expr: crate::FastHashMap::default(), }; // fill up the pre-emitted and const expressions context.cached.reset(ir_function.expressions.len()); for (handle, expr) in ir_function.expressions.iter() { if (expr.needs_pre_emit() && !matches!(*expr, crate::Expression::LocalVariable(_))) || context.expression_constness.is_const(handle) { context.cache_expression_value(handle, &mut prelude)?; } } for (handle, variable) in ir_function.local_variables.iter() { let id = context.gen_id(); if context.writer.flags.contains(WriterFlags::DEBUG) { if let Some(ref name) = variable.name { context.writer.debugs.push(Instruction::name(id, name)); } } let init_word = variable.init.map(|constant| context.cached[constant]); let pointer_type_id = context .writer .get_handle_pointer_type_id(variable.ty, spirv::StorageClass::Function); let instruction = Instruction::variable( pointer_type_id, id, spirv::StorageClass::Function, init_word.or_else(|| match ir_module.types[variable.ty].inner { crate::TypeInner::RayQuery { .. } => None, _ => { let type_id = context.get_handle_type_id(variable.ty); Some(context.writer.write_constant_null(type_id)) } }), ); context .function .variables .insert(handle, LocalVariable { id, instruction }); if let crate::TypeInner::RayQuery { .. } = ir_module.types[variable.ty].inner { // Don't refactor this into a struct: Although spirv itself allows opaque types in structs, // the vulkan environment for spirv does not. Putting ray queries into structs can cause // confusing bugs. let u32_type_id = context.writer.get_u32_type_id(); let ptr_u32_type_id = context .writer .get_pointer_type_id(u32_type_id, spirv::StorageClass::Function); let tracker_id = context.gen_id(); let tracker_init_id = context.writer.get_constant_scalar(crate::Literal::U32( crate::back::RayQueryPoint::empty().bits(), )); let tracker_instruction = Instruction::variable( ptr_u32_type_id, tracker_id, spirv::StorageClass::Function, Some(tracker_init_id), ); context .function .ray_query_initialization_tracker_variables .insert( handle, LocalVariable { id: tracker_id, instruction: tracker_instruction, }, ); let f32_type_id = context.writer.get_f32_type_id(); let ptr_f32_type_id = context .writer .get_pointer_type_id(f32_type_id, spirv::StorageClass::Function); let t_max_tracker_id = context.gen_id(); let t_max_tracker_init_id = context.writer.get_constant_scalar(crate::Literal::F32(0.0)); let t_max_tracker_instruction = Instruction::variable( ptr_f32_type_id, t_max_tracker_id, spirv::StorageClass::Function, Some(t_max_tracker_init_id), ); context.function.ray_query_t_max_tracker_variables.insert( handle, LocalVariable { id: t_max_tracker_id, instruction: t_max_tracker_instruction, }, ); } } for (handle, expr) in ir_function.expressions.iter() { match *expr { crate::Expression::LocalVariable(_) => { // Cache the `OpVariable` instruction we generated above as // the value of this expression. context.cache_expression_value(handle, &mut prelude)?; } crate::Expression::Access { base, .. } | crate::Expression::AccessIndex { base, .. } => { // Count references to `base` by `Access` and `AccessIndex` // instructions. See `access_uses` for details. *context.function.access_uses.entry(base).or_insert(0) += 1; } _ => {} } } let next_id = context.gen_id(); context .function .consume(prelude, Instruction::branch(next_id)); let workgroup_vars_init_exit_block_id = match (context.writer.zero_initialize_workgroup_memory, interface) { ( super::ZeroInitializeWorkgroupMemoryMode::Polyfill, Some( ref mut interface @ FunctionInterface { stage: crate::ShaderStage::Compute | crate::ShaderStage::Mesh | crate::ShaderStage::Task, .. }, ), ) => context.writer.generate_workgroup_vars_init_block( next_id, ir_module, info, local_invocation_index_id, interface, context.function, ), _ => None, }; let main_id = if let Some(exit_id) = workgroup_vars_init_exit_block_id { exit_id } else { next_id }; context.write_function_body(main_id, debug_info.as_ref())?; // Consume the `BlockContext`, ending its borrows and letting the // `Writer` steal back its cached expression table and temp_list. let BlockContext { cached, temp_list, .. } = context; self.saved_cached = cached; self.temp_list = temp_list; function.to_words(&mut self.logical_layout.function_definitions); if let Some(EntryPointContext { mesh_state: Some(ref mesh_state), .. }) = function.entry_point_context { self.write_mesh_shader_wrapper(mesh_state, function_id) } else if let Some(EntryPointContext { task_payload_variable_id: Some(tp), .. }) = function.entry_point_context { self.write_task_shader_wrapper(tp, function_id) } else { Ok(function_id) } } fn write_execution_mode( &mut self, function_id: Word, mode: spirv::ExecutionMode, ) -> Result<(), Error> { //self.check(mode.required_capabilities())?; Instruction::execution_mode(function_id, mode, &[]) .to_words(&mut self.logical_layout.execution_modes); Ok(()) } // TODO Move to instructions module fn write_entry_point( &mut self, entry_point: &crate::EntryPoint, info: &FunctionInfo, ir_module: &crate::Module, debug_info: &Option, ) -> Result { let mut interface_ids = Vec::new(); let function_id = self.write_function( &entry_point.function, info, ir_module, Some(FunctionInterface { varying_ids: &mut interface_ids, stage: entry_point.stage, task_payload: entry_point.task_payload, mesh_info: entry_point.mesh_info.clone(), workgroup_size: entry_point.workgroup_size, }), debug_info, )?; let exec_model = match entry_point.stage { crate::ShaderStage::Vertex => spirv::ExecutionModel::Vertex, crate::ShaderStage::Fragment => { self.write_execution_mode(function_id, spirv::ExecutionMode::OriginUpperLeft)?; match entry_point.early_depth_test { Some(crate::EarlyDepthTest::Force) => { self.write_execution_mode( function_id, spirv::ExecutionMode::EarlyFragmentTests, )?; } Some(crate::EarlyDepthTest::Allow { conservative }) => { // TODO: Consider emitting EarlyAndLateFragmentTestsAMD here, if available. // https://github.khronos.org/SPIRV-Registry/extensions/AMD/SPV_AMD_shader_early_and_late_fragment_tests.html // This permits early depth tests even if the shader writes to a storage // binding match conservative { crate::ConservativeDepth::GreaterEqual => self.write_execution_mode( function_id, spirv::ExecutionMode::DepthGreater, )?, crate::ConservativeDepth::LessEqual => self.write_execution_mode( function_id, spirv::ExecutionMode::DepthLess, )?, crate::ConservativeDepth::Unchanged => self.write_execution_mode( function_id, spirv::ExecutionMode::DepthUnchanged, )?, } } None => {} } if let Some(ref result) = entry_point.function.result { if contains_builtin( result.binding.as_ref(), result.ty, &ir_module.types, crate::BuiltIn::FragDepth, ) { self.write_execution_mode( function_id, spirv::ExecutionMode::DepthReplacing, )?; } } spirv::ExecutionModel::Fragment } crate::ShaderStage::Compute => { let execution_mode = spirv::ExecutionMode::LocalSize; Instruction::execution_mode( function_id, execution_mode, &entry_point.workgroup_size, ) .to_words(&mut self.logical_layout.execution_modes); spirv::ExecutionModel::GLCompute } crate::ShaderStage::Task => { let execution_mode = spirv::ExecutionMode::LocalSize; Instruction::execution_mode( function_id, execution_mode, &entry_point.workgroup_size, ) .to_words(&mut self.logical_layout.execution_modes); spirv::ExecutionModel::TaskEXT } crate::ShaderStage::Mesh => { let execution_mode = spirv::ExecutionMode::LocalSize; Instruction::execution_mode( function_id, execution_mode, &entry_point.workgroup_size, ) .to_words(&mut self.logical_layout.execution_modes); let mesh_info = entry_point.mesh_info.as_ref().unwrap(); Instruction::execution_mode( function_id, match mesh_info.topology { crate::MeshOutputTopology::Points => spirv::ExecutionMode::OutputPoints, crate::MeshOutputTopology::Lines => spirv::ExecutionMode::OutputLinesEXT, crate::MeshOutputTopology::Triangles => { spirv::ExecutionMode::OutputTrianglesEXT } }, &[], ) .to_words(&mut self.logical_layout.execution_modes); Instruction::execution_mode( function_id, spirv::ExecutionMode::OutputVertices, core::slice::from_ref(&mesh_info.max_vertices), ) .to_words(&mut self.logical_layout.execution_modes); Instruction::execution_mode( function_id, spirv::ExecutionMode::OutputPrimitivesEXT, core::slice::from_ref(&mesh_info.max_primitives), ) .to_words(&mut self.logical_layout.execution_modes); spirv::ExecutionModel::MeshEXT } crate::ShaderStage::RayGeneration | crate::ShaderStage::AnyHit | crate::ShaderStage::ClosestHit | crate::ShaderStage::Miss => unreachable!(), }; //self.check(exec_model.required_capabilities())?; Ok(Instruction::entry_point( exec_model, function_id, &entry_point.name, interface_ids.as_slice(), )) } fn make_scalar(&mut self, id: Word, scalar: crate::Scalar) -> Instruction { use crate::ScalarKind as Sk; let bits = (scalar.width * BITS_PER_BYTE) as u32; match scalar.kind { Sk::Sint | Sk::Uint => { let signedness = if scalar.kind == Sk::Sint { super::instructions::Signedness::Signed } else { super::instructions::Signedness::Unsigned }; let cap = match bits { 8 => Some(spirv::Capability::Int8), 16 => Some(spirv::Capability::Int16), 64 => Some(spirv::Capability::Int64), _ => None, }; if let Some(cap) = cap { self.capabilities_used.insert(cap); } Instruction::type_int(id, bits, signedness) } Sk::Float => { if bits == 64 { self.capabilities_used.insert(spirv::Capability::Float64); } if bits == 16 { self.capabilities_used.insert(spirv::Capability::Float16); self.capabilities_used .insert(spirv::Capability::StorageBuffer16BitAccess); self.capabilities_used .insert(spirv::Capability::UniformAndStorageBuffer16BitAccess); if self.use_storage_input_output_16 { self.capabilities_used .insert(spirv::Capability::StorageInputOutput16); } } Instruction::type_float(id, bits) } Sk::Bool => Instruction::type_bool(id), Sk::AbstractInt | Sk::AbstractFloat => { unreachable!("abstract types should never reach the backend"); } } } fn request_type_capabilities(&mut self, inner: &crate::TypeInner) -> Result<(), Error> { match *inner { crate::TypeInner::Image { dim, arrayed, class, } => { let sampled = match class { crate::ImageClass::Sampled { .. } => true, crate::ImageClass::Depth { .. } => true, crate::ImageClass::Storage { format, .. } => { self.request_image_format_capabilities(format.into())?; false } crate::ImageClass::External => unimplemented!(), }; match dim { crate::ImageDimension::D1 => { if sampled { self.require_any("sampled 1D images", &[spirv::Capability::Sampled1D])?; } else { self.require_any("1D storage images", &[spirv::Capability::Image1D])?; } } crate::ImageDimension::Cube if arrayed => { if sampled { self.require_any( "sampled cube array images", &[spirv::Capability::SampledCubeArray], )?; } else { self.require_any( "cube array storage images", &[spirv::Capability::ImageCubeArray], )?; } } _ => {} } } crate::TypeInner::AccelerationStructure { .. } => { self.require_any("Acceleration Structure", &[spirv::Capability::RayQueryKHR])?; } crate::TypeInner::RayQuery { .. } => { self.require_any("Ray Query", &[spirv::Capability::RayQueryKHR])?; } crate::TypeInner::Atomic(crate::Scalar { width: 8, kind: _ }) => { self.require_any("64 bit integer atomics", &[spirv::Capability::Int64Atomics])?; } crate::TypeInner::Atomic(crate::Scalar { width: 4, kind: crate::ScalarKind::Float, }) => { self.require_any( "32 bit floating-point atomics", &[spirv::Capability::AtomicFloat32AddEXT], )?; self.use_extension("SPV_EXT_shader_atomic_float_add"); } // 16 bit floating-point support requires Float16 capability crate::TypeInner::Matrix { scalar: crate::Scalar::F16, .. } | crate::TypeInner::Vector { scalar: crate::Scalar::F16, .. } | crate::TypeInner::Scalar(crate::Scalar::F16) => { self.require_any("16 bit floating-point", &[spirv::Capability::Float16])?; self.use_extension("SPV_KHR_16bit_storage"); } // Cooperative types and ops crate::TypeInner::CooperativeMatrix { .. } => { self.require_any( "cooperative matrix", &[spirv::Capability::CooperativeMatrixKHR], )?; self.require_any("memory model", &[spirv::Capability::VulkanMemoryModel])?; self.use_extension("SPV_KHR_cooperative_matrix"); self.use_extension("SPV_KHR_vulkan_memory_model"); } _ => {} } Ok(()) } fn write_numeric_type_declaration_local(&mut self, id: Word, numeric: NumericType) { let instruction = match numeric { NumericType::Scalar(scalar) => self.make_scalar(id, scalar), NumericType::Vector { size, scalar } => { let scalar_id = self.get_numeric_type_id(NumericType::Scalar(scalar)); Instruction::type_vector(id, scalar_id, size) } NumericType::Matrix { columns, rows, scalar, } => { let column_id = self.get_numeric_type_id(NumericType::Vector { size: rows, scalar }); Instruction::type_matrix(id, column_id, columns) } }; instruction.to_words(&mut self.logical_layout.declarations); } fn write_cooperative_type_declaration_local(&mut self, id: Word, coop: CooperativeType) { let instruction = match coop { CooperativeType::Matrix { columns, rows, scalar, role, } => { let scalar_id = self.get_localtype_id(LocalType::Numeric(NumericType::Scalar(scalar))); let scope_id = self.get_index_constant(spirv::Scope::Subgroup as u32); let columns_id = self.get_index_constant(columns as u32); let rows_id = self.get_index_constant(rows as u32); let role_id = self.get_index_constant(spirv::CooperativeMatrixUse::from(role) as u32); Instruction::type_coop_matrix(id, scalar_id, scope_id, rows_id, columns_id, role_id) } }; instruction.to_words(&mut self.logical_layout.declarations); } fn write_type_declaration_local(&mut self, id: Word, local_ty: LocalType) { let instruction = match local_ty { LocalType::Numeric(numeric) => { self.write_numeric_type_declaration_local(id, numeric); return; } LocalType::Cooperative(coop) => { self.write_cooperative_type_declaration_local(id, coop); return; } LocalType::Pointer { base, class } => Instruction::type_pointer(id, class, base), LocalType::Image(image) => { let local_type = LocalType::Numeric(NumericType::Scalar(image.sampled_type)); let type_id = self.get_localtype_id(local_type); Instruction::type_image(id, type_id, image.dim, image.flags, image.image_format) } LocalType::Sampler => Instruction::type_sampler(id), LocalType::SampledImage { image_type_id } => { Instruction::type_sampled_image(id, image_type_id) } LocalType::BindingArray { base, size } => { let inner_ty = self.get_handle_type_id(base); let scalar_id = self.get_constant_scalar(crate::Literal::U32(size)); Instruction::type_array(id, inner_ty, scalar_id) } LocalType::AccelerationStructure => Instruction::type_acceleration_structure(id), LocalType::RayQuery => Instruction::type_ray_query(id), }; instruction.to_words(&mut self.logical_layout.declarations); } fn write_type_declaration_arena( &mut self, module: &crate::Module, handle: Handle, ) -> Result { let ty = &module.types[handle]; // If it's a type that needs SPIR-V capabilities, request them now. // This needs to happen regardless of the LocalType lookup succeeding, // because some types which map to the same LocalType have different // capability requirements. See https://github.com/gfx-rs/wgpu/issues/5569 self.request_type_capabilities(&ty.inner)?; let id = if let Some(local) = self.localtype_from_inner(&ty.inner) { // This type can be represented as a `LocalType`, so check if we've // already written an instruction for it. If not, do so now, with // `write_type_declaration_local`. match self.lookup_type.entry(LookupType::Local(local)) { // We already have an id for this `LocalType`. Entry::Occupied(e) => *e.get(), // It's a type we haven't seen before. Entry::Vacant(e) => { let id = self.id_gen.next(); e.insert(id); self.write_type_declaration_local(id, local); id } } } else { use spirv::Decoration; let id = self.id_gen.next(); let instruction = match ty.inner { crate::TypeInner::Array { base, size, stride } => { self.decorate(id, Decoration::ArrayStride, &[stride]); let type_id = self.get_handle_type_id(base); match size.resolve(module.to_ctx())? { crate::proc::IndexableLength::Known(length) => { let length_id = self.get_index_constant(length); Instruction::type_array(id, type_id, length_id) } crate::proc::IndexableLength::Dynamic => { Instruction::type_runtime_array(id, type_id) } } } crate::TypeInner::BindingArray { base, size } => { let type_id = self.get_handle_type_id(base); match size.resolve(module.to_ctx())? { crate::proc::IndexableLength::Known(length) => { let length_id = self.get_index_constant(length); Instruction::type_array(id, type_id, length_id) } crate::proc::IndexableLength::Dynamic => { Instruction::type_runtime_array(id, type_id) } } } crate::TypeInner::Struct { ref members, span: _, } => { let mut has_runtime_array = false; let mut member_ids = Vec::with_capacity(members.len()); for (index, member) in members.iter().enumerate() { let member_ty = &module.types[member.ty]; match member_ty.inner { crate::TypeInner::Array { base: _, size: crate::ArraySize::Dynamic, stride: _, } => { has_runtime_array = true; } _ => (), } self.decorate_struct_member(id, index, member, &module.types)?; let member_id = self.get_handle_type_id(member.ty); member_ids.push(member_id); } if has_runtime_array { self.decorate(id, Decoration::Block, &[]); } Instruction::type_struct(id, member_ids.as_slice()) } // These all have TypeLocal representations, so they should have been // handled by `write_type_declaration_local` above. crate::TypeInner::Scalar(_) | crate::TypeInner::Atomic(_) | crate::TypeInner::Vector { .. } | crate::TypeInner::Matrix { .. } | crate::TypeInner::CooperativeMatrix { .. } | crate::TypeInner::Pointer { .. } | crate::TypeInner::ValuePointer { .. } | crate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } | crate::TypeInner::AccelerationStructure { .. } | crate::TypeInner::RayQuery { .. } => unreachable!(), }; instruction.to_words(&mut self.logical_layout.declarations); id }; // Add this handle as a new alias for that type. self.lookup_type.insert(LookupType::Handle(handle), id); if self.flags.contains(WriterFlags::DEBUG) { if let Some(ref name) = ty.name { self.debugs.push(Instruction::name(id, name)); } } Ok(id) } /// Writes a std140 layout compatible type declaration for a type. Returns /// the ID of the declared type, or None if no declaration is required. /// /// This should be called for any type for which there exists a /// [`GlobalVariable`] in the [`Uniform`] address space. If the type already /// adheres to std140 layout rules it will return without declaring any /// types. If the type contains another type which requires a std140 /// compatible type declaration, it will recursively call itself. /// /// When `handle` refers to a [`TypeInner::Matrix`] with 2 rows, the /// declared type will be an `OpTypeStruct` containing an `OpVector` for /// each of the matrix's columns. /// /// When `handle` refers to a [`TypeInner::Array`] whose base type is a /// matrix with 2 rows, this will declare an `OpTypeArray` whose element /// type is the matrix's corresponding std140 compatible type. /// /// When `handle` refers to a [`TypeInner::Struct`] and any of its members /// require a std140 compatible type declaration, this will declare a new /// struct with the following rules: /// * Struct or array members will be declared with their std140 compatible /// type declaration, if one is required. /// * Two-row matrix members will have each of their columns hoisted /// directly into the struct as 2-component vector members. /// * All other members will be declared with their normal type. /// /// Note that this means the Naga IR index of a struct member may not match /// the index in the generated SPIR-V. The mapping can be obtained via /// `Std140TypeInfo::member_indices`. /// /// [`GlobalVariable`]: crate::GlobalVariable /// [`Uniform`]: crate::AddressSpace::Uniform /// [`TypeInner::Matrix`]: crate::TypeInner::Matrix /// [`TypeInner::Array`]: crate::TypeInner::Array /// [`TypeInner::Struct`]: crate::TypeInner::Struct fn write_std140_compat_type_declaration( &mut self, module: &crate::Module, handle: Handle, ) -> Result, Error> { if let Some(std140_type_info) = self.std140_compat_uniform_types.get(&handle) { return Ok(Some(std140_type_info.type_id)); } let type_inner = &module.types[handle].inner; let std140_type_id = match *type_inner { crate::TypeInner::Matrix { columns, rows: rows @ crate::VectorSize::Bi, scalar, } => { let std140_type_id = self.id_gen.next(); let mut member_type_ids: ArrayVec = ArrayVec::new(); let column_type_id = self.get_numeric_type_id(NumericType::Vector { size: rows, scalar }); for column in 0..columns as u32 { member_type_ids.push(column_type_id); self.annotations.push(Instruction::member_decorate( std140_type_id, column, spirv::Decoration::Offset, &[column * rows as u32 * scalar.width as u32], )); if self.flags.contains(WriterFlags::DEBUG) { self.debugs.push(Instruction::member_name( std140_type_id, column, &format!("col{column}"), )); } } Instruction::type_struct(std140_type_id, &member_type_ids) .to_words(&mut self.logical_layout.declarations); self.std140_compat_uniform_types.insert( handle, Std140CompatTypeInfo { type_id: std140_type_id, member_indices: Vec::new(), }, ); Some(std140_type_id) } crate::TypeInner::Array { base, size, stride } => { match self.write_std140_compat_type_declaration(module, base)? { Some(std140_base_type_id) => { let std140_type_id = self.id_gen.next(); self.decorate(std140_type_id, spirv::Decoration::ArrayStride, &[stride]); let instruction = match size.resolve(module.to_ctx())? { crate::proc::IndexableLength::Known(length) => { let length_id = self.get_index_constant(length); Instruction::type_array( std140_type_id, std140_base_type_id, length_id, ) } crate::proc::IndexableLength::Dynamic => { unreachable!() } }; instruction.to_words(&mut self.logical_layout.declarations); self.std140_compat_uniform_types.insert( handle, Std140CompatTypeInfo { type_id: std140_type_id, member_indices: Vec::new(), }, ); Some(std140_type_id) } None => None, } } crate::TypeInner::Struct { ref members, .. } => { let mut needs_std140_type = false; for member in members { match module.types[member.ty].inner { // We don't need to write a std140 type for the matrix itself as // it will be decomposed into the parent struct. As a result, the // struct does need a std140 type, however. crate::TypeInner::Matrix { rows: crate::VectorSize::Bi, .. } => needs_std140_type = true, // If an array member needs a std140 type, because it is an array // (of an array, etc) of `matCx2`s, then the struct also needs // a std140 type which uses the std140 type for this member. crate::TypeInner::Array { .. } if self .write_std140_compat_type_declaration(module, member.ty)? .is_some() => { needs_std140_type = true; } _ => {} } } if needs_std140_type { let std140_type_id = self.id_gen.next(); let mut member_ids = Vec::new(); let mut member_indices = Vec::new(); let mut next_index = 0; for member in members { member_indices.push(next_index); match module.types[member.ty].inner { crate::TypeInner::Matrix { columns, rows: rows @ crate::VectorSize::Bi, scalar, } => { let vector_type_id = self.get_numeric_type_id(NumericType::Vector { size: rows, scalar, }); for column in 0..columns as u32 { self.annotations.push(Instruction::member_decorate( std140_type_id, next_index, spirv::Decoration::Offset, &[member.offset + column * rows as u32 * scalar.width as u32], )); if self.flags.contains(WriterFlags::DEBUG) { if let Some(ref name) = member.name { self.debugs.push(Instruction::member_name( std140_type_id, next_index, &format!("{name}_col{column}"), )); } } member_ids.push(vector_type_id); next_index += 1; } } _ => { let member_id = match self.std140_compat_uniform_types.get(&member.ty) { Some(std140_member_type_info) => { self.annotations.push(Instruction::member_decorate( std140_type_id, next_index, spirv::Decoration::Offset, &[member.offset], )); if self.flags.contains(WriterFlags::DEBUG) { if let Some(ref name) = member.name { self.debugs.push(Instruction::member_name( std140_type_id, next_index, name, )); } } std140_member_type_info.type_id } None => { self.decorate_struct_member( std140_type_id, next_index as usize, member, &module.types, )?; self.get_handle_type_id(member.ty) } }; member_ids.push(member_id); next_index += 1; } } } Instruction::type_struct(std140_type_id, &member_ids) .to_words(&mut self.logical_layout.declarations); self.std140_compat_uniform_types.insert( handle, Std140CompatTypeInfo { type_id: std140_type_id, member_indices, }, ); Some(std140_type_id) } else { None } } _ => None, }; if let Some(std140_type_id) = std140_type_id { if self.flags.contains(WriterFlags::DEBUG) { let name = format!("std140_{:?}", handle.for_debug(&module.types)); self.debugs.push(Instruction::name(std140_type_id, &name)); } } Ok(std140_type_id) } fn request_image_format_capabilities( &mut self, format: spirv::ImageFormat, ) -> Result<(), Error> { use spirv::ImageFormat as If; match format { If::Rg32f | If::Rg16f | If::R11fG11fB10f | If::R16f | If::Rgba16 | If::Rgb10A2 | If::Rg16 | If::Rg8 | If::R16 | If::R8 | If::Rgba16Snorm | If::Rg16Snorm | If::Rg8Snorm | If::R16Snorm | If::R8Snorm | If::Rg32i | If::Rg16i | If::Rg8i | If::R16i | If::R8i | If::Rgb10a2ui | If::Rg32ui | If::Rg16ui | If::Rg8ui | If::R16ui | If::R8ui => self.require_any( "storage image format", &[spirv::Capability::StorageImageExtendedFormats], ), If::R64ui | If::R64i => { self.use_extension("SPV_EXT_shader_image_int64"); self.require_any( "64-bit integer storage image format", &[spirv::Capability::Int64ImageEXT], ) } If::Unknown | If::Rgba32f | If::Rgba16f | If::R32f | If::Rgba8 | If::Rgba8Snorm | If::Rgba32i | If::Rgba16i | If::Rgba8i | If::R32i | If::Rgba32ui | If::Rgba16ui | If::Rgba8ui | If::R32ui => Ok(()), } } pub(super) fn get_index_constant(&mut self, index: Word) -> Word { self.get_constant_scalar(crate::Literal::U32(index)) } pub(super) fn get_constant_scalar_with( &mut self, value: u8, scalar: crate::Scalar, ) -> Result { Ok( self.get_constant_scalar(crate::Literal::new(value, scalar).ok_or( Error::Validation("Unexpected kind and/or width for Literal"), )?), ) } pub(super) fn get_constant_scalar(&mut self, value: crate::Literal) -> Word { let scalar = CachedConstant::Literal(value.into()); if let Some(&id) = self.cached_constants.get(&scalar) { return id; } let id = self.id_gen.next(); self.write_constant_scalar(id, &value, None); self.cached_constants.insert(scalar, id); id } fn write_constant_scalar( &mut self, id: Word, value: &crate::Literal, debug_name: Option<&String>, ) { if self.flags.contains(WriterFlags::DEBUG) { if let Some(name) = debug_name { self.debugs.push(Instruction::name(id, name)); } } let type_id = self.get_numeric_type_id(NumericType::Scalar(value.scalar())); let instruction = match *value { crate::Literal::F64(value) => { let bits = value.to_bits(); Instruction::constant_64bit(type_id, id, bits as u32, (bits >> 32) as u32) } crate::Literal::F32(value) => Instruction::constant_32bit(type_id, id, value.to_bits()), crate::Literal::F16(value) => { let low = value.to_bits(); Instruction::constant_16bit(type_id, id, low as u32) } crate::Literal::U32(value) => Instruction::constant_32bit(type_id, id, value), crate::Literal::I32(value) => Instruction::constant_32bit(type_id, id, value as u32), crate::Literal::U64(value) => { Instruction::constant_64bit(type_id, id, value as u32, (value >> 32) as u32) } crate::Literal::I64(value) => { Instruction::constant_64bit(type_id, id, value as u32, (value >> 32) as u32) } crate::Literal::Bool(true) => Instruction::constant_true(type_id, id), crate::Literal::Bool(false) => Instruction::constant_false(type_id, id), crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { unreachable!("Abstract types should not appear in IR presented to backends"); } }; instruction.to_words(&mut self.logical_layout.declarations); } pub(super) fn get_constant_composite( &mut self, ty: LookupType, constituent_ids: &[Word], ) -> Word { let composite = CachedConstant::Composite { ty, constituent_ids: constituent_ids.to_vec(), }; if let Some(&id) = self.cached_constants.get(&composite) { return id; } let id = self.id_gen.next(); self.write_constant_composite(id, ty, constituent_ids, None); self.cached_constants.insert(composite, id); id } fn write_constant_composite( &mut self, id: Word, ty: LookupType, constituent_ids: &[Word], debug_name: Option<&String>, ) { if self.flags.contains(WriterFlags::DEBUG) { if let Some(name) = debug_name { self.debugs.push(Instruction::name(id, name)); } } let type_id = self.get_type_id(ty); Instruction::constant_composite(type_id, id, constituent_ids) .to_words(&mut self.logical_layout.declarations); } pub(super) fn get_constant_null(&mut self, type_id: Word) -> Word { let null = CachedConstant::ZeroValue(type_id); if let Some(&id) = self.cached_constants.get(&null) { return id; } let id = self.write_constant_null(type_id); self.cached_constants.insert(null, id); id } pub(super) fn write_constant_null(&mut self, type_id: Word) -> Word { let null_id = self.id_gen.next(); Instruction::constant_null(type_id, null_id) .to_words(&mut self.logical_layout.declarations); null_id } fn write_constant_expr( &mut self, handle: Handle, ir_module: &crate::Module, mod_info: &ModuleInfo, ) -> Result { let id = match ir_module.global_expressions[handle] { crate::Expression::Literal(literal) => self.get_constant_scalar(literal), crate::Expression::Constant(constant) => { let constant = &ir_module.constants[constant]; self.constant_ids[constant.init] } crate::Expression::ZeroValue(ty) => { let type_id = self.get_handle_type_id(ty); self.get_constant_null(type_id) } crate::Expression::Compose { ty, ref components } => { let component_ids: Vec<_> = crate::proc::flatten_compose( ty, components, &ir_module.global_expressions, &ir_module.types, ) .map(|component| self.constant_ids[component]) .collect(); self.get_constant_composite(LookupType::Handle(ty), component_ids.as_slice()) } crate::Expression::Splat { size, value } => { let value_id = self.constant_ids[value]; let component_ids = &[value_id; 4][..size as usize]; let ty = self.get_expression_lookup_type(&mod_info[handle]); self.get_constant_composite(ty, component_ids) } _ => { return Err(Error::Override); } }; self.constant_ids[handle] = id; Ok(id) } pub(super) fn write_control_barrier( &mut self, flags: crate::Barrier, body: &mut Vec, ) { let memory_scope = if flags.contains(crate::Barrier::STORAGE) { spirv::Scope::Device } else if flags.contains(crate::Barrier::SUB_GROUP) { spirv::Scope::Subgroup } else { spirv::Scope::Workgroup }; let mut semantics = spirv::MemorySemantics::ACQUIRE_RELEASE; semantics.set( spirv::MemorySemantics::UNIFORM_MEMORY, flags.contains(crate::Barrier::STORAGE), ); semantics.set( spirv::MemorySemantics::WORKGROUP_MEMORY, flags.contains(crate::Barrier::WORK_GROUP), ); semantics.set( spirv::MemorySemantics::SUBGROUP_MEMORY, flags.contains(crate::Barrier::SUB_GROUP), ); semantics.set( spirv::MemorySemantics::IMAGE_MEMORY, flags.contains(crate::Barrier::TEXTURE), ); let exec_scope_id = if flags.contains(crate::Barrier::SUB_GROUP) { self.get_index_constant(spirv::Scope::Subgroup as u32) } else { self.get_index_constant(spirv::Scope::Workgroup as u32) }; let mem_scope_id = self.get_index_constant(memory_scope as u32); let semantics_id = self.get_index_constant(semantics.bits()); body.push(Instruction::control_barrier( exec_scope_id, mem_scope_id, semantics_id, )); } pub(super) fn write_memory_barrier(&mut self, flags: crate::Barrier, block: &mut Block) { let mut semantics = spirv::MemorySemantics::ACQUIRE_RELEASE; semantics.set( spirv::MemorySemantics::UNIFORM_MEMORY, flags.contains(crate::Barrier::STORAGE), ); semantics.set( spirv::MemorySemantics::WORKGROUP_MEMORY, flags.contains(crate::Barrier::WORK_GROUP), ); semantics.set( spirv::MemorySemantics::SUBGROUP_MEMORY, flags.contains(crate::Barrier::SUB_GROUP), ); semantics.set( spirv::MemorySemantics::IMAGE_MEMORY, flags.contains(crate::Barrier::TEXTURE), ); let mem_scope_id = if flags.contains(crate::Barrier::STORAGE) { self.get_index_constant(spirv::Scope::Device as u32) } else if flags.contains(crate::Barrier::SUB_GROUP) { self.get_index_constant(spirv::Scope::Subgroup as u32) } else { self.get_index_constant(spirv::Scope::Workgroup as u32) }; let semantics_id = self.get_index_constant(semantics.bits()); block .body .push(Instruction::memory_barrier(mem_scope_id, semantics_id)); } fn generate_workgroup_vars_init_block( &mut self, entry_id: Word, ir_module: &crate::Module, info: &FunctionInfo, local_invocation_index: Option, interface: &mut FunctionInterface, function: &mut Function, ) -> Option { let body = ir_module .global_variables .iter() .filter(|&(handle, var)| { let task_exception = (var.space == crate::AddressSpace::TaskPayload) && interface.stage == crate::ShaderStage::Task; !info[handle].is_empty() && (var.space == crate::AddressSpace::WorkGroup || task_exception) }) .map(|(handle, var)| { // It's safe to use `var_id` here, not `access_id`, because only // variables in the `Uniform` and `StorageBuffer` address spaces // get wrapped, and we're initializing `WorkGroup` variables. let var_id = self.global_variables[handle].var_id; let var_type_id = self.get_handle_type_id(var.ty); let init_word = self.get_constant_null(var_type_id); Instruction::store(var_id, init_word, None) }) .collect::>(); if body.is_empty() { return None; } let mut pre_if_block = Block::new(entry_id); let local_invocation_index = if let Some(local_invocation_index) = local_invocation_index { local_invocation_index } else { let varying_id = self.id_gen.next(); let class = spirv::StorageClass::Input; let u32_ty_id = self.get_u32_type_id(); let pointer_type_id = self.get_pointer_type_id(u32_ty_id, class); Instruction::variable(pointer_type_id, varying_id, class, None) .to_words(&mut self.logical_layout.declarations); self.decorate( varying_id, spirv::Decoration::BuiltIn, &[spirv::BuiltIn::LocalInvocationIndex as u32], ); interface.varying_ids.push(varying_id); let id = self.id_gen.next(); pre_if_block .body .push(Instruction::load(u32_ty_id, id, varying_id, None)); id }; let zero_id = self.get_constant_scalar(crate::Literal::U32(0)); let eq_id = self.id_gen.next(); pre_if_block.body.push(Instruction::binary( spirv::Op::IEqual, self.get_bool_type_id(), eq_id, local_invocation_index, zero_id, )); let merge_id = self.id_gen.next(); pre_if_block.body.push(Instruction::selection_merge( merge_id, spirv::SelectionControl::NONE, )); let accept_id = self.id_gen.next(); function.consume( pre_if_block, Instruction::branch_conditional(eq_id, accept_id, merge_id), ); let accept_block = Block { label_id: accept_id, body, }; function.consume(accept_block, Instruction::branch(merge_id)); let mut post_if_block = Block::new(merge_id); self.write_control_barrier(crate::Barrier::WORK_GROUP, &mut post_if_block.body); let next_id = self.id_gen.next(); function.consume(post_if_block, Instruction::branch(next_id)); Some(next_id) } /// Generate an `OpVariable` for one value in an [`EntryPoint`]'s IO interface. /// /// The [`Binding`]s of the arguments and result of an [`EntryPoint`]'s /// [`Function`] describe a SPIR-V shader interface. In SPIR-V, the /// interface is represented by global variables in the `Input` and `Output` /// storage classes, with decorations indicating which builtin or location /// each variable corresponds to. /// /// This function emits a single global `OpVariable` for a single value from /// the interface, and adds appropriate decorations to indicate which /// builtin or location it represents, how it should be interpolated, and so /// on. The `class` argument gives the variable's SPIR-V storage class, /// which should be either [`Input`] or [`Output`]. /// /// [`Binding`]: crate::Binding /// [`Function`]: crate::Function /// [`EntryPoint`]: crate::EntryPoint /// [`Input`]: spirv::StorageClass::Input /// [`Output`]: spirv::StorageClass::Output fn write_varying( &mut self, ir_module: &crate::Module, stage: crate::ShaderStage, class: spirv::StorageClass, debug_name: Option<&str>, ty: Handle, binding: &crate::Binding, ) -> Result { let id = self.id_gen.next(); let ty_inner = &ir_module.types[ty].inner; let needs_polyfill = self.needs_f16_polyfill(ty_inner); let pointer_type_id = if needs_polyfill { let f32_value_local = super::f16_polyfill::F16IoPolyfill::create_polyfill_type(ty_inner) .expect("needs_polyfill returned true but create_polyfill_type returned None"); let f32_type_id = self.get_localtype_id(f32_value_local); let ptr_id = self.get_pointer_type_id(f32_type_id, class); self.io_f16_polyfills.register_io_var(id, f32_type_id); ptr_id } else { self.get_handle_pointer_type_id(ty, class) }; Instruction::variable(pointer_type_id, id, class, None) .to_words(&mut self.logical_layout.declarations); if self .flags .contains(WriterFlags::DEBUG | WriterFlags::LABEL_VARYINGS) { if let Some(name) = debug_name { self.debugs.push(Instruction::name(id, name)); } } let binding = self.map_binding(ir_module, stage, class, ty, binding)?; self.write_binding(id, binding); Ok(id) } pub fn write_binding(&mut self, id: Word, binding: BindingDecorations) { match binding { BindingDecorations::None => (), BindingDecorations::BuiltIn(bi, others) => { self.decorate(id, spirv::Decoration::BuiltIn, &[bi as u32]); for other in others { self.decorate(id, other, &[]); } } BindingDecorations::Location { location, others, blend_src, } => { self.decorate(id, spirv::Decoration::Location, &[location]); for other in others { self.decorate(id, other, &[]); } if let Some(blend_src) = blend_src { self.decorate(id, spirv::Decoration::Index, &[blend_src]); } } } } pub fn write_binding_struct_member( &mut self, struct_id: Word, member_idx: Word, binding_info: BindingDecorations, ) { match binding_info { BindingDecorations::None => (), BindingDecorations::BuiltIn(bi, others) => { self.annotations.push(Instruction::member_decorate( struct_id, member_idx, spirv::Decoration::BuiltIn, &[bi as Word], )); for other in others { self.annotations.push(Instruction::member_decorate( struct_id, member_idx, other, &[], )); } } BindingDecorations::Location { location, others, blend_src, } => { self.annotations.push(Instruction::member_decorate( struct_id, member_idx, spirv::Decoration::Location, &[location], )); for other in others { self.annotations.push(Instruction::member_decorate( struct_id, member_idx, other, &[], )); } if let Some(blend_src) = blend_src { self.annotations.push(Instruction::member_decorate( struct_id, member_idx, spirv::Decoration::Index, &[blend_src], )); } } } } pub fn map_binding( &mut self, ir_module: &crate::Module, stage: crate::ShaderStage, class: spirv::StorageClass, ty: Handle, binding: &crate::Binding, ) -> Result { use spirv::BuiltIn; use spirv::Decoration; match *binding { crate::Binding::Location { location, interpolation, sampling, blend_src, per_primitive, } => { let mut others = ArrayVec::new(); let no_decorations = // VUID-StandaloneSpirv-Flat-06202 // > The Flat, NoPerspective, Sample, and Centroid decorations // > must not be used on variables with the Input storage class in a vertex shader (class == spirv::StorageClass::Input && stage == crate::ShaderStage::Vertex) || // VUID-StandaloneSpirv-Flat-06201 // > The Flat, NoPerspective, Sample, and Centroid decorations // > must not be used on variables with the Output storage class in a fragment shader (class == spirv::StorageClass::Output && stage == crate::ShaderStage::Fragment); if !no_decorations { match interpolation { // Perspective-correct interpolation is the default in SPIR-V. None | Some(crate::Interpolation::Perspective) => (), Some(crate::Interpolation::Flat) => { others.push(Decoration::Flat); } Some(crate::Interpolation::Linear) => { others.push(Decoration::NoPerspective); } Some(crate::Interpolation::PerVertex) => { others.push(Decoration::PerVertexKHR); self.require_any( "`per_vertex` interpolation", &[spirv::Capability::FragmentBarycentricKHR], )?; self.use_extension("SPV_KHR_fragment_shader_barycentric"); } } match sampling { // Center sampling is the default in SPIR-V. None | Some( crate::Sampling::Center | crate::Sampling::First | crate::Sampling::Either, ) => (), Some(crate::Sampling::Centroid) => { others.push(Decoration::Centroid); } Some(crate::Sampling::Sample) => { self.require_any( "per-sample interpolation", &[spirv::Capability::SampleRateShading], )?; others.push(Decoration::Sample); } } } if per_primitive && stage == crate::ShaderStage::Fragment { others.push(Decoration::PerPrimitiveEXT); } Ok(BindingDecorations::Location { location, others, blend_src, }) } crate::Binding::BuiltIn(built_in) => { use crate::BuiltIn as Bi; let mut others = ArrayVec::new(); let built_in = match built_in { Bi::Position { invariant } => { if invariant { others.push(Decoration::Invariant); } if class == spirv::StorageClass::Output { BuiltIn::Position } else { BuiltIn::FragCoord } } Bi::ViewIndex => { self.require_any("`view_index` built-in", &[spirv::Capability::MultiView])?; BuiltIn::ViewIndex } // vertex Bi::BaseInstance => BuiltIn::BaseInstance, Bi::BaseVertex => BuiltIn::BaseVertex, Bi::ClipDistances => { self.require_any( "`clip_distances` built-in", &[spirv::Capability::ClipDistance], )?; BuiltIn::ClipDistance } Bi::CullDistance => { self.require_any( "`cull_distance` built-in", &[spirv::Capability::CullDistance], )?; BuiltIn::CullDistance } Bi::InstanceIndex => BuiltIn::InstanceIndex, Bi::PointSize => BuiltIn::PointSize, Bi::VertexIndex => BuiltIn::VertexIndex, Bi::DrawIndex => { self.use_extension("SPV_KHR_shader_draw_parameters"); self.require_any( "`draw_index built-in", &[spirv::Capability::DrawParameters], )?; BuiltIn::DrawIndex } // fragment Bi::FragDepth => BuiltIn::FragDepth, Bi::PointCoord => BuiltIn::PointCoord, Bi::FrontFacing => BuiltIn::FrontFacing, Bi::PrimitiveIndex => { // Geometry shader capability is required for primitive index self.require_any( "`primitive_index` built-in", &[spirv::Capability::Geometry], )?; if stage == crate::ShaderStage::Mesh { others.push(Decoration::PerPrimitiveEXT); } BuiltIn::PrimitiveId } Bi::Barycentric { perspective } => { self.require_any( "`barycentric` built-in", &[spirv::Capability::FragmentBarycentricKHR], )?; self.use_extension("SPV_KHR_fragment_shader_barycentric"); if perspective { BuiltIn::BaryCoordKHR } else { BuiltIn::BaryCoordNoPerspKHR } } Bi::SampleIndex => { self.require_any( "`sample_index` built-in", &[spirv::Capability::SampleRateShading], )?; BuiltIn::SampleId } Bi::SampleMask => BuiltIn::SampleMask, // compute Bi::GlobalInvocationId => BuiltIn::GlobalInvocationId, Bi::LocalInvocationId => BuiltIn::LocalInvocationId, Bi::LocalInvocationIndex => BuiltIn::LocalInvocationIndex, Bi::WorkGroupId => BuiltIn::WorkgroupId, Bi::WorkGroupSize => BuiltIn::WorkgroupSize, Bi::NumWorkGroups => BuiltIn::NumWorkgroups, // Subgroup Bi::NumSubgroups => { self.require_any( "`num_subgroups` built-in", &[spirv::Capability::GroupNonUniform], )?; BuiltIn::NumSubgroups } Bi::SubgroupId => { self.require_any( "`subgroup_id` built-in", &[spirv::Capability::GroupNonUniform], )?; BuiltIn::SubgroupId } Bi::SubgroupSize => { self.require_any( "`subgroup_size` built-in", &[ spirv::Capability::GroupNonUniform, spirv::Capability::SubgroupBallotKHR, ], )?; BuiltIn::SubgroupSize } Bi::SubgroupInvocationId => { self.require_any( "`subgroup_invocation_id` built-in", &[ spirv::Capability::GroupNonUniform, spirv::Capability::SubgroupBallotKHR, ], )?; BuiltIn::SubgroupLocalInvocationId } Bi::CullPrimitive => { others.push(Decoration::PerPrimitiveEXT); BuiltIn::CullPrimitiveEXT } Bi::PointIndex => BuiltIn::PrimitivePointIndicesEXT, Bi::LineIndices => BuiltIn::PrimitiveLineIndicesEXT, Bi::TriangleIndices => BuiltIn::PrimitiveTriangleIndicesEXT, // No decoration, this EmitMeshTasksEXT is called at function return Bi::MeshTaskSize => return Ok(BindingDecorations::None), // These aren't normal builtins and don't occur in function output Bi::VertexCount | Bi::Vertices | Bi::PrimitiveCount | Bi::Primitives => { unreachable!() } Bi::RayInvocationId | Bi::NumRayInvocations | Bi::InstanceCustomData | Bi::GeometryIndex | Bi::WorldRayOrigin | Bi::WorldRayDirection | Bi::ObjectRayOrigin | Bi::ObjectRayDirection | Bi::RayTmin | Bi::RayTCurrentMax | Bi::ObjectToWorld | Bi::WorldToObject | Bi::HitKind => unreachable!(), }; use crate::ScalarKind as Sk; // Per the Vulkan spec, `VUID-StandaloneSpirv-Flat-04744`: // // > Any variable with integer or double-precision floating- // > point type and with Input storage class in a fragment // > shader, must be decorated Flat if class == spirv::StorageClass::Input && stage == crate::ShaderStage::Fragment { let is_flat = match ir_module.types[ty].inner { crate::TypeInner::Scalar(scalar) | crate::TypeInner::Vector { scalar, .. } => match scalar.kind { Sk::Uint | Sk::Sint | Sk::Bool => true, Sk::Float => false, Sk::AbstractInt | Sk::AbstractFloat => { return Err(Error::Validation( "Abstract types should not appear in IR presented to backends", )) } }, _ => false, }; if is_flat { others.push(Decoration::Flat); } } Ok(BindingDecorations::BuiltIn(built_in, others)) } } } /// Load an IO variable, converting from `f32` to `f16` if polyfill is active. /// Returns the id of the loaded value matching `target_type_id`. pub(super) fn load_io_with_f16_polyfill( &mut self, body: &mut Vec, varying_id: Word, target_type_id: Word, ) -> Word { let tmp = self.id_gen.next(); if let Some(f32_ty) = self.io_f16_polyfills.get_f32_io_type(varying_id) { body.push(Instruction::load(f32_ty, tmp, varying_id, None)); let converted = self.id_gen.next(); super::f16_polyfill::F16IoPolyfill::emit_f32_to_f16_conversion( tmp, target_type_id, converted, body, ); converted } else { body.push(Instruction::load(target_type_id, tmp, varying_id, None)); tmp } } /// Store an IO variable, converting from `f16` to `f32` if polyfill is active. pub(super) fn store_io_with_f16_polyfill( &mut self, body: &mut Vec, varying_id: Word, value_id: Word, ) { if let Some(f32_ty) = self.io_f16_polyfills.get_f32_io_type(varying_id) { let converted = self.id_gen.next(); super::f16_polyfill::F16IoPolyfill::emit_f16_to_f32_conversion( value_id, f32_ty, converted, body, ); body.push(Instruction::store(varying_id, converted, None)); } else { body.push(Instruction::store(varying_id, value_id, None)); } } fn write_global_variable( &mut self, ir_module: &crate::Module, global_variable: &crate::GlobalVariable, ) -> Result { use spirv::Decoration; let id = self.id_gen.next(); let class = map_storage_class(global_variable.space); //self.check(class.required_capabilities())?; if global_variable .memory_decorations .contains(crate::MemoryDecorations::COHERENT) { self.decorate(id, Decoration::Coherent, &[]); } if global_variable .memory_decorations .contains(crate::MemoryDecorations::VOLATILE) { self.decorate(id, Decoration::Volatile, &[]); } if self.flags.contains(WriterFlags::DEBUG) { if let Some(ref name) = global_variable.name { self.debugs.push(Instruction::name(id, name)); } } let storage_access = match global_variable.space { crate::AddressSpace::Storage { access } => Some(access), _ => match ir_module.types[global_variable.ty].inner { crate::TypeInner::Image { class: crate::ImageClass::Storage { access, .. }, .. } => Some(access), _ => None, }, }; if let Some(storage_access) = storage_access { if !storage_access.contains(crate::StorageAccess::LOAD) { self.decorate(id, Decoration::NonReadable, &[]); } if !storage_access.contains(crate::StorageAccess::STORE) { self.decorate(id, Decoration::NonWritable, &[]); } } // Note: we should be able to substitute `binding_array`, // but there is still code that tries to register the pre-substituted type, // and it is failing on 0. let mut substitute_inner_type_lookup = None; if let Some(ref res_binding) = global_variable.binding { let bind_target = self.resolve_resource_binding(res_binding)?; self.decorate(id, Decoration::DescriptorSet, &[bind_target.descriptor_set]); self.decorate(id, Decoration::Binding, &[bind_target.binding]); if let Some(remapped_binding_array_size) = bind_target.binding_array_size { if let crate::TypeInner::BindingArray { base, .. } = ir_module.types[global_variable.ty].inner { let binding_array_type_id = self.get_type_id(LookupType::Local(LocalType::BindingArray { base, size: remapped_binding_array_size, })); substitute_inner_type_lookup = Some(LookupType::Local(LocalType::Pointer { base: binding_array_type_id, class, })); } } }; let init_word = global_variable .init .map(|constant| self.constant_ids[constant]); let inner_type_id = self.get_type_id( substitute_inner_type_lookup.unwrap_or(LookupType::Handle(global_variable.ty)), ); // generate the wrapping structure if needed let pointer_type_id = if global_needs_wrapper(ir_module, global_variable) { let wrapper_type_id = self.id_gen.next(); self.decorate(wrapper_type_id, Decoration::Block, &[]); match self.std140_compat_uniform_types.get(&global_variable.ty) { Some(std140_type_info) if global_variable.space == crate::AddressSpace::Uniform => { self.annotations.push(Instruction::member_decorate( wrapper_type_id, 0, Decoration::Offset, &[0], )); Instruction::type_struct(wrapper_type_id, &[std140_type_info.type_id]) .to_words(&mut self.logical_layout.declarations); } _ => { let member = crate::StructMember { name: None, ty: global_variable.ty, binding: None, offset: 0, }; self.decorate_struct_member(wrapper_type_id, 0, &member, &ir_module.types)?; Instruction::type_struct(wrapper_type_id, &[inner_type_id]) .to_words(&mut self.logical_layout.declarations); } } let pointer_type_id = self.id_gen.next(); Instruction::type_pointer(pointer_type_id, class, wrapper_type_id) .to_words(&mut self.logical_layout.declarations); pointer_type_id } else { // This is a global variable in the Storage address space. The only // way it could have `global_needs_wrapper() == false` is if it has // a runtime-sized or binding array. // Runtime-sized arrays were decorated when iterating through struct content. // Now binding arrays require Block decorating. if let crate::AddressSpace::Storage { .. } = global_variable.space { match ir_module.types[global_variable.ty].inner { crate::TypeInner::BindingArray { base, .. } => { let ty = &ir_module.types[base]; let mut should_decorate = true; // Check if the type has a runtime array. // A normal runtime array gets validated out, // so only structs can be with runtime arrays if let crate::TypeInner::Struct { ref members, .. } = ty.inner { // only the last member in a struct can be dynamically sized if let Some(last_member) = members.last() { if let &crate::TypeInner::Array { size: crate::ArraySize::Dynamic, .. } = &ir_module.types[last_member.ty].inner { should_decorate = false; } } } if should_decorate { let decorated_id = self.get_handle_type_id(base); self.decorate(decorated_id, Decoration::Block, &[]); } } _ => (), }; } if substitute_inner_type_lookup.is_some() { inner_type_id } else { self.get_handle_pointer_type_id(global_variable.ty, class) } }; let init_word = match (global_variable.space, self.zero_initialize_workgroup_memory) { (crate::AddressSpace::Private, _) | (crate::AddressSpace::WorkGroup, super::ZeroInitializeWorkgroupMemoryMode::Native) => { init_word.or_else(|| Some(self.get_constant_null(inner_type_id))) } _ => init_word, }; Instruction::variable(pointer_type_id, id, class, init_word) .to_words(&mut self.logical_layout.declarations); Ok(id) } /// Write the necessary decorations for a struct member. /// /// Emit decorations for the `index`'th member of the struct type /// designated by `struct_id`, described by `member`. fn decorate_struct_member( &mut self, struct_id: Word, index: usize, member: &crate::StructMember, arena: &UniqueArena, ) -> Result<(), Error> { use spirv::Decoration; self.annotations.push(Instruction::member_decorate( struct_id, index as u32, Decoration::Offset, &[member.offset], )); if self.flags.contains(WriterFlags::DEBUG) { if let Some(ref name) = member.name { self.debugs .push(Instruction::member_name(struct_id, index as u32, name)); } } // Matrices and (potentially nested) arrays of matrices both require decorations, // so "see through" any arrays to determine if they're needed. let mut member_array_subty_inner = &arena[member.ty].inner; while let crate::TypeInner::Array { base, .. } = *member_array_subty_inner { member_array_subty_inner = &arena[base].inner; } if let crate::TypeInner::Matrix { columns: _, rows, scalar, } = *member_array_subty_inner { let byte_stride = Alignment::from(rows) * scalar.width as u32; self.annotations.push(Instruction::member_decorate( struct_id, index as u32, Decoration::ColMajor, &[], )); self.annotations.push(Instruction::member_decorate( struct_id, index as u32, Decoration::MatrixStride, &[byte_stride], )); } Ok(()) } pub(super) fn get_function_type(&mut self, lookup_function_type: LookupFunctionType) -> Word { match self .lookup_function_type .entry(lookup_function_type.clone()) { Entry::Occupied(e) => *e.get(), Entry::Vacant(_) => { let id = self.id_gen.next(); let instruction = Instruction::type_function( id, lookup_function_type.return_type_id, &lookup_function_type.parameter_type_ids, ); instruction.to_words(&mut self.logical_layout.declarations); self.lookup_function_type.insert(lookup_function_type, id); id } } } const fn write_physical_layout(&mut self) { self.physical_layout.bound = self.id_gen.0 + 1; } fn write_logical_layout( &mut self, ir_module: &crate::Module, mod_info: &ModuleInfo, ep_index: Option, debug_info: &Option, ) -> Result<(), Error> { fn has_view_index_check( ir_module: &crate::Module, binding: Option<&crate::Binding>, ty: Handle, ) -> bool { match ir_module.types[ty].inner { crate::TypeInner::Struct { ref members, .. } => members.iter().any(|member| { has_view_index_check(ir_module, member.binding.as_ref(), member.ty) }), _ => binding == Some(&crate::Binding::BuiltIn(crate::BuiltIn::ViewIndex)), } } let has_storage_buffers = ir_module .global_variables .iter() .any(|(_, var)| match var.space { crate::AddressSpace::Storage { .. } => true, _ => false, }); let has_view_index = ir_module .entry_points .iter() .flat_map(|entry| entry.function.arguments.iter()) .any(|arg| has_view_index_check(ir_module, arg.binding.as_ref(), arg.ty)); let mut has_ray_query = ir_module.special_types.ray_desc.is_some() | ir_module.special_types.ray_intersection.is_some(); let has_vertex_return = ir_module.special_types.ray_vertex_return.is_some(); for (_, &crate::Type { ref inner, .. }) in ir_module.types.iter() { // spirv does not know whether these have vertex return - that is done by us if let &crate::TypeInner::AccelerationStructure { .. } | &crate::TypeInner::RayQuery { .. } = inner { has_ray_query = true } } if self.physical_layout.version < 0x10300 && has_storage_buffers { // enable the storage buffer class on < SPV-1.3 Instruction::extension("SPV_KHR_storage_buffer_storage_class") .to_words(&mut self.logical_layout.extensions); } if has_view_index { Instruction::extension("SPV_KHR_multiview") .to_words(&mut self.logical_layout.extensions) } if has_ray_query { Instruction::extension("SPV_KHR_ray_query") .to_words(&mut self.logical_layout.extensions) } if has_vertex_return { Instruction::extension("SPV_KHR_ray_tracing_position_fetch") .to_words(&mut self.logical_layout.extensions); } if ir_module.uses_mesh_shaders() { self.use_extension("SPV_EXT_mesh_shader"); self.require_any("Mesh Shaders", &[spirv::Capability::MeshShadingEXT])?; let lang_version = self.lang_version(); if lang_version.0 <= 1 && lang_version.1 < 4 { return Err(Error::SpirvVersionTooLow(1, 4)); } } Instruction::type_void(self.void_type).to_words(&mut self.logical_layout.declarations); Instruction::ext_inst_import(self.gl450_ext_inst_id, "GLSL.std.450") .to_words(&mut self.logical_layout.ext_inst_imports); let mut debug_info_inner = None; if self.flags.contains(WriterFlags::DEBUG) { if let Some(debug_info) = debug_info.as_ref() { let source_file_id = self.id_gen.next(); self.debugs .push(Instruction::string(debug_info.file_name, source_file_id)); debug_info_inner = Some(DebugInfoInner { source_code: debug_info.source_code, source_file_id, }); self.debugs.append(&mut Instruction::source_auto_continued( debug_info.language, 0, &debug_info_inner, )); } } // write all types for (handle, _) in ir_module.types.iter() { self.write_type_declaration_arena(ir_module, handle)?; } // write std140 layout compatible types required by uniforms for (_, var) in ir_module.global_variables.iter() { if var.space == crate::AddressSpace::Uniform { self.write_std140_compat_type_declaration(ir_module, var.ty)?; } } // write all const-expressions as constants self.constant_ids .resize(ir_module.global_expressions.len(), 0); for (handle, _) in ir_module.global_expressions.iter() { self.write_constant_expr(handle, ir_module, mod_info)?; } debug_assert!(self.constant_ids.iter().all(|&id| id != 0)); // write the name of constants on their respective const-expression initializer if self.flags.contains(WriterFlags::DEBUG) { for (_, constant) in ir_module.constants.iter() { if let Some(ref name) = constant.name { let id = self.constant_ids[constant.init]; self.debugs.push(Instruction::name(id, name)); } } } // write all global variables for (handle, var) in ir_module.global_variables.iter() { // If a single entry point was specified, only write `OpVariable` instructions // for the globals it actually uses. Emit dummies for the others, // to preserve the indices in `global_variables`. let gvar = match ep_index { Some(index) if mod_info.get_entry_point(index)[handle].is_empty() => { GlobalVariable::dummy() } _ => { let id = self.write_global_variable(ir_module, var)?; GlobalVariable::new(id) } }; self.global_variables.insert(handle, gvar); } // write all functions for (handle, ir_function) in ir_module.functions.iter() { let info = &mod_info[handle]; if let Some(index) = ep_index { let ep_info = mod_info.get_entry_point(index); // If this function uses globals that we omitted from the SPIR-V // because the entry point and its callees didn't use them, // then we must skip it. if !ep_info.dominates_global_use(info) { log::debug!("Skip function {:?}", ir_function.name); continue; } // Skip functions that that are not compatible with this entry point's stage. // // When validation is enabled, it rejects modules whose entry points try to call // incompatible functions, so if we got this far, then any functions incompatible // with our selected entry point must not be used. // // When validation is disabled, `fun_info.available_stages` is always just // `ShaderStages::all()`, so this will write all functions in the module, and // the downstream GLSL compiler will catch any problems. if !info.available_stages.contains(ep_info.available_stages) { continue; } } let id = self.write_function(ir_function, info, ir_module, None, &debug_info_inner)?; self.lookup_function.insert(handle, id); } // write all or one entry points for (index, ir_ep) in ir_module.entry_points.iter().enumerate() { if ep_index.is_some() && ep_index != Some(index) { continue; } let info = mod_info.get_entry_point(index); let ep_instruction = self.write_entry_point(ir_ep, info, ir_module, &debug_info_inner)?; ep_instruction.to_words(&mut self.logical_layout.entry_points); } for capability in self.capabilities_used.iter() { Instruction::capability(*capability).to_words(&mut self.logical_layout.capabilities); } for extension in self.extensions_used.iter() { Instruction::extension(extension).to_words(&mut self.logical_layout.extensions); } if ir_module.entry_points.is_empty() { // SPIR-V doesn't like modules without entry points Instruction::capability(spirv::Capability::Linkage) .to_words(&mut self.logical_layout.capabilities); } let addressing_model = spirv::AddressingModel::Logical; let memory_model = if self .capabilities_used .contains(&spirv::Capability::VulkanMemoryModel) { spirv::MemoryModel::Vulkan } else { spirv::MemoryModel::GLSL450 }; //self.check(addressing_model.required_capabilities())?; //self.check(memory_model.required_capabilities())?; Instruction::memory_model(addressing_model, memory_model) .to_words(&mut self.logical_layout.memory_model); for debug_string in self.debug_strings.iter() { debug_string.to_words(&mut self.logical_layout.debugs); } if self.flags.contains(WriterFlags::DEBUG) { for debug in self.debugs.iter() { debug.to_words(&mut self.logical_layout.debugs); } } for annotation in self.annotations.iter() { annotation.to_words(&mut self.logical_layout.annotations); } Ok(()) } pub fn write( &mut self, ir_module: &crate::Module, info: &ModuleInfo, pipeline_options: Option<&PipelineOptions>, debug_info: &Option, words: &mut Vec, ) -> Result<(), Error> { self.reset(); // Try to find the entry point and corresponding index let ep_index = match pipeline_options { Some(po) => { let index = ir_module .entry_points .iter() .position(|ep| po.shader_stage == ep.stage && po.entry_point == ep.name) .ok_or(Error::EntryPointNotFound)?; Some(index) } None => None, }; self.write_logical_layout(ir_module, info, ep_index, debug_info)?; self.write_physical_layout(); self.physical_layout.in_words(words); self.logical_layout.in_words(words); Ok(()) } /// Return the set of capabilities the last module written used. pub const fn get_capabilities_used(&self) -> &crate::FastIndexSet { &self.capabilities_used } pub fn decorate_non_uniform_binding_array_access(&mut self, id: Word) -> Result<(), Error> { self.require_any("NonUniformEXT", &[spirv::Capability::ShaderNonUniform])?; self.use_extension("SPV_EXT_descriptor_indexing"); self.decorate(id, spirv::Decoration::NonUniform, &[]); Ok(()) } pub(super) fn needs_f16_polyfill(&self, ty_inner: &crate::TypeInner) -> bool { self.io_f16_polyfills.needs_polyfill(ty_inner) } pub(super) fn write_debug_printf( &mut self, block: &mut Block, string: &str, format_params: &[Word], ) { if self.debug_printf.is_none() { self.use_extension("SPV_KHR_non_semantic_info"); let import_id = self.id_gen.next(); Instruction::ext_inst_import(import_id, "NonSemantic.DebugPrintf") .to_words(&mut self.logical_layout.ext_inst_imports); self.debug_printf = Some(import_id) } let import_id = self.debug_printf.unwrap(); let string_id = self.id_gen.next(); self.debug_strings .push(Instruction::string(string, string_id)); let mut operands = Vec::with_capacity(1 + format_params.len()); operands.push(string_id); operands.extend(format_params.iter()); let print_id = self.id_gen.next(); block.body.push(Instruction::ext_inst( import_id, 1, self.void_type, print_id, &operands, )); } } #[test] fn test_write_physical_layout() { let mut writer = Writer::new(&Options::default()).unwrap(); assert_eq!(writer.physical_layout.bound, 0); writer.write_physical_layout(); assert_eq!(writer.physical_layout.bound, 3); } ================================================ FILE: naga/src/back/wgsl/mod.rs ================================================ /*! Backend for [WGSL][wgsl] (WebGPU Shading Language). [wgsl]: https://gpuweb.github.io/gpuweb/wgsl.html */ mod polyfill; mod writer; use alloc::format; use alloc::string::String; use thiserror::Error; pub use writer::{Writer, WriterFlags}; use crate::common::wgsl; #[derive(Error, Debug)] pub enum Error { #[error(transparent)] FmtError(#[from] core::fmt::Error), #[error("{0}")] Custom(String), #[error("{0}")] Unimplemented(String), // TODO: Error used only during development #[error("Unsupported relational function: {0:?}")] UnsupportedRelationalFunction(crate::RelationalFunction), #[error("Unsupported {kind}: {value}")] Unsupported { /// What kind of unsupported thing this is: interpolation, builtin, etc. kind: &'static str, /// The debug form of the Naga IR value that this backend can't express. value: String, }, } impl Error { /// Produce an [`Unsupported`] error for `value`. /// /// [`Unsupported`]: Error::Unsupported fn unsupported(kind: &'static str, value: T) -> Error { Error::Unsupported { kind, value: format!("{value:?}"), } } } trait ToWgslIfImplemented { fn to_wgsl_if_implemented(self) -> Result<&'static str, Error>; } impl ToWgslIfImplemented for T where T: wgsl::TryToWgsl + core::fmt::Debug + Copy, { fn to_wgsl_if_implemented(self) -> Result<&'static str, Error> { self.try_to_wgsl() .ok_or_else(|| Error::unsupported(T::DESCRIPTION, self)) } } pub fn write_string( module: &crate::Module, info: &crate::valid::ModuleInfo, flags: WriterFlags, ) -> Result { let mut w = Writer::new(String::new(), flags); w.write(module, info)?; let output = w.finish(); Ok(output) } impl crate::AtomicFunction { const fn to_wgsl(self) -> &'static str { match self { Self::Add => "Add", Self::Subtract => "Sub", Self::And => "And", Self::InclusiveOr => "Or", Self::ExclusiveOr => "Xor", Self::Min => "Min", Self::Max => "Max", Self::Exchange { compare: None } => "Exchange", Self::Exchange { .. } => "CompareExchangeWeak", } } } pub const fn supported_capabilities() -> crate::valid::Capabilities { // WGSL regurgitation supports almost everything, though browser webgpu can't parse most of these. use crate::valid::Capabilities as Caps; Caps::all() } ================================================ FILE: naga/src/back/wgsl/polyfill/inverse/inverse_2x2_f16.wgsl ================================================ fn _naga_inverse_2x2_f16(m: mat2x2) -> mat2x2 { var adj: mat2x2; adj[0][0] = m[1][1]; adj[0][1] = -m[0][1]; adj[1][0] = -m[1][0]; adj[1][1] = m[0][0]; let det: f16 = m[0][0] * m[1][1] - m[1][0] * m[0][1]; return adj * (1 / det); } ================================================ FILE: naga/src/back/wgsl/polyfill/inverse/inverse_2x2_f32.wgsl ================================================ fn _naga_inverse_2x2_f32(m: mat2x2) -> mat2x2 { var adj: mat2x2; adj[0][0] = m[1][1]; adj[0][1] = -m[0][1]; adj[1][0] = -m[1][0]; adj[1][1] = m[0][0]; let det: f32 = m[0][0] * m[1][1] - m[1][0] * m[0][1]; return adj * (1 / det); } ================================================ FILE: naga/src/back/wgsl/polyfill/inverse/inverse_3x3_f16.wgsl ================================================ fn _naga_inverse_3x3_f16(m: mat3x3) -> mat3x3 { var adj: mat3x3; adj[0][0] = (m[1][1] * m[2][2] - m[2][1] * m[1][2]); adj[1][0] = - (m[1][0] * m[2][2] - m[2][0] * m[1][2]); adj[2][0] = (m[1][0] * m[2][1] - m[2][0] * m[1][1]); adj[0][1] = - (m[0][1] * m[2][2] - m[2][1] * m[0][2]); adj[1][1] = (m[0][0] * m[2][2] - m[2][0] * m[0][2]); adj[2][1] = - (m[0][0] * m[2][1] - m[2][0] * m[0][1]); adj[0][2] = (m[0][1] * m[1][2] - m[1][1] * m[0][2]); adj[1][2] = - (m[0][0] * m[1][2] - m[1][0] * m[0][2]); adj[2][2] = (m[0][0] * m[1][1] - m[1][0] * m[0][1]); let det: f16 = (m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])); return adj * (1 / det); } ================================================ FILE: naga/src/back/wgsl/polyfill/inverse/inverse_3x3_f32.wgsl ================================================ fn _naga_inverse_3x3_f32(m: mat3x3) -> mat3x3 { var adj: mat3x3; adj[0][0] = (m[1][1] * m[2][2] - m[2][1] * m[1][2]); adj[1][0] = - (m[1][0] * m[2][2] - m[2][0] * m[1][2]); adj[2][0] = (m[1][0] * m[2][1] - m[2][0] * m[1][1]); adj[0][1] = - (m[0][1] * m[2][2] - m[2][1] * m[0][2]); adj[1][1] = (m[0][0] * m[2][2] - m[2][0] * m[0][2]); adj[2][1] = - (m[0][0] * m[2][1] - m[2][0] * m[0][1]); adj[0][2] = (m[0][1] * m[1][2] - m[1][1] * m[0][2]); adj[1][2] = - (m[0][0] * m[1][2] - m[1][0] * m[0][2]); adj[2][2] = (m[0][0] * m[1][1] - m[1][0] * m[0][1]); let det: f32 = (m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])); return adj * (1 / det); } ================================================ FILE: naga/src/back/wgsl/polyfill/inverse/inverse_4x4_f16.wgsl ================================================ fn _naga_inverse_4x4_f16(m: mat4x4) -> mat4x4 { let sub_factor00: f16 = m[2][2] * m[3][3] - m[3][2] * m[2][3]; let sub_factor01: f16 = m[2][1] * m[3][3] - m[3][1] * m[2][3]; let sub_factor02: f16 = m[2][1] * m[3][2] - m[3][1] * m[2][2]; let sub_factor03: f16 = m[2][0] * m[3][3] - m[3][0] * m[2][3]; let sub_factor04: f16 = m[2][0] * m[3][2] - m[3][0] * m[2][2]; let sub_factor05: f16 = m[2][0] * m[3][1] - m[3][0] * m[2][1]; let sub_factor06: f16 = m[1][2] * m[3][3] - m[3][2] * m[1][3]; let sub_factor07: f16 = m[1][1] * m[3][3] - m[3][1] * m[1][3]; let sub_factor08: f16 = m[1][1] * m[3][2] - m[3][1] * m[1][2]; let sub_factor09: f16 = m[1][0] * m[3][3] - m[3][0] * m[1][3]; let sub_factor10: f16 = m[1][0] * m[3][2] - m[3][0] * m[1][2]; let sub_factor11: f16 = m[1][1] * m[3][3] - m[3][1] * m[1][3]; let sub_factor12: f16 = m[1][0] * m[3][1] - m[3][0] * m[1][1]; let sub_factor13: f16 = m[1][2] * m[2][3] - m[2][2] * m[1][3]; let sub_factor14: f16 = m[1][1] * m[2][3] - m[2][1] * m[1][3]; let sub_factor15: f16 = m[1][1] * m[2][2] - m[2][1] * m[1][2]; let sub_factor16: f16 = m[1][0] * m[2][3] - m[2][0] * m[1][3]; let sub_factor17: f16 = m[1][0] * m[2][2] - m[2][0] * m[1][2]; let sub_factor18: f16 = m[1][0] * m[2][1] - m[2][0] * m[1][1]; var adj: mat4x4; adj[0][0] = (m[1][1] * sub_factor00 - m[1][2] * sub_factor01 + m[1][3] * sub_factor02); adj[1][0] = - (m[1][0] * sub_factor00 - m[1][2] * sub_factor03 + m[1][3] * sub_factor04); adj[2][0] = (m[1][0] * sub_factor01 - m[1][1] * sub_factor03 + m[1][3] * sub_factor05); adj[3][0] = - (m[1][0] * sub_factor02 - m[1][1] * sub_factor04 + m[1][2] * sub_factor05); adj[0][1] = - (m[0][1] * sub_factor00 - m[0][2] * sub_factor01 + m[0][3] * sub_factor02); adj[1][1] = (m[0][0] * sub_factor00 - m[0][2] * sub_factor03 + m[0][3] * sub_factor04); adj[2][1] = - (m[0][0] * sub_factor01 - m[0][1] * sub_factor03 + m[0][3] * sub_factor05); adj[3][1] = (m[0][0] * sub_factor02 - m[0][1] * sub_factor04 + m[0][2] * sub_factor05); adj[0][2] = (m[0][1] * sub_factor06 - m[0][2] * sub_factor07 + m[0][3] * sub_factor08); adj[1][2] = - (m[0][0] * sub_factor06 - m[0][2] * sub_factor09 + m[0][3] * sub_factor10); adj[2][2] = (m[0][0] * sub_factor11 - m[0][1] * sub_factor09 + m[0][3] * sub_factor12); adj[3][2] = - (m[0][0] * sub_factor08 - m[0][1] * sub_factor10 + m[0][2] * sub_factor12); adj[0][3] = - (m[0][1] * sub_factor13 - m[0][2] * sub_factor14 + m[0][3] * sub_factor15); adj[1][3] = (m[0][0] * sub_factor13 - m[0][2] * sub_factor16 + m[0][3] * sub_factor17); adj[2][3] = - (m[0][0] * sub_factor14 - m[0][1] * sub_factor16 + m[0][3] * sub_factor18); adj[3][3] = (m[0][0] * sub_factor15 - m[0][1] * sub_factor17 + m[0][2] * sub_factor18); let det = (m[0][0] * adj[0][0] + m[0][1] * adj[1][0] + m[0][2] * adj[2][0] + m[0][3] * adj[3][0]); return adj * (1 / det); } ================================================ FILE: naga/src/back/wgsl/polyfill/inverse/inverse_4x4_f32.wgsl ================================================ fn _naga_inverse_4x4_f32(m: mat4x4) -> mat4x4 { let sub_factor00: f32 = m[2][2] * m[3][3] - m[3][2] * m[2][3]; let sub_factor01: f32 = m[2][1] * m[3][3] - m[3][1] * m[2][3]; let sub_factor02: f32 = m[2][1] * m[3][2] - m[3][1] * m[2][2]; let sub_factor03: f32 = m[2][0] * m[3][3] - m[3][0] * m[2][3]; let sub_factor04: f32 = m[2][0] * m[3][2] - m[3][0] * m[2][2]; let sub_factor05: f32 = m[2][0] * m[3][1] - m[3][0] * m[2][1]; let sub_factor06: f32 = m[1][2] * m[3][3] - m[3][2] * m[1][3]; let sub_factor07: f32 = m[1][1] * m[3][3] - m[3][1] * m[1][3]; let sub_factor08: f32 = m[1][1] * m[3][2] - m[3][1] * m[1][2]; let sub_factor09: f32 = m[1][0] * m[3][3] - m[3][0] * m[1][3]; let sub_factor10: f32 = m[1][0] * m[3][2] - m[3][0] * m[1][2]; let sub_factor11: f32 = m[1][1] * m[3][3] - m[3][1] * m[1][3]; let sub_factor12: f32 = m[1][0] * m[3][1] - m[3][0] * m[1][1]; let sub_factor13: f32 = m[1][2] * m[2][3] - m[2][2] * m[1][3]; let sub_factor14: f32 = m[1][1] * m[2][3] - m[2][1] * m[1][3]; let sub_factor15: f32 = m[1][1] * m[2][2] - m[2][1] * m[1][2]; let sub_factor16: f32 = m[1][0] * m[2][3] - m[2][0] * m[1][3]; let sub_factor17: f32 = m[1][0] * m[2][2] - m[2][0] * m[1][2]; let sub_factor18: f32 = m[1][0] * m[2][1] - m[2][0] * m[1][1]; var adj: mat4x4; adj[0][0] = (m[1][1] * sub_factor00 - m[1][2] * sub_factor01 + m[1][3] * sub_factor02); adj[1][0] = - (m[1][0] * sub_factor00 - m[1][2] * sub_factor03 + m[1][3] * sub_factor04); adj[2][0] = (m[1][0] * sub_factor01 - m[1][1] * sub_factor03 + m[1][3] * sub_factor05); adj[3][0] = - (m[1][0] * sub_factor02 - m[1][1] * sub_factor04 + m[1][2] * sub_factor05); adj[0][1] = - (m[0][1] * sub_factor00 - m[0][2] * sub_factor01 + m[0][3] * sub_factor02); adj[1][1] = (m[0][0] * sub_factor00 - m[0][2] * sub_factor03 + m[0][3] * sub_factor04); adj[2][1] = - (m[0][0] * sub_factor01 - m[0][1] * sub_factor03 + m[0][3] * sub_factor05); adj[3][1] = (m[0][0] * sub_factor02 - m[0][1] * sub_factor04 + m[0][2] * sub_factor05); adj[0][2] = (m[0][1] * sub_factor06 - m[0][2] * sub_factor07 + m[0][3] * sub_factor08); adj[1][2] = - (m[0][0] * sub_factor06 - m[0][2] * sub_factor09 + m[0][3] * sub_factor10); adj[2][2] = (m[0][0] * sub_factor11 - m[0][1] * sub_factor09 + m[0][3] * sub_factor12); adj[3][2] = - (m[0][0] * sub_factor08 - m[0][1] * sub_factor10 + m[0][2] * sub_factor12); adj[0][3] = - (m[0][1] * sub_factor13 - m[0][2] * sub_factor14 + m[0][3] * sub_factor15); adj[1][3] = (m[0][0] * sub_factor13 - m[0][2] * sub_factor16 + m[0][3] * sub_factor17); adj[2][3] = - (m[0][0] * sub_factor14 - m[0][1] * sub_factor16 + m[0][3] * sub_factor18); adj[3][3] = (m[0][0] * sub_factor15 - m[0][1] * sub_factor17 + m[0][2] * sub_factor18); let det = (m[0][0] * adj[0][0] + m[0][1] * adj[1][0] + m[0][2] * adj[2][0] + m[0][3] * adj[3][0]); return adj * (1 / det); } ================================================ FILE: naga/src/back/wgsl/polyfill/mod.rs ================================================ use crate::{ScalarKind, TypeInner, VectorSize}; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct InversePolyfill { pub fun_name: &'static str, pub source: &'static str, } impl InversePolyfill { pub fn find_overload(ty: &TypeInner) -> Option { let &TypeInner::Matrix { columns, rows, scalar, } = ty else { return None; }; if columns != rows || scalar.kind != ScalarKind::Float { return None; }; Self::polyfill_overload(columns, scalar.width) } const fn polyfill_overload( dimension: VectorSize, width: crate::Bytes, ) -> Option { const INVERSE_2X2_F32: &str = include_str!("inverse/inverse_2x2_f32.wgsl"); const INVERSE_3X3_F32: &str = include_str!("inverse/inverse_3x3_f32.wgsl"); const INVERSE_4X4_F32: &str = include_str!("inverse/inverse_4x4_f32.wgsl"); const INVERSE_2X2_F16: &str = include_str!("inverse/inverse_2x2_f16.wgsl"); const INVERSE_3X3_F16: &str = include_str!("inverse/inverse_3x3_f16.wgsl"); const INVERSE_4X4_F16: &str = include_str!("inverse/inverse_4x4_f16.wgsl"); match (dimension, width) { (VectorSize::Bi, 4) => Some(InversePolyfill { fun_name: "_naga_inverse_2x2_f32", source: INVERSE_2X2_F32, }), (VectorSize::Tri, 4) => Some(InversePolyfill { fun_name: "_naga_inverse_3x3_f32", source: INVERSE_3X3_F32, }), (VectorSize::Quad, 4) => Some(InversePolyfill { fun_name: "_naga_inverse_4x4_f32", source: INVERSE_4X4_F32, }), (VectorSize::Bi, 2) => Some(InversePolyfill { fun_name: "_naga_inverse_2x2_f16", source: INVERSE_2X2_F16, }), (VectorSize::Tri, 2) => Some(InversePolyfill { fun_name: "_naga_inverse_3x3_f16", source: INVERSE_3X3_F16, }), (VectorSize::Quad, 2) => Some(InversePolyfill { fun_name: "_naga_inverse_4x4_f16", source: INVERSE_4X4_F16, }), _ => None, } } } ================================================ FILE: naga/src/back/wgsl/writer.rs ================================================ use alloc::{ format, string::{String, ToString}, vec, vec::Vec, }; use core::fmt::Write; use super::Error; use super::ToWgslIfImplemented as _; use crate::{back::wgsl::polyfill::InversePolyfill, common::wgsl::TypeContext}; use crate::{ back::{self, Baked}, common::{ self, wgsl::{address_space_str, ToWgsl, TryToWgsl}, }, proc::{self, NameKey}, valid, Handle, Module, ShaderStage, TypeInner, }; /// Shorthand result used internally by the backend type BackendResult = Result<(), Error>; /// WGSL [attribute](https://gpuweb.github.io/gpuweb/wgsl/#attributes) enum Attribute { Binding(u32), BuiltIn(crate::BuiltIn), Group(u32), Invariant, Interpolate(Option, Option), Location(u32), BlendSrc(u32), Stage(ShaderStage), WorkGroupSize([u32; 3]), MeshStage(String), TaskPayload(String), PerPrimitive, IncomingRayPayload(String), } /// The WGSL form that `write_expr_with_indirection` should use to render a Naga /// expression. /// /// Sometimes a Naga `Expression` alone doesn't provide enough information to /// choose the right rendering for it in WGSL. For example, one natural WGSL /// rendering of a Naga `LocalVariable(x)` expression might be `&x`, since /// `LocalVariable` produces a pointer to the local variable's storage. But when /// rendering a `Store` statement, the `pointer` operand must be the left hand /// side of a WGSL assignment, so the proper rendering is `x`. /// /// The caller of `write_expr_with_indirection` must provide an `Expected` value /// to indicate how ambiguous expressions should be rendered. #[derive(Clone, Copy, Debug)] enum Indirection { /// Render pointer-construction expressions as WGSL `ptr`-typed expressions. /// /// This is the right choice for most cases. Whenever a Naga pointer /// expression is not the `pointer` operand of a `Load` or `Store`, it /// must be a WGSL pointer expression. Ordinary, /// Render pointer-construction expressions as WGSL reference-typed /// expressions. /// /// For example, this is the right choice for the `pointer` operand when /// rendering a `Store` statement as a WGSL assignment. Reference, } bitflags::bitflags! { #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct WriterFlags: u32 { /// Always annotate the type information instead of inferring. const EXPLICIT_TYPES = 0x1; } } pub struct Writer { out: W, flags: WriterFlags, names: crate::FastHashMap, namer: proc::Namer, named_expressions: crate::NamedExpressions, required_polyfills: crate::FastIndexSet, } impl Writer { pub fn new(out: W, flags: WriterFlags) -> Self { Writer { out, flags, names: crate::FastHashMap::default(), namer: proc::Namer::default(), named_expressions: crate::NamedExpressions::default(), required_polyfills: crate::FastIndexSet::default(), } } fn reset(&mut self, module: &Module) { self.names.clear(); self.namer.reset( module, &crate::keywords::wgsl::RESERVED_SET, &crate::keywords::wgsl::BUILTIN_IDENTIFIER_SET, // an identifier must not start with two underscore proc::CaseInsensitiveKeywordSet::empty(), &["__", "_naga"], &mut self.names, ); self.named_expressions.clear(); self.required_polyfills.clear(); } /// Determine if `ty` is the Naga IR presentation of a WGSL builtin type. /// /// Return true if `ty` refers to the Naga IR form of a WGSL builtin type /// like `__atomic_compare_exchange_result`. /// /// Even though the module may use the type, the WGSL backend should avoid /// emitting a definition for it, since it is [predeclared] in WGSL. /// /// This also covers types like [`NagaExternalTextureParams`], which other /// backends use to lower WGSL constructs like external textures to their /// implementations. WGSL can express these directly, so the types need not /// be emitted. /// /// [predeclared]: https://www.w3.org/TR/WGSL/#predeclared /// [`NagaExternalTextureParams`]: crate::ir::SpecialTypes::external_texture_params fn is_builtin_wgsl_struct(&self, module: &Module, ty: Handle) -> bool { module .special_types .predeclared_types .values() .any(|t| *t == ty) || Some(ty) == module.special_types.external_texture_params || Some(ty) == module.special_types.external_texture_transfer_function } pub fn write(&mut self, module: &Module, info: &valid::ModuleInfo) -> BackendResult { self.reset(module); // Write all `enable` declarations self.write_enable_declarations(module)?; // Write all structs for (handle, ty) in module.types.iter() { if let TypeInner::Struct { ref members, .. } = ty.inner { { if !self.is_builtin_wgsl_struct(module, handle) { self.write_struct(module, handle, members)?; writeln!(self.out)?; } } } } // Write all named constants let mut constants = module .constants .iter() .filter(|&(_, c)| c.name.is_some()) .peekable(); while let Some((handle, _)) = constants.next() { self.write_global_constant(module, handle)?; // Add extra newline for readability on last iteration if constants.peek().is_none() { writeln!(self.out)?; } } // Write all overrides let mut overrides = module.overrides.iter().peekable(); while let Some((handle, _)) = overrides.next() { self.write_override(module, handle)?; // Add extra newline for readability on last iteration if overrides.peek().is_none() { writeln!(self.out)?; } } // Write all globals for (ty, global) in module.global_variables.iter() { self.write_global(module, global, ty)?; } if !module.global_variables.is_empty() { // Add extra newline for readability writeln!(self.out)?; } // Write all regular functions for (handle, function) in module.functions.iter() { let fun_info = &info[handle]; let func_ctx = back::FunctionCtx { ty: back::FunctionType::Function(handle), info: fun_info, expressions: &function.expressions, named_expressions: &function.named_expressions, }; // Write the function self.write_function(module, function, &func_ctx)?; writeln!(self.out)?; } // Write all entry points for (index, ep) in module.entry_points.iter().enumerate() { let attributes = match ep.stage { ShaderStage::Vertex | ShaderStage::Fragment => vec![Attribute::Stage(ep.stage)], ShaderStage::Compute => vec![ Attribute::Stage(ShaderStage::Compute), Attribute::WorkGroupSize(ep.workgroup_size), ], ShaderStage::Mesh => { let mesh_output_name = module.global_variables [ep.mesh_info.as_ref().unwrap().output_variable] .name .clone() .unwrap(); let mut mesh_attrs = vec![ Attribute::MeshStage(mesh_output_name), Attribute::WorkGroupSize(ep.workgroup_size), ]; if let Some(task_payload) = ep.task_payload { let payload_name = module.global_variables[task_payload].name.clone().unwrap(); mesh_attrs.push(Attribute::TaskPayload(payload_name)); } mesh_attrs } ShaderStage::Task => { let payload_name = module.global_variables[ep.task_payload.unwrap()] .name .clone() .unwrap(); vec![ Attribute::Stage(ShaderStage::Task), Attribute::TaskPayload(payload_name), Attribute::WorkGroupSize(ep.workgroup_size), ] } ShaderStage::RayGeneration => vec![Attribute::Stage(ShaderStage::RayGeneration)], ShaderStage::AnyHit | ShaderStage::ClosestHit | ShaderStage::Miss => { let payload_name = module.global_variables[ep.incoming_ray_payload.unwrap()] .name .clone() .unwrap(); vec![ Attribute::Stage(ep.stage), Attribute::IncomingRayPayload(payload_name), ] } }; self.write_attributes(&attributes)?; // Add a newline after attribute writeln!(self.out)?; let func_ctx = back::FunctionCtx { ty: back::FunctionType::EntryPoint(index as u16), info: info.get_entry_point(index), expressions: &ep.function.expressions, named_expressions: &ep.function.named_expressions, }; self.write_function(module, &ep.function, &func_ctx)?; if index < module.entry_points.len() - 1 { writeln!(self.out)?; } } // Write any polyfills that were required. for polyfill in &self.required_polyfills { writeln!(self.out)?; write!(self.out, "{}", polyfill.source)?; writeln!(self.out)?; } Ok(()) } /// Helper method which writes all the `enable` declarations /// needed for a module. fn write_enable_declarations(&mut self, module: &Module) -> BackendResult { #[derive(Default)] struct RequiredEnabled { f16: bool, dual_source_blending: bool, clip_distances: bool, mesh_shaders: bool, primitive_index: bool, cooperative_matrix: bool, draw_index: bool, ray_tracing_pipeline: bool, } let mut needed = RequiredEnabled { mesh_shaders: module.uses_mesh_shaders(), ..Default::default() }; let check_binding = |binding: &crate::Binding, needed: &mut RequiredEnabled| match *binding { crate::Binding::Location { blend_src: Some(_), .. } => { needed.dual_source_blending = true; } crate::Binding::BuiltIn(crate::BuiltIn::ClipDistances) => { needed.clip_distances = true; } crate::Binding::BuiltIn(crate::BuiltIn::PrimitiveIndex) => { needed.primitive_index = true; } crate::Binding::Location { per_primitive: true, .. } => { needed.mesh_shaders = true; } crate::Binding::BuiltIn(crate::BuiltIn::DrawIndex) => needed.draw_index = true, crate::Binding::BuiltIn( crate::BuiltIn::RayInvocationId | crate::BuiltIn::NumRayInvocations | crate::BuiltIn::InstanceCustomData | crate::BuiltIn::GeometryIndex | crate::BuiltIn::WorldRayOrigin | crate::BuiltIn::WorldRayDirection | crate::BuiltIn::ObjectRayOrigin | crate::BuiltIn::ObjectRayDirection | crate::BuiltIn::RayTmin | crate::BuiltIn::RayTCurrentMax | crate::BuiltIn::ObjectToWorld | crate::BuiltIn::WorldToObject, ) => { needed.ray_tracing_pipeline = true; } _ => {} }; // Determine which `enable` declarations are needed for (_, ty) in module.types.iter() { match ty.inner { TypeInner::Scalar(scalar) | TypeInner::Vector { scalar, .. } | TypeInner::Matrix { scalar, .. } => { needed.f16 |= scalar == crate::Scalar::F16; } TypeInner::Struct { ref members, .. } => { for binding in members.iter().filter_map(|m| m.binding.as_ref()) { check_binding(binding, &mut needed); } } TypeInner::CooperativeMatrix { .. } => { needed.cooperative_matrix = true; } TypeInner::AccelerationStructure { .. } => { needed.ray_tracing_pipeline = true; } _ => {} } } for ep in &module.entry_points { if let Some(res) = ep.function.result.as_ref().and_then(|a| a.binding.as_ref()) { check_binding(res, &mut needed); } for arg in ep .function .arguments .iter() .filter_map(|a| a.binding.as_ref()) { check_binding(arg, &mut needed); } } if module.global_variables.iter().any(|gv| { gv.1.space == crate::AddressSpace::IncomingRayPayload || gv.1.space == crate::AddressSpace::RayPayload }) { needed.ray_tracing_pipeline = true; } if module.entry_points.iter().any(|ep| { matches!( ep.stage, ShaderStage::RayGeneration | ShaderStage::AnyHit | ShaderStage::ClosestHit | ShaderStage::Miss ) }) { needed.ray_tracing_pipeline = true; } if module.global_variables.iter().any(|gv| { gv.1.space == crate::AddressSpace::IncomingRayPayload || gv.1.space == crate::AddressSpace::RayPayload }) { needed.ray_tracing_pipeline = true; } if module.entry_points.iter().any(|ep| { matches!( ep.stage, ShaderStage::RayGeneration | ShaderStage::AnyHit | ShaderStage::ClosestHit | ShaderStage::Miss ) }) { needed.ray_tracing_pipeline = true; } // Write required declarations let mut any_written = false; if needed.f16 { writeln!(self.out, "enable f16;")?; any_written = true; } if needed.dual_source_blending { writeln!(self.out, "enable dual_source_blending;")?; any_written = true; } if needed.clip_distances { writeln!(self.out, "enable clip_distances;")?; any_written = true; } if module.uses_mesh_shaders() { writeln!(self.out, "enable wgpu_mesh_shader;")?; any_written = true; } if needed.draw_index { writeln!(self.out, "enable draw_index;")?; any_written = true; } if needed.primitive_index { writeln!(self.out, "enable primitive_index;")?; any_written = true; } if needed.cooperative_matrix { writeln!(self.out, "enable wgpu_cooperative_matrix;")?; any_written = true; } if needed.ray_tracing_pipeline { writeln!(self.out, "enable wgpu_ray_tracing_pipeline;")?; any_written = true; } if any_written { // Empty line for readability writeln!(self.out)?; } Ok(()) } /// Helper method used to write /// [functions](https://gpuweb.github.io/gpuweb/wgsl/#functions) /// /// # Notes /// Ends in a newline fn write_function( &mut self, module: &Module, func: &crate::Function, func_ctx: &back::FunctionCtx<'_>, ) -> BackendResult { let func_name = match func_ctx.ty { back::FunctionType::EntryPoint(index) => &self.names[&NameKey::EntryPoint(index)], back::FunctionType::Function(handle) => &self.names[&NameKey::Function(handle)], }; // Write function name write!(self.out, "fn {func_name}(")?; // Write function arguments for (index, arg) in func.arguments.iter().enumerate() { // Write argument attribute if a binding is present if let Some(ref binding) = arg.binding { self.write_attributes(&map_binding_to_attribute(binding))?; } // Write argument name let argument_name = &self.names[&func_ctx.argument_key(index as u32)]; write!(self.out, "{argument_name}: ")?; // Write argument type self.write_type(module, arg.ty)?; if index < func.arguments.len() - 1 { // Add a separator between args write!(self.out, ", ")?; } } write!(self.out, ")")?; // Write function return type if let Some(ref result) = func.result { write!(self.out, " -> ")?; if let Some(ref binding) = result.binding { self.write_attributes(&map_binding_to_attribute(binding))?; } self.write_type(module, result.ty)?; } write!(self.out, " {{")?; writeln!(self.out)?; // Write function local variables for (handle, local) in func.local_variables.iter() { // Write indentation (only for readability) write!(self.out, "{}", back::INDENT)?; // Write the local name // The leading space is important write!(self.out, "var {}: ", self.names[&func_ctx.name_key(handle)])?; // Write the local type self.write_type(module, local.ty)?; // Write the local initializer if needed if let Some(init) = local.init { // Put the equal signal only if there's a initializer // The leading and trailing spaces aren't needed but help with readability write!(self.out, " = ")?; // Write the constant // `write_constant` adds no trailing or leading space/newline self.write_expr(module, init, func_ctx)?; } // Finish the local with `;` and add a newline (only for readability) writeln!(self.out, ";")? } if !func.local_variables.is_empty() { writeln!(self.out)?; } // Write the function body (statement list) for sta in func.body.iter() { // The indentation should always be 1 when writing the function body self.write_stmt(module, sta, func_ctx, back::Level(1))?; } writeln!(self.out, "}}")?; self.named_expressions.clear(); Ok(()) } /// Helper method to write a attribute fn write_attributes(&mut self, attributes: &[Attribute]) -> BackendResult { for attribute in attributes { match *attribute { Attribute::Location(id) => write!(self.out, "@location({id}) ")?, Attribute::BlendSrc(blend_src) => write!(self.out, "@blend_src({blend_src}) ")?, Attribute::BuiltIn(builtin_attrib) => { let builtin = builtin_attrib.to_wgsl_if_implemented()?; write!(self.out, "@builtin({builtin}) ")?; } Attribute::Stage(shader_stage) => { let stage_str = match shader_stage { ShaderStage::Vertex => "vertex", ShaderStage::Fragment => "fragment", ShaderStage::Compute => "compute", ShaderStage::Task => "task", //Handled by another variant in the Attribute enum, so this code should never be hit. ShaderStage::Mesh => unreachable!(), ShaderStage::RayGeneration => "ray_generation", ShaderStage::AnyHit => "any_hit", ShaderStage::ClosestHit => "closest_hit", ShaderStage::Miss => "miss", }; write!(self.out, "@{stage_str} ")?; } Attribute::WorkGroupSize(size) => { write!( self.out, "@workgroup_size({}, {}, {}) ", size[0], size[1], size[2] )?; } Attribute::Binding(id) => write!(self.out, "@binding({id}) ")?, Attribute::Group(id) => write!(self.out, "@group({id}) ")?, Attribute::Invariant => write!(self.out, "@invariant ")?, Attribute::Interpolate(interpolation, sampling) => { if sampling.is_some() && sampling != Some(crate::Sampling::Center) { let interpolation = interpolation .unwrap_or(crate::Interpolation::Perspective) .to_wgsl(); let sampling = sampling.unwrap_or(crate::Sampling::Center).to_wgsl(); write!(self.out, "@interpolate({interpolation}, {sampling}) ")?; } else if interpolation.is_some() && interpolation != Some(crate::Interpolation::Perspective) { let interpolation = interpolation .unwrap_or(crate::Interpolation::Perspective) .to_wgsl(); write!(self.out, "@interpolate({interpolation}) ")?; } } Attribute::MeshStage(ref name) => { write!(self.out, "@mesh({name}) ")?; } Attribute::TaskPayload(ref payload_name) => { write!(self.out, "@payload({payload_name}) ")?; } Attribute::PerPrimitive => write!(self.out, "@per_primitive ")?, Attribute::IncomingRayPayload(ref payload_name) => { write!(self.out, "@incoming_payload({payload_name}) ")?; } }; } Ok(()) } /// Helper method used to write structs /// Write the full declaration of a struct type. /// /// Write out a definition of the struct type referred to by /// `handle` in `module`. The output will be an instance of the /// `struct_decl` production in the WGSL grammar. /// /// Use `members` as the list of `handle`'s members. (This /// function is usually called after matching a `TypeInner`, so /// the callers already have the members at hand.) fn write_struct( &mut self, module: &Module, handle: Handle, members: &[crate::StructMember], ) -> BackendResult { write!(self.out, "struct {}", self.names[&NameKey::Type(handle)])?; write!(self.out, " {{")?; writeln!(self.out)?; for (index, member) in members.iter().enumerate() { // The indentation is only for readability write!(self.out, "{}", back::INDENT)?; if let Some(ref binding) = member.binding { self.write_attributes(&map_binding_to_attribute(binding))?; } // Write struct member name and type let member_name = &self.names[&NameKey::StructMember(handle, index as u32)]; write!(self.out, "{member_name}: ")?; self.write_type(module, member.ty)?; write!(self.out, ",")?; writeln!(self.out)?; } writeln!(self.out, "}}")?; Ok(()) } fn write_type(&mut self, module: &Module, ty: Handle) -> BackendResult { // This actually can't be factored out into a nice constructor method, // because the borrow checker needs to be able to see that the borrows // of `self.names` and `self.out` are disjoint. let type_context = WriterTypeContext { module, names: &self.names, }; type_context.write_type(ty, &mut self.out)?; Ok(()) } fn write_type_resolution( &mut self, module: &Module, resolution: &proc::TypeResolution, ) -> BackendResult { // This actually can't be factored out into a nice constructor method, // because the borrow checker needs to be able to see that the borrows // of `self.names` and `self.out` are disjoint. let type_context = WriterTypeContext { module, names: &self.names, }; type_context.write_type_resolution(resolution, &mut self.out)?; Ok(()) } /// Helper method used to write statements /// /// # Notes /// Always adds a newline fn write_stmt( &mut self, module: &Module, stmt: &crate::Statement, func_ctx: &back::FunctionCtx<'_>, level: back::Level, ) -> BackendResult { use crate::{Expression, Statement}; match *stmt { Statement::Emit(ref range) => { for handle in range.clone() { let info = &func_ctx.info[handle]; let expr_name = if let Some(name) = func_ctx.named_expressions.get(&handle) { // Front end provides names for all variables at the start of writing. // But we write them to step by step. We need to recache them // Otherwise, we could accidentally write variable name instead of full expression. // Also, we use sanitized names! It defense backend from generating variable with name from reserved keywords. Some(self.namer.call(name)) } else { let expr = &func_ctx.expressions[handle]; let min_ref_count = expr.bake_ref_count(); // Forcefully creating baking expressions in some cases to help with readability let required_baking_expr = match *expr { Expression::ImageLoad { .. } | Expression::ImageQuery { .. } | Expression::ImageSample { .. } => true, _ => false, }; if min_ref_count <= info.ref_count || required_baking_expr { Some(Baked(handle).to_string()) } else { None } }; if let Some(name) = expr_name { write!(self.out, "{level}")?; self.start_named_expr(module, handle, func_ctx, &name)?; self.write_expr(module, handle, func_ctx)?; self.named_expressions.insert(handle, name); writeln!(self.out, ";")?; } } } // TODO: copy-paste from glsl-out Statement::If { condition, ref accept, ref reject, } => { write!(self.out, "{level}")?; write!(self.out, "if ")?; self.write_expr(module, condition, func_ctx)?; writeln!(self.out, " {{")?; let l2 = level.next(); for sta in accept { // Increase indentation to help with readability self.write_stmt(module, sta, func_ctx, l2)?; } // If there are no statements in the reject block we skip writing it // This is only for readability if !reject.is_empty() { writeln!(self.out, "{level}}} else {{")?; for sta in reject { // Increase indentation to help with readability self.write_stmt(module, sta, func_ctx, l2)?; } } writeln!(self.out, "{level}}}")? } Statement::Return { value } => { write!(self.out, "{level}")?; write!(self.out, "return")?; if let Some(return_value) = value { // The leading space is important write!(self.out, " ")?; self.write_expr(module, return_value, func_ctx)?; } writeln!(self.out, ";")?; } // TODO: copy-paste from glsl-out Statement::Kill => { write!(self.out, "{level}")?; writeln!(self.out, "discard;")? } Statement::Store { pointer, value } => { write!(self.out, "{level}")?; let is_atomic_pointer = func_ctx .resolve_type(pointer, &module.types) .is_atomic_pointer(&module.types); if is_atomic_pointer { write!(self.out, "atomicStore(")?; self.write_expr(module, pointer, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, value, func_ctx)?; write!(self.out, ")")?; } else { self.write_expr_with_indirection( module, pointer, func_ctx, Indirection::Reference, )?; write!(self.out, " = ")?; self.write_expr(module, value, func_ctx)?; } writeln!(self.out, ";")? } Statement::Call { function, ref arguments, result, } => { write!(self.out, "{level}")?; if let Some(expr) = result { let name = Baked(expr).to_string(); self.start_named_expr(module, expr, func_ctx, &name)?; self.named_expressions.insert(expr, name); } let func_name = &self.names[&NameKey::Function(function)]; write!(self.out, "{func_name}(")?; for (index, &argument) in arguments.iter().enumerate() { if index != 0 { write!(self.out, ", ")?; } self.write_expr(module, argument, func_ctx)?; } writeln!(self.out, ");")? } Statement::Atomic { pointer, ref fun, value, result, } => { write!(self.out, "{level}")?; if let Some(result) = result { let res_name = Baked(result).to_string(); self.start_named_expr(module, result, func_ctx, &res_name)?; self.named_expressions.insert(result, res_name); } let fun_str = fun.to_wgsl(); write!(self.out, "atomic{fun_str}(")?; self.write_expr(module, pointer, func_ctx)?; if let crate::AtomicFunction::Exchange { compare: Some(cmp) } = *fun { write!(self.out, ", ")?; self.write_expr(module, cmp, func_ctx)?; } write!(self.out, ", ")?; self.write_expr(module, value, func_ctx)?; writeln!(self.out, ");")? } Statement::ImageAtomic { image, coordinate, array_index, ref fun, value, } => { write!(self.out, "{level}")?; let fun_str = fun.to_wgsl(); write!(self.out, "textureAtomic{fun_str}(")?; self.write_expr(module, image, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, coordinate, func_ctx)?; if let Some(array_index_expr) = array_index { write!(self.out, ", ")?; self.write_expr(module, array_index_expr, func_ctx)?; } write!(self.out, ", ")?; self.write_expr(module, value, func_ctx)?; writeln!(self.out, ");")?; } Statement::WorkGroupUniformLoad { pointer, result } => { write!(self.out, "{level}")?; // TODO: Obey named expressions here. let res_name = Baked(result).to_string(); self.start_named_expr(module, result, func_ctx, &res_name)?; self.named_expressions.insert(result, res_name); write!(self.out, "workgroupUniformLoad(")?; self.write_expr(module, pointer, func_ctx)?; writeln!(self.out, ");")?; } Statement::ImageStore { image, coordinate, array_index, value, } => { write!(self.out, "{level}")?; write!(self.out, "textureStore(")?; self.write_expr(module, image, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, coordinate, func_ctx)?; if let Some(array_index_expr) = array_index { write!(self.out, ", ")?; self.write_expr(module, array_index_expr, func_ctx)?; } write!(self.out, ", ")?; self.write_expr(module, value, func_ctx)?; writeln!(self.out, ");")?; } // TODO: copy-paste from glsl-out Statement::Block(ref block) => { write!(self.out, "{level}")?; writeln!(self.out, "{{")?; for sta in block.iter() { // Increase the indentation to help with readability self.write_stmt(module, sta, func_ctx, level.next())? } writeln!(self.out, "{level}}}")? } Statement::Switch { selector, ref cases, } => { // Start the switch write!(self.out, "{level}")?; write!(self.out, "switch ")?; self.write_expr(module, selector, func_ctx)?; writeln!(self.out, " {{")?; let l2 = level.next(); let mut new_case = true; for case in cases { if case.fall_through && !case.body.is_empty() { // TODO: we could do the same workaround as we did for the HLSL backend return Err(Error::Unimplemented( "fall-through switch case block".into(), )); } match case.value { crate::SwitchValue::I32(value) => { if new_case { write!(self.out, "{l2}case ")?; } write!(self.out, "{value}")?; } crate::SwitchValue::U32(value) => { if new_case { write!(self.out, "{l2}case ")?; } write!(self.out, "{value}u")?; } crate::SwitchValue::Default => { if new_case { if case.fall_through { write!(self.out, "{l2}case ")?; } else { write!(self.out, "{l2}")?; } } write!(self.out, "default")?; } } new_case = !case.fall_through; if case.fall_through { write!(self.out, ", ")?; } else { writeln!(self.out, ": {{")?; } for sta in case.body.iter() { self.write_stmt(module, sta, func_ctx, l2.next())?; } if !case.fall_through { writeln!(self.out, "{l2}}}")?; } } writeln!(self.out, "{level}}}")? } Statement::Loop { ref body, ref continuing, break_if, } => { write!(self.out, "{level}")?; writeln!(self.out, "loop {{")?; let l2 = level.next(); for sta in body.iter() { self.write_stmt(module, sta, func_ctx, l2)?; } // The continuing is optional so we don't need to write it if // it is empty, but the `break if` counts as a continuing statement // so even if `continuing` is empty we must generate it if a // `break if` exists if !continuing.is_empty() || break_if.is_some() { writeln!(self.out, "{l2}continuing {{")?; for sta in continuing.iter() { self.write_stmt(module, sta, func_ctx, l2.next())?; } // The `break if` is always the last // statement of the `continuing` block if let Some(condition) = break_if { // The trailing space is important write!(self.out, "{}break if ", l2.next())?; self.write_expr(module, condition, func_ctx)?; // Close the `break if` statement writeln!(self.out, ";")?; } writeln!(self.out, "{l2}}}")?; } writeln!(self.out, "{level}}}")? } Statement::Break => { writeln!(self.out, "{level}break;")?; } Statement::Continue => { writeln!(self.out, "{level}continue;")?; } Statement::ControlBarrier(barrier) | Statement::MemoryBarrier(barrier) => { if barrier.contains(crate::Barrier::STORAGE) { writeln!(self.out, "{level}storageBarrier();")?; } if barrier.contains(crate::Barrier::WORK_GROUP) { writeln!(self.out, "{level}workgroupBarrier();")?; } if barrier.contains(crate::Barrier::SUB_GROUP) { writeln!(self.out, "{level}subgroupBarrier();")?; } if barrier.contains(crate::Barrier::TEXTURE) { writeln!(self.out, "{level}textureBarrier();")?; } } Statement::RayQuery { .. } => unreachable!(), Statement::SubgroupBallot { result, predicate } => { write!(self.out, "{level}")?; let res_name = Baked(result).to_string(); self.start_named_expr(module, result, func_ctx, &res_name)?; self.named_expressions.insert(result, res_name); write!(self.out, "subgroupBallot(")?; if let Some(predicate) = predicate { self.write_expr(module, predicate, func_ctx)?; } writeln!(self.out, ");")?; } Statement::SubgroupCollectiveOperation { op, collective_op, argument, result, } => { write!(self.out, "{level}")?; let res_name = Baked(result).to_string(); self.start_named_expr(module, result, func_ctx, &res_name)?; self.named_expressions.insert(result, res_name); match (collective_op, op) { (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::All) => { write!(self.out, "subgroupAll(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Any) => { write!(self.out, "subgroupAny(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Add) => { write!(self.out, "subgroupAdd(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Mul) => { write!(self.out, "subgroupMul(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Max) => { write!(self.out, "subgroupMax(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Min) => { write!(self.out, "subgroupMin(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::And) => { write!(self.out, "subgroupAnd(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Or) => { write!(self.out, "subgroupOr(")? } (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Xor) => { write!(self.out, "subgroupXor(")? } (crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Add) => { write!(self.out, "subgroupExclusiveAdd(")? } (crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Mul) => { write!(self.out, "subgroupExclusiveMul(")? } (crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Add) => { write!(self.out, "subgroupInclusiveAdd(")? } (crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Mul) => { write!(self.out, "subgroupInclusiveMul(")? } _ => unimplemented!(), } self.write_expr(module, argument, func_ctx)?; writeln!(self.out, ");")?; } Statement::SubgroupGather { mode, argument, result, } => { write!(self.out, "{level}")?; let res_name = Baked(result).to_string(); self.start_named_expr(module, result, func_ctx, &res_name)?; self.named_expressions.insert(result, res_name); match mode { crate::GatherMode::BroadcastFirst => { write!(self.out, "subgroupBroadcastFirst(")?; } crate::GatherMode::Broadcast(_) => { write!(self.out, "subgroupBroadcast(")?; } crate::GatherMode::Shuffle(_) => { write!(self.out, "subgroupShuffle(")?; } crate::GatherMode::ShuffleDown(_) => { write!(self.out, "subgroupShuffleDown(")?; } crate::GatherMode::ShuffleUp(_) => { write!(self.out, "subgroupShuffleUp(")?; } crate::GatherMode::ShuffleXor(_) => { write!(self.out, "subgroupShuffleXor(")?; } crate::GatherMode::QuadBroadcast(_) => { write!(self.out, "quadBroadcast(")?; } crate::GatherMode::QuadSwap(direction) => match direction { crate::Direction::X => { write!(self.out, "quadSwapX(")?; } crate::Direction::Y => { write!(self.out, "quadSwapY(")?; } crate::Direction::Diagonal => { write!(self.out, "quadSwapDiagonal(")?; } }, } self.write_expr(module, argument, func_ctx)?; match mode { crate::GatherMode::BroadcastFirst => {} crate::GatherMode::Broadcast(index) | crate::GatherMode::Shuffle(index) | crate::GatherMode::ShuffleDown(index) | crate::GatherMode::ShuffleUp(index) | crate::GatherMode::ShuffleXor(index) | crate::GatherMode::QuadBroadcast(index) => { write!(self.out, ", ")?; self.write_expr(module, index, func_ctx)?; } crate::GatherMode::QuadSwap(_) => {} } writeln!(self.out, ");")?; } Statement::CooperativeStore { target, ref data } => { let suffix = if data.row_major { "T" } else { "" }; write!(self.out, "{level}coopStore{suffix}(")?; self.write_expr(module, target, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, data.pointer, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, data.stride, func_ctx)?; writeln!(self.out, ");")? } Statement::RayPipelineFunction(fun) => match fun { crate::RayPipelineFunction::TraceRay { acceleration_structure, descriptor, payload, } => { write!(self.out, "{level}traceRay(")?; self.write_expr(module, acceleration_structure, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, descriptor, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, payload, func_ctx)?; writeln!(self.out, ");")? } }, } Ok(()) } /// Return the sort of indirection that `expr`'s plain form evaluates to. /// /// An expression's 'plain form' is the most general rendition of that /// expression into WGSL, lacking `&` or `*` operators: /// /// - The plain form of `LocalVariable(x)` is simply `x`, which is a reference /// to the local variable's storage. /// /// - The plain form of `GlobalVariable(g)` is simply `g`, which is usually a /// reference to the global variable's storage. However, globals in the /// `Handle` address space are immutable, and `GlobalVariable` expressions for /// those produce the value directly, not a pointer to it. Such /// `GlobalVariable` expressions are `Ordinary`. /// /// - `Access` and `AccessIndex` are `Reference` when their `base` operand is a /// pointer. If they are applied directly to a composite value, they are /// `Ordinary`. /// /// Note that `FunctionArgument` expressions are never `Reference`, even when /// the argument's type is `Pointer`. `FunctionArgument` always evaluates to the /// argument's value directly, so any pointer it produces is merely the value /// passed by the caller. fn plain_form_indirection( &self, expr: Handle, module: &Module, func_ctx: &back::FunctionCtx<'_>, ) -> Indirection { use crate::Expression as Ex; // Named expressions are `let` expressions, which apply the Load Rule, // so if their type is a Naga pointer, then that must be a WGSL pointer // as well. if self.named_expressions.contains_key(&expr) { return Indirection::Ordinary; } match func_ctx.expressions[expr] { Ex::LocalVariable(_) => Indirection::Reference, Ex::GlobalVariable(handle) => { let global = &module.global_variables[handle]; match global.space { crate::AddressSpace::Handle => Indirection::Ordinary, _ => Indirection::Reference, } } Ex::Access { base, .. } | Ex::AccessIndex { base, .. } => { let base_ty = func_ctx.resolve_type(base, &module.types); match *base_ty { TypeInner::Pointer { .. } | TypeInner::ValuePointer { .. } => { Indirection::Reference } _ => Indirection::Ordinary, } } _ => Indirection::Ordinary, } } fn start_named_expr( &mut self, module: &Module, handle: Handle, func_ctx: &back::FunctionCtx, name: &str, ) -> BackendResult { // Write variable name write!(self.out, "let {name}")?; if self.flags.contains(WriterFlags::EXPLICIT_TYPES) { write!(self.out, ": ")?; // Write variable type self.write_type_resolution(module, &func_ctx.info[handle].ty)?; } write!(self.out, " = ")?; Ok(()) } /// Write the ordinary WGSL form of `expr`. /// /// See `write_expr_with_indirection` for details. fn write_expr( &mut self, module: &Module, expr: Handle, func_ctx: &back::FunctionCtx<'_>, ) -> BackendResult { self.write_expr_with_indirection(module, expr, func_ctx, Indirection::Ordinary) } /// Write `expr` as a WGSL expression with the requested indirection. /// /// In terms of the WGSL grammar, the resulting expression is a /// `singular_expression`. It may be parenthesized. This makes it suitable /// for use as the operand of a unary or binary operator without worrying /// about precedence. /// /// This does not produce newlines or indentation. /// /// The `requested` argument indicates (roughly) whether Naga /// `Pointer`-valued expressions represent WGSL references or pointers. See /// `Indirection` for details. fn write_expr_with_indirection( &mut self, module: &Module, expr: Handle, func_ctx: &back::FunctionCtx<'_>, requested: Indirection, ) -> BackendResult { // If the plain form of the expression is not what we need, emit the // operator necessary to correct that. let plain = self.plain_form_indirection(expr, module, func_ctx); log::trace!( "expression {:?}={:?} is {:?}, expected {:?}", expr, func_ctx.expressions[expr], plain, requested, ); match (requested, plain) { (Indirection::Ordinary, Indirection::Reference) => { write!(self.out, "(&")?; self.write_expr_plain_form(module, expr, func_ctx, plain)?; write!(self.out, ")")?; } (Indirection::Reference, Indirection::Ordinary) => { write!(self.out, "(*")?; self.write_expr_plain_form(module, expr, func_ctx, plain)?; write!(self.out, ")")?; } (_, _) => self.write_expr_plain_form(module, expr, func_ctx, plain)?, } Ok(()) } fn write_const_expression( &mut self, module: &Module, expr: Handle, arena: &crate::Arena, ) -> BackendResult { self.write_possibly_const_expression(module, expr, arena, |writer, expr| { writer.write_const_expression(module, expr, arena) }) } fn write_possibly_const_expression( &mut self, module: &Module, expr: Handle, expressions: &crate::Arena, write_expression: E, ) -> BackendResult where E: Fn(&mut Self, Handle) -> BackendResult, { use crate::Expression; match expressions[expr] { Expression::Literal(literal) => match literal { crate::Literal::F16(value) => write!(self.out, "{value}h")?, crate::Literal::F32(value) => write!(self.out, "{value}f")?, crate::Literal::U32(value) => write!(self.out, "{value}u")?, crate::Literal::I32(value) => { // `-2147483648i` is not valid WGSL. The most negative `i32` // value can only be expressed in WGSL using AbstractInt and // a unary negation operator. if value == i32::MIN { write!(self.out, "i32({value})")?; } else { write!(self.out, "{value}i")?; } } crate::Literal::Bool(value) => write!(self.out, "{value}")?, crate::Literal::F64(value) => write!(self.out, "{value:?}lf")?, crate::Literal::I64(value) => { // `-9223372036854775808li` is not valid WGSL. Nor can we simply use the // AbstractInt trick above, as AbstractInt also cannot represent // `9223372036854775808`. Instead construct the second most negative // AbstractInt, subtract one from it, then cast to i64. if value == i64::MIN { write!(self.out, "i64({} - 1)", value + 1)?; } else { write!(self.out, "{value}li")?; } } crate::Literal::U64(value) => write!(self.out, "{value:?}lu")?, crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { return Err(Error::Custom( "Abstract types should not appear in IR presented to backends".into(), )); } }, Expression::Constant(handle) => { let constant = &module.constants[handle]; if constant.name.is_some() { write!(self.out, "{}", self.names[&NameKey::Constant(handle)])?; } else { self.write_const_expression(module, constant.init, &module.global_expressions)?; } } Expression::ZeroValue(ty) => { self.write_type(module, ty)?; write!(self.out, "()")?; } Expression::Compose { ty, ref components } => { self.write_type(module, ty)?; write!(self.out, "(")?; for (index, component) in components.iter().enumerate() { if index != 0 { write!(self.out, ", ")?; } write_expression(self, *component)?; } write!(self.out, ")")? } Expression::Splat { size, value } => { let size = common::vector_size_str(size); write!(self.out, "vec{size}(")?; write_expression(self, value)?; write!(self.out, ")")?; } Expression::Override(handle) => { write!(self.out, "{}", self.names[&NameKey::Override(handle)])?; } _ => unreachable!(), } Ok(()) } /// Write the 'plain form' of `expr`. /// /// An expression's 'plain form' is the most general rendition of that /// expression into WGSL, lacking `&` or `*` operators. The plain forms of /// `LocalVariable(x)` and `GlobalVariable(g)` are simply `x` and `g`. Such /// Naga expressions represent both WGSL pointers and references; it's the /// caller's responsibility to distinguish those cases appropriately. fn write_expr_plain_form( &mut self, module: &Module, expr: Handle, func_ctx: &back::FunctionCtx<'_>, indirection: Indirection, ) -> BackendResult { use crate::Expression; if let Some(name) = self.named_expressions.get(&expr) { write!(self.out, "{name}")?; return Ok(()); } let expression = &func_ctx.expressions[expr]; // Write the plain WGSL form of a Naga expression. // // The plain form of `LocalVariable` and `GlobalVariable` expressions is // simply the variable name; `*` and `&` operators are never emitted. // // The plain form of `Access` and `AccessIndex` expressions are WGSL // `postfix_expression` forms for member/component access and // subscripting. match *expression { Expression::Literal(_) | Expression::Constant(_) | Expression::ZeroValue(_) | Expression::Compose { .. } | Expression::Splat { .. } => { self.write_possibly_const_expression( module, expr, func_ctx.expressions, |writer, expr| writer.write_expr(module, expr, func_ctx), )?; } Expression::Override(handle) => { write!(self.out, "{}", self.names[&NameKey::Override(handle)])?; } Expression::FunctionArgument(pos) => { let name_key = func_ctx.argument_key(pos); let name = &self.names[&name_key]; write!(self.out, "{name}")?; } Expression::Binary { op, left, right } => { write!(self.out, "(")?; self.write_expr(module, left, func_ctx)?; write!(self.out, " {} ", back::binary_operation_str(op))?; self.write_expr(module, right, func_ctx)?; write!(self.out, ")")?; } Expression::Access { base, index } => { self.write_expr_with_indirection(module, base, func_ctx, indirection)?; write!(self.out, "[")?; self.write_expr(module, index, func_ctx)?; write!(self.out, "]")? } Expression::AccessIndex { base, index } => { let base_ty_res = &func_ctx.info[base].ty; let mut resolved = base_ty_res.inner_with(&module.types); self.write_expr_with_indirection(module, base, func_ctx, indirection)?; let base_ty_handle = match *resolved { TypeInner::Pointer { base, space: _ } => { resolved = &module.types[base].inner; Some(base) } _ => base_ty_res.handle(), }; match *resolved { TypeInner::Vector { .. } => { // Write vector access as a swizzle write!(self.out, ".{}", back::COMPONENTS[index as usize])? } TypeInner::Matrix { .. } | TypeInner::Array { .. } | TypeInner::BindingArray { .. } | TypeInner::ValuePointer { .. } => write!(self.out, "[{index}]")?, TypeInner::Struct { .. } => { // This will never panic in case the type is a `Struct`, this is not true // for other types so we can only check while inside this match arm let ty = base_ty_handle.unwrap(); write!( self.out, ".{}", &self.names[&NameKey::StructMember(ty, index)] )? } ref other => return Err(Error::Custom(format!("Cannot index {other:?}"))), } } Expression::ImageSample { image, sampler, gather: None, coordinate, array_index, offset, level, depth_ref, clamp_to_edge, } => { use crate::SampleLevel as Sl; let suffix_cmp = match depth_ref { Some(_) => "Compare", None => "", }; let suffix_level = match level { Sl::Auto => "", Sl::Zero if clamp_to_edge => "BaseClampToEdge", Sl::Zero | Sl::Exact(_) => "Level", Sl::Bias(_) => "Bias", Sl::Gradient { .. } => "Grad", }; write!(self.out, "textureSample{suffix_cmp}{suffix_level}(")?; self.write_expr(module, image, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, sampler, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, coordinate, func_ctx)?; if let Some(array_index) = array_index { write!(self.out, ", ")?; self.write_expr(module, array_index, func_ctx)?; } if let Some(depth_ref) = depth_ref { write!(self.out, ", ")?; self.write_expr(module, depth_ref, func_ctx)?; } match level { Sl::Auto => {} Sl::Zero => { // Level 0 is implied for depth comparison and BaseClampToEdge if depth_ref.is_none() && !clamp_to_edge { write!(self.out, ", 0.0")?; } } Sl::Exact(expr) => { write!(self.out, ", ")?; self.write_expr(module, expr, func_ctx)?; } Sl::Bias(expr) => { write!(self.out, ", ")?; self.write_expr(module, expr, func_ctx)?; } Sl::Gradient { x, y } => { write!(self.out, ", ")?; self.write_expr(module, x, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, y, func_ctx)?; } } if let Some(offset) = offset { write!(self.out, ", ")?; self.write_const_expression(module, offset, func_ctx.expressions)?; } write!(self.out, ")")?; } Expression::ImageSample { image, sampler, gather: Some(component), coordinate, array_index, offset, level: _, depth_ref, clamp_to_edge: _, } => { let suffix_cmp = match depth_ref { Some(_) => "Compare", None => "", }; write!(self.out, "textureGather{suffix_cmp}(")?; match *func_ctx.resolve_type(image, &module.types) { TypeInner::Image { class: crate::ImageClass::Depth { multi: _ }, .. } => {} _ => { write!(self.out, "{}, ", component as u8)?; } } self.write_expr(module, image, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, sampler, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, coordinate, func_ctx)?; if let Some(array_index) = array_index { write!(self.out, ", ")?; self.write_expr(module, array_index, func_ctx)?; } if let Some(depth_ref) = depth_ref { write!(self.out, ", ")?; self.write_expr(module, depth_ref, func_ctx)?; } if let Some(offset) = offset { write!(self.out, ", ")?; self.write_const_expression(module, offset, func_ctx.expressions)?; } write!(self.out, ")")?; } Expression::ImageQuery { image, query } => { use crate::ImageQuery as Iq; let texture_function = match query { Iq::Size { .. } => "textureDimensions", Iq::NumLevels => "textureNumLevels", Iq::NumLayers => "textureNumLayers", Iq::NumSamples => "textureNumSamples", }; write!(self.out, "{texture_function}(")?; self.write_expr(module, image, func_ctx)?; if let Iq::Size { level: Some(level) } = query { write!(self.out, ", ")?; self.write_expr(module, level, func_ctx)?; }; write!(self.out, ")")?; } Expression::ImageLoad { image, coordinate, array_index, sample, level, } => { write!(self.out, "textureLoad(")?; self.write_expr(module, image, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, coordinate, func_ctx)?; if let Some(array_index) = array_index { write!(self.out, ", ")?; self.write_expr(module, array_index, func_ctx)?; } if let Some(index) = sample.or(level) { write!(self.out, ", ")?; self.write_expr(module, index, func_ctx)?; } write!(self.out, ")")?; } Expression::GlobalVariable(handle) => { let name = &self.names[&NameKey::GlobalVariable(handle)]; write!(self.out, "{name}")?; } Expression::As { expr, kind, convert, } => { let inner = func_ctx.resolve_type(expr, &module.types); match *inner { TypeInner::Matrix { columns, rows, scalar, } => { let scalar = crate::Scalar { kind, width: convert.unwrap_or(scalar.width), }; let scalar_kind_str = scalar.to_wgsl_if_implemented()?; write!( self.out, "mat{}x{}<{}>", common::vector_size_str(columns), common::vector_size_str(rows), scalar_kind_str )?; } TypeInner::Vector { size, scalar: crate::Scalar { width, .. }, } => { let scalar = crate::Scalar { kind, width: convert.unwrap_or(width), }; let vector_size_str = common::vector_size_str(size); let scalar_kind_str = scalar.to_wgsl_if_implemented()?; if convert.is_some() { write!(self.out, "vec{vector_size_str}<{scalar_kind_str}>")?; } else { write!(self.out, "bitcast>")?; } } TypeInner::Scalar(crate::Scalar { width, .. }) => { let scalar = crate::Scalar { kind, width: convert.unwrap_or(width), }; let scalar_kind_str = scalar.to_wgsl_if_implemented()?; if convert.is_some() { write!(self.out, "{scalar_kind_str}")? } else { write!(self.out, "bitcast<{scalar_kind_str}>")? } } _ => { return Err(Error::Unimplemented(format!( "write_expr expression::as {inner:?}" ))); } }; write!(self.out, "(")?; self.write_expr(module, expr, func_ctx)?; write!(self.out, ")")?; } Expression::Load { pointer } => { let is_atomic_pointer = func_ctx .resolve_type(pointer, &module.types) .is_atomic_pointer(&module.types); if is_atomic_pointer { write!(self.out, "atomicLoad(")?; self.write_expr(module, pointer, func_ctx)?; write!(self.out, ")")?; } else { self.write_expr_with_indirection( module, pointer, func_ctx, Indirection::Reference, )?; } } Expression::LocalVariable(handle) => { write!(self.out, "{}", self.names[&func_ctx.name_key(handle)])? } Expression::ArrayLength(expr) => { write!(self.out, "arrayLength(")?; self.write_expr(module, expr, func_ctx)?; write!(self.out, ")")?; } Expression::Math { fun, arg, arg1, arg2, arg3, } => { use crate::MathFunction as Mf; enum Function { Regular(&'static str), InversePolyfill(InversePolyfill), } let function = match fun.try_to_wgsl() { Some(name) => Function::Regular(name), None => match fun { Mf::Inverse => { let ty = func_ctx.resolve_type(arg, &module.types); let Some(overload) = InversePolyfill::find_overload(ty) else { return Err(Error::unsupported("math function", fun)); }; Function::InversePolyfill(overload) } _ => return Err(Error::unsupported("math function", fun)), }, }; match function { Function::Regular(fun_name) => { write!(self.out, "{fun_name}(")?; self.write_expr(module, arg, func_ctx)?; for arg in IntoIterator::into_iter([arg1, arg2, arg3]).flatten() { write!(self.out, ", ")?; self.write_expr(module, arg, func_ctx)?; } write!(self.out, ")")? } Function::InversePolyfill(inverse) => { write!(self.out, "{}(", inverse.fun_name)?; self.write_expr(module, arg, func_ctx)?; write!(self.out, ")")?; self.required_polyfills.insert(inverse); } } } Expression::Swizzle { size, vector, pattern, } => { self.write_expr(module, vector, func_ctx)?; write!(self.out, ".")?; for &sc in pattern[..size as usize].iter() { self.out.write_char(back::COMPONENTS[sc as usize])?; } } Expression::Unary { op, expr } => { let unary = match op { crate::UnaryOperator::Negate => "-", crate::UnaryOperator::LogicalNot => "!", crate::UnaryOperator::BitwiseNot => "~", }; write!(self.out, "{unary}(")?; self.write_expr(module, expr, func_ctx)?; write!(self.out, ")")? } Expression::Select { condition, accept, reject, } => { write!(self.out, "select(")?; self.write_expr(module, reject, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, accept, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, condition, func_ctx)?; write!(self.out, ")")? } Expression::Derivative { axis, ctrl, expr } => { use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; let op = match (axis, ctrl) { (Axis::X, Ctrl::Coarse) => "dpdxCoarse", (Axis::X, Ctrl::Fine) => "dpdxFine", (Axis::X, Ctrl::None) => "dpdx", (Axis::Y, Ctrl::Coarse) => "dpdyCoarse", (Axis::Y, Ctrl::Fine) => "dpdyFine", (Axis::Y, Ctrl::None) => "dpdy", (Axis::Width, Ctrl::Coarse) => "fwidthCoarse", (Axis::Width, Ctrl::Fine) => "fwidthFine", (Axis::Width, Ctrl::None) => "fwidth", }; write!(self.out, "{op}(")?; self.write_expr(module, expr, func_ctx)?; write!(self.out, ")")? } Expression::Relational { fun, argument } => { use crate::RelationalFunction as Rf; let fun_name = match fun { Rf::All => "all", Rf::Any => "any", _ => return Err(Error::UnsupportedRelationalFunction(fun)), }; write!(self.out, "{fun_name}(")?; self.write_expr(module, argument, func_ctx)?; write!(self.out, ")")? } // Not supported yet Expression::RayQueryGetIntersection { .. } | Expression::RayQueryVertexPositions { .. } => unreachable!(), // Nothing to do here, since call expression already cached Expression::CallResult(_) | Expression::AtomicResult { .. } | Expression::RayQueryProceedResult | Expression::SubgroupBallotResult | Expression::SubgroupOperationResult { .. } | Expression::WorkGroupUniformLoadResult { .. } => {} Expression::CooperativeLoad { columns, rows, role, ref data, } => { let suffix = if data.row_major { "T" } else { "" }; let scalar = func_ctx.info[data.pointer] .ty .inner_with(&module.types) .pointer_base_type() .unwrap() .inner_with(&module.types) .scalar() .unwrap(); write!( self.out, "coopLoad{suffix}>(", columns as u32, rows as u32, scalar.try_to_wgsl().unwrap(), role, )?; self.write_expr(module, data.pointer, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, data.stride, func_ctx)?; write!(self.out, ")")?; } Expression::CooperativeMultiplyAdd { a, b, c } => { write!(self.out, "coopMultiplyAdd(")?; self.write_expr(module, a, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, b, func_ctx)?; write!(self.out, ", ")?; self.write_expr(module, c, func_ctx)?; write!(self.out, ")")?; } } Ok(()) } /// Helper method used to write global variables /// # Notes /// Always adds a newline fn write_global( &mut self, module: &Module, global: &crate::GlobalVariable, handle: Handle, ) -> BackendResult { // Write group and binding attributes if present if let Some(ref binding) = global.binding { self.write_attributes(&[ Attribute::Group(binding.group), Attribute::Binding(binding.binding), ])?; writeln!(self.out)?; } if global .memory_decorations .contains(crate::MemoryDecorations::COHERENT) { write!(self.out, "@coherent ")?; } if global .memory_decorations .contains(crate::MemoryDecorations::VOLATILE) { write!(self.out, "@volatile ")?; } // First write global name and address space if supported write!(self.out, "var")?; let (address, maybe_access) = address_space_str(global.space); if let Some(space) = address { write!(self.out, "<{space}")?; if let Some(access) = maybe_access { write!(self.out, ", {access}")?; } write!(self.out, ">")?; } write!( self.out, " {}: ", &self.names[&NameKey::GlobalVariable(handle)] )?; // Write global type self.write_type(module, global.ty)?; // Write initializer if let Some(init) = global.init { write!(self.out, " = ")?; self.write_const_expression(module, init, &module.global_expressions)?; } // End with semicolon writeln!(self.out, ";")?; Ok(()) } /// Helper method used to write global constants /// /// # Notes /// Ends in a newline fn write_global_constant( &mut self, module: &Module, handle: Handle, ) -> BackendResult { let name = &self.names[&NameKey::Constant(handle)]; // First write only constant name write!(self.out, "const {name}: ")?; self.write_type(module, module.constants[handle].ty)?; write!(self.out, " = ")?; let init = module.constants[handle].init; self.write_const_expression(module, init, &module.global_expressions)?; writeln!(self.out, ";")?; Ok(()) } /// Helper method used to write overrides /// /// # Notes /// Ends in a newline fn write_override( &mut self, module: &Module, handle: Handle, ) -> BackendResult { let override_ = &module.overrides[handle]; let name = &self.names[&NameKey::Override(handle)]; // Write @id attribute if present if let Some(id) = override_.id { write!(self.out, "@id({id}) ")?; } // Write override declaration write!(self.out, "override {name}: ")?; self.write_type(module, override_.ty)?; // Write initializer if present if let Some(init) = override_.init { write!(self.out, " = ")?; self.write_const_expression(module, init, &module.global_expressions)?; } writeln!(self.out, ";")?; Ok(()) } // See https://github.com/rust-lang/rust-clippy/issues/4979. pub fn finish(self) -> W { self.out } } struct WriterTypeContext<'m> { module: &'m Module, names: &'m crate::FastHashMap, } impl TypeContext for WriterTypeContext<'_> { fn lookup_type(&self, handle: Handle) -> &crate::Type { &self.module.types[handle] } fn type_name(&self, handle: Handle) -> &str { self.names[&NameKey::Type(handle)].as_str() } fn write_unnamed_struct(&self, _: &TypeInner, _: &mut W) -> core::fmt::Result { unreachable!("the WGSL back end should always provide type handles"); } fn write_override( &self, handle: Handle, out: &mut W, ) -> core::fmt::Result { write!(out, "{}", self.names[&NameKey::Override(handle)]) } fn write_non_wgsl_inner(&self, _: &TypeInner, _: &mut W) -> core::fmt::Result { unreachable!("backends should only be passed validated modules"); } fn write_non_wgsl_scalar(&self, _: crate::Scalar, _: &mut W) -> core::fmt::Result { unreachable!("backends should only be passed validated modules"); } } fn map_binding_to_attribute(binding: &crate::Binding) -> Vec { match *binding { crate::Binding::BuiltIn(built_in) => { if let crate::BuiltIn::Position { invariant: true } = built_in { vec![Attribute::BuiltIn(built_in), Attribute::Invariant] } else { vec![Attribute::BuiltIn(built_in)] } } crate::Binding::Location { location, interpolation, sampling, blend_src: None, per_primitive, } => { let mut attrs = vec![ Attribute::Location(location), Attribute::Interpolate(interpolation, sampling), ]; if per_primitive { attrs.push(Attribute::PerPrimitive); } attrs } crate::Binding::Location { location, interpolation, sampling, blend_src: Some(blend_src), per_primitive, } => { let mut attrs = vec![ Attribute::Location(location), Attribute::BlendSrc(blend_src), Attribute::Interpolate(interpolation, sampling), ]; if per_primitive { attrs.push(Attribute::PerPrimitive); } attrs } } } ================================================ FILE: naga/src/common/diagnostic_debug.rs ================================================ //! Displaying Naga IR terms in debugging output. #[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))] use crate::common::wgsl::TypeContext; use crate::proc::TypeResolution; use crate::{Handle, Scalar, Type, TypeInner, UniqueArena}; use core::fmt; /// A wrapper for displaying Naga IR terms in debugging output. /// /// This is like [`DiagnosticDisplay`], but requires weaker context /// and produces correspondingly lower-fidelity output. For example, /// this cannot show the override names for override-sized array /// lengths. /// /// [`DiagnosticDisplay`]: super::DiagnosticDisplay pub struct DiagnosticDebug(pub T); impl fmt::Debug for DiagnosticDebug<(Handle, &UniqueArena)> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (handle, ctx) = self.0; #[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))] ctx.write_type(handle, f)?; #[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))] { let _ = ctx; write!(f, "{handle:?}")?; } Ok(()) } } impl fmt::Debug for DiagnosticDebug<(&TypeInner, &UniqueArena)> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (inner, ctx) = self.0; #[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))] ctx.write_type_inner(inner, f)?; #[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))] { let _ = ctx; write!(f, "{inner:?}")?; } Ok(()) } } impl fmt::Debug for DiagnosticDebug<(&TypeResolution, &UniqueArena)> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (resolution, ctx) = self.0; #[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))] ctx.write_type_resolution(resolution, f)?; #[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))] { let _ = ctx; write!(f, "{resolution:?}")?; } Ok(()) } } impl fmt::Debug for DiagnosticDebug { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let scalar = self.0; #[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))] f.write_str(&crate::common::wgsl::TryToWgsl::to_wgsl_for_diagnostics( scalar, ))?; #[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))] write!(f, "{scalar:?}")?; Ok(()) } } pub trait ForDebug: Sized { /// Format this type using [`core::fmt::Debug`]. /// /// Return a value that implements the [`core::fmt::Debug`] trait /// by displaying `self` in a language-appropriate way. For /// example: /// /// # use naga::common::ForDebug; /// # let scalar: naga::Scalar = naga::Scalar::F32; /// log::debug!("My scalar: {:?}", scalar.for_debug()); fn for_debug(self) -> DiagnosticDebug { DiagnosticDebug(self) } } impl ForDebug for Scalar {} pub trait ForDebugWithTypes: Sized { /// Format this type using [`core::fmt::Debug`]. /// /// Given an arena to look up type handles in, return a value that /// implements the [`core::fmt::Debug`] trait by displaying `self` /// in a language-appropriate way. For example: /// /// # use naga::{Span, Type, TypeInner, Scalar, UniqueArena}; /// # use naga::common::ForDebugWithTypes; /// # let mut types = UniqueArena::::default(); /// # let inner = TypeInner::Scalar(Scalar::F32); /// # let span = Span::UNDEFINED; /// # let handle = types.insert(Type { name: None, inner }, span); /// log::debug!("My type: {:?}", handle.for_debug(&types)); fn for_debug(self, types: &UniqueArena) -> DiagnosticDebug<(Self, &UniqueArena)> { DiagnosticDebug((self, types)) } } impl ForDebugWithTypes for Handle {} impl ForDebugWithTypes for &TypeInner {} impl ForDebugWithTypes for &TypeResolution {} ================================================ FILE: naga/src/common/diagnostic_display.rs ================================================ //! Displaying Naga IR terms in diagnostic output. use crate::proc::{GlobalCtx, Rule, TypeResolution}; use crate::{Handle, Scalar, Type}; #[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))] use crate::common::wgsl::TypeContext; use core::fmt; /// A wrapper for displaying Naga IR terms in diagnostic output. /// /// For some Naga IR type `T`, `DiagnosticDisplay` implements /// [`core::fmt::Display`] in a way that displays values of type `T` /// appropriately for diagnostic messages presented to human readers. /// /// For example, the implementation of [`Display`] for /// `DiagnosticDisplay` formats the type represented by the /// given [`Scalar`] appropriately for users. /// /// Some types like `Handle` require contextual information like /// a type arena to be displayed. In such cases, we implement [`Display`] /// for a type like `DiagnosticDisplay<(Handle, GlobalCtx)>`, where /// the [`GlobalCtx`] type provides the necessary context. /// /// Do not implement this type for [`TypeInner`], as that does not /// have enough information to display struct types correctly. /// /// If you only need debugging output, [`DiagnosticDebug`] uses /// easier-to-obtain context types but still does a good enough job /// for logging or debugging. /// /// [`Display`]: core::fmt::Display /// [`GlobalCtx`]: crate::proc::GlobalCtx /// [`TypeInner`]: crate::ir::TypeInner /// [`DiagnosticDebug`]: super::DiagnosticDebug /// /// ## Language-sensitive diagnostics /// /// Diagnostic output ought to depend on the source language from /// which the IR was produced: diagnostics resulting from processing /// GLSL code should use GLSL type syntax, for example. That means /// that `DiagnosticDisplay` ought to include some indication of which /// notation to use. /// /// For the moment, only WGSL output is implemented, so /// `DiagnosticDisplay` lacks any support for this (#7268). However, /// the plan is that all language-independent code in Naga should use /// `DiagnosticDisplay` wherever appropriate, such that when its /// definition is expanded to include some indication of the right /// source language to use, any use site that does not supply this /// indication will provoke a compile-time error. pub struct DiagnosticDisplay(pub T); impl fmt::Display for DiagnosticDisplay<(&TypeResolution, GlobalCtx<'_>)> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (resolution, ctx) = self.0; #[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))] ctx.write_type_resolution(resolution, f)?; #[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))] { let _ = ctx; write!(f, "{resolution:?}")?; } Ok(()) } } impl fmt::Display for DiagnosticDisplay<(Handle, GlobalCtx<'_>)> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (handle, ref ctx) = self.0; #[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))] ctx.write_type(handle, f)?; #[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))] { let _ = ctx; write!(f, "{handle:?}")?; } Ok(()) } } impl fmt::Display for DiagnosticDisplay<(&str, &Rule, GlobalCtx<'_>)> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (name, rule, ref ctx) = self.0; #[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))] ctx.write_type_rule(name, rule, f)?; #[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))] { let _ = ctx; write!(f, "{name}({:?}) -> {:?}", rule.arguments, rule.conclusion)?; } Ok(()) } } impl fmt::Display for DiagnosticDisplay { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let scalar = self.0; #[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))] f.write_str(&crate::common::wgsl::TryToWgsl::to_wgsl_for_diagnostics( scalar, ))?; #[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))] write!(f, "{scalar:?}")?; Ok(()) } } ================================================ FILE: naga/src/common/mod.rs ================================================ //! Code common to the front and backends for specific languages. mod diagnostic_debug; mod diagnostic_display; pub mod predeclared; pub mod wgsl; pub use diagnostic_debug::{DiagnosticDebug, ForDebug, ForDebugWithTypes}; pub use diagnostic_display::DiagnosticDisplay; // Re-exported here for backwards compatibility pub use super::proc::vector_size_str; ================================================ FILE: naga/src/common/predeclared.rs ================================================ //! Generating names for predeclared types. use crate::ir; use alloc::format; use alloc::string::String; impl ir::PredeclaredType { pub fn struct_name(&self) -> String { use crate::PredeclaredType as Pt; match *self { Pt::AtomicCompareExchangeWeakResult(scalar) => { format!( "__atomic_compare_exchange_result<{:?},{}>", scalar.kind, scalar.width, ) } Pt::ModfResult { size, scalar } => frexp_mod_name("modf", size, scalar), Pt::FrexpResult { size, scalar } => frexp_mod_name("frexp", size, scalar), } } } fn frexp_mod_name(function: &str, size: Option, scalar: ir::Scalar) -> String { let bits = 8 * scalar.width; match size { Some(size) => { let size = size as u8; format!("__{function}_result_vec{size}_f{bits}") } None => format!("__{function}_result_f{bits}"), } } ================================================ FILE: naga/src/common/wgsl/diagnostics.rs ================================================ //! WGSL diagnostic filters and severities. use core::fmt::{self, Display, Formatter}; use crate::diagnostic_filter::{ FilterableTriggeringRule, Severity, StandardFilterableTriggeringRule, }; impl Severity { const ERROR: &'static str = "error"; const WARNING: &'static str = "warning"; const INFO: &'static str = "info"; const OFF: &'static str = "off"; /// Convert from a sentinel word in WGSL into its associated [`Severity`], if possible. pub fn from_wgsl_ident(s: &str) -> Option { Some(match s { Self::ERROR => Self::Error, Self::WARNING => Self::Warning, Self::INFO => Self::Info, Self::OFF => Self::Off, _ => return None, }) } } pub struct DisplayFilterableTriggeringRule<'a>(&'a FilterableTriggeringRule); impl Display for DisplayFilterableTriggeringRule<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let &Self(inner) = self; match *inner { FilterableTriggeringRule::Standard(rule) => write!(f, "{}", rule.to_wgsl_ident()), FilterableTriggeringRule::Unknown(ref rule) => write!(f, "{rule}"), FilterableTriggeringRule::User(ref rules) => { let &[ref seg1, ref seg2] = rules.as_ref(); write!(f, "{seg1}.{seg2}") } } } } impl FilterableTriggeringRule { /// [`Display`] this rule's identifiers in WGSL. pub const fn display_wgsl_ident(&self) -> impl Display + '_ { DisplayFilterableTriggeringRule(self) } } impl StandardFilterableTriggeringRule { const DERIVATIVE_UNIFORMITY: &'static str = "derivative_uniformity"; /// Convert from a sentinel word in WGSL into its associated /// [`StandardFilterableTriggeringRule`], if possible. pub fn from_wgsl_ident(s: &str) -> Option { Some(match s { Self::DERIVATIVE_UNIFORMITY => Self::DerivativeUniformity, _ => return None, }) } /// Maps this [`StandardFilterableTriggeringRule`] into the sentinel word associated with it in /// WGSL. pub const fn to_wgsl_ident(self) -> &'static str { match self { Self::DerivativeUniformity => Self::DERIVATIVE_UNIFORMITY, } } } ================================================ FILE: naga/src/common/wgsl/mod.rs ================================================ //! Code shared between the WGSL front and back ends. mod diagnostics; mod to_wgsl; mod types; pub use diagnostics::DisplayFilterableTriggeringRule; pub use to_wgsl::{address_space_str, ToWgsl, TryToWgsl}; pub use types::TypeContext; ================================================ FILE: naga/src/common/wgsl/to_wgsl.rs ================================================ //! Generating WGSL source code for Naga IR types. use alloc::format; use alloc::string::{String, ToString}; /// Types that can return the WGSL source representation of their /// values as a `'static` string. /// /// This trait is specifically for types whose WGSL forms are simple /// enough that they can always be returned as a static string. /// /// - If only some values have a WGSL representation, consider /// implementing [`TryToWgsl`] instead. /// /// - If a type's WGSL form requires dynamic formatting, so that /// returning a `&'static str` isn't feasible, consider implementing /// [`core::fmt::Display`] on some wrapper type instead. pub trait ToWgsl: Sized { /// Return WGSL source code representation of `self`. fn to_wgsl(self) -> &'static str; } /// Types that may be able to return the WGSL source representation /// for their values as a `'static` string. /// /// This trait is specifically for types whose values are either /// simple enough that their WGSL form can be represented a static /// string, or aren't representable in WGSL at all. /// /// - If all values in the type have `&'static str` representations in /// WGSL, consider implementing [`ToWgsl`] instead. /// /// - If a type's WGSL form requires dynamic formatting, so that /// returning a `&'static str` isn't feasible, consider implementing /// [`core::fmt::Display`] on some wrapper type instead. pub trait TryToWgsl: Sized { /// Return the WGSL form of `self` as a `'static` string. /// /// If `self` doesn't have a representation in WGSL (standard or /// as extended by Naga), then return `None`. fn try_to_wgsl(self) -> Option<&'static str>; /// What kind of WGSL thing `Self` represents. const DESCRIPTION: &'static str; /// Return the WGSL form of `self` as appropriate for diagnostics. /// /// If `self` can be expressed in WGSL, return that form as a /// [`String`]. Otherwise, return some representation of `self` /// that is appropriate for use in diagnostic messages. /// /// The default implementation of this function falls back to /// `self`'s [`Debug`] form. /// /// [`Debug`]: core::fmt::Debug fn to_wgsl_for_diagnostics(self) -> String where Self: core::fmt::Debug + Copy, { match self.try_to_wgsl() { Some(static_string) => static_string.to_string(), None => format!("{{non-WGSL {} {self:?}}}", Self::DESCRIPTION), } } } impl TryToWgsl for crate::MathFunction { const DESCRIPTION: &'static str = "math function"; fn try_to_wgsl(self) -> Option<&'static str> { use crate::MathFunction as Mf; Some(match self { Mf::Abs => "abs", Mf::Min => "min", Mf::Max => "max", Mf::Clamp => "clamp", Mf::Saturate => "saturate", Mf::Cos => "cos", Mf::Cosh => "cosh", Mf::Sin => "sin", Mf::Sinh => "sinh", Mf::Tan => "tan", Mf::Tanh => "tanh", Mf::Acos => "acos", Mf::Asin => "asin", Mf::Atan => "atan", Mf::Atan2 => "atan2", Mf::Asinh => "asinh", Mf::Acosh => "acosh", Mf::Atanh => "atanh", Mf::Radians => "radians", Mf::Degrees => "degrees", Mf::Ceil => "ceil", Mf::Floor => "floor", Mf::Round => "round", Mf::Fract => "fract", Mf::Trunc => "trunc", Mf::Modf => "modf", Mf::Frexp => "frexp", Mf::Ldexp => "ldexp", Mf::Exp => "exp", Mf::Exp2 => "exp2", Mf::Log => "log", Mf::Log2 => "log2", Mf::Pow => "pow", Mf::Dot => "dot", Mf::Dot4I8Packed => "dot4I8Packed", Mf::Dot4U8Packed => "dot4U8Packed", Mf::Cross => "cross", Mf::Distance => "distance", Mf::Length => "length", Mf::Normalize => "normalize", Mf::FaceForward => "faceForward", Mf::Reflect => "reflect", Mf::Refract => "refract", Mf::Sign => "sign", Mf::Fma => "fma", Mf::Mix => "mix", Mf::Step => "step", Mf::SmoothStep => "smoothstep", Mf::Sqrt => "sqrt", Mf::InverseSqrt => "inverseSqrt", Mf::Transpose => "transpose", Mf::Determinant => "determinant", Mf::QuantizeToF16 => "quantizeToF16", Mf::CountTrailingZeros => "countTrailingZeros", Mf::CountLeadingZeros => "countLeadingZeros", Mf::CountOneBits => "countOneBits", Mf::ReverseBits => "reverseBits", Mf::ExtractBits => "extractBits", Mf::InsertBits => "insertBits", Mf::FirstTrailingBit => "firstTrailingBit", Mf::FirstLeadingBit => "firstLeadingBit", Mf::Pack4x8snorm => "pack4x8snorm", Mf::Pack4x8unorm => "pack4x8unorm", Mf::Pack2x16snorm => "pack2x16snorm", Mf::Pack2x16unorm => "pack2x16unorm", Mf::Pack2x16float => "pack2x16float", Mf::Pack4xI8 => "pack4xI8", Mf::Pack4xU8 => "pack4xU8", Mf::Pack4xI8Clamp => "pack4xI8Clamp", Mf::Pack4xU8Clamp => "pack4xU8Clamp", Mf::Unpack4x8snorm => "unpack4x8snorm", Mf::Unpack4x8unorm => "unpack4x8unorm", Mf::Unpack2x16snorm => "unpack2x16snorm", Mf::Unpack2x16unorm => "unpack2x16unorm", Mf::Unpack2x16float => "unpack2x16float", Mf::Unpack4xI8 => "unpack4xI8", Mf::Unpack4xU8 => "unpack4xU8", // Non-standard math functions. Mf::Inverse | Mf::Outer => return None, }) } } impl TryToWgsl for crate::BuiltIn { const DESCRIPTION: &'static str = "builtin value"; fn try_to_wgsl(self) -> Option<&'static str> { use crate::BuiltIn as Bi; Some(match self { Bi::Position { .. } => "position", Bi::ViewIndex => "view_index", Bi::InstanceIndex => "instance_index", Bi::VertexIndex => "vertex_index", Bi::ClipDistances => "clip_distances", Bi::FragDepth => "frag_depth", Bi::FrontFacing => "front_facing", Bi::PrimitiveIndex => "primitive_index", Bi::DrawIndex => "draw_index", Bi::Barycentric { perspective: true } => "barycentric", Bi::Barycentric { perspective: false } => "barycentric_no_perspective", Bi::SampleIndex => "sample_index", Bi::SampleMask => "sample_mask", Bi::GlobalInvocationId => "global_invocation_id", Bi::LocalInvocationId => "local_invocation_id", Bi::LocalInvocationIndex => "local_invocation_index", Bi::WorkGroupId => "workgroup_id", Bi::NumWorkGroups => "num_workgroups", Bi::NumSubgroups => "num_subgroups", Bi::SubgroupId => "subgroup_id", Bi::SubgroupSize => "subgroup_size", Bi::SubgroupInvocationId => "subgroup_invocation_id", // Non-standard built-ins. Bi::MeshTaskSize => "mesh_task_size", Bi::TriangleIndices => "triangle_indices", Bi::LineIndices => "line_indices", Bi::PointIndex => "point_index", Bi::Vertices => "vertices", Bi::Primitives => "primitives", Bi::VertexCount => "vertex_count", Bi::PrimitiveCount => "primitive_count", Bi::CullPrimitive => "cull_primitive", Bi::RayInvocationId => "ray_invocation_id", Bi::NumRayInvocations => "num_ray_invocations", Bi::InstanceCustomData => "instance_custom_data", Bi::GeometryIndex => "geometry_index", Bi::WorldRayOrigin => "world_ray_origin", Bi::WorldRayDirection => "world_ray_direction", Bi::ObjectRayOrigin => "object_ray_origin", Bi::ObjectRayDirection => "object_ray_direction", Bi::RayTmin => "ray_t_min", Bi::RayTCurrentMax => "ray_t_current_max", Bi::ObjectToWorld => "object_to_world", Bi::WorldToObject => "world_to_object", Bi::HitKind => "hit_kind", Bi::BaseInstance | Bi::BaseVertex | Bi::CullDistance | Bi::PointSize | Bi::PointCoord | Bi::WorkGroupSize => return None, }) } } impl ToWgsl for crate::Interpolation { fn to_wgsl(self) -> &'static str { match self { crate::Interpolation::Perspective => "perspective", crate::Interpolation::Linear => "linear", crate::Interpolation::Flat => "flat", crate::Interpolation::PerVertex => "per_vertex", } } } impl ToWgsl for crate::Sampling { fn to_wgsl(self) -> &'static str { match self { crate::Sampling::Center => "center", crate::Sampling::Centroid => "centroid", crate::Sampling::Sample => "sample", crate::Sampling::First => "first", crate::Sampling::Either => "either", } } } impl ToWgsl for crate::StorageFormat { fn to_wgsl(self) -> &'static str { use crate::StorageFormat as Sf; match self { Sf::R8Unorm => "r8unorm", Sf::R8Snorm => "r8snorm", Sf::R8Uint => "r8uint", Sf::R8Sint => "r8sint", Sf::R16Uint => "r16uint", Sf::R16Sint => "r16sint", Sf::R16Float => "r16float", Sf::Rg8Unorm => "rg8unorm", Sf::Rg8Snorm => "rg8snorm", Sf::Rg8Uint => "rg8uint", Sf::Rg8Sint => "rg8sint", Sf::R32Uint => "r32uint", Sf::R32Sint => "r32sint", Sf::R32Float => "r32float", Sf::Rg16Uint => "rg16uint", Sf::Rg16Sint => "rg16sint", Sf::Rg16Float => "rg16float", Sf::Rgba8Unorm => "rgba8unorm", Sf::Rgba8Snorm => "rgba8snorm", Sf::Rgba8Uint => "rgba8uint", Sf::Rgba8Sint => "rgba8sint", Sf::Bgra8Unorm => "bgra8unorm", Sf::Rgb10a2Uint => "rgb10a2uint", Sf::Rgb10a2Unorm => "rgb10a2unorm", Sf::Rg11b10Ufloat => "rg11b10ufloat", Sf::R64Uint => "r64uint", Sf::Rg32Uint => "rg32uint", Sf::Rg32Sint => "rg32sint", Sf::Rg32Float => "rg32float", Sf::Rgba16Uint => "rgba16uint", Sf::Rgba16Sint => "rgba16sint", Sf::Rgba16Float => "rgba16float", Sf::Rgba32Uint => "rgba32uint", Sf::Rgba32Sint => "rgba32sint", Sf::Rgba32Float => "rgba32float", Sf::R16Unorm => "r16unorm", Sf::R16Snorm => "r16snorm", Sf::Rg16Unorm => "rg16unorm", Sf::Rg16Snorm => "rg16snorm", Sf::Rgba16Unorm => "rgba16unorm", Sf::Rgba16Snorm => "rgba16snorm", } } } impl TryToWgsl for crate::Scalar { const DESCRIPTION: &'static str = "scalar type"; fn try_to_wgsl(self) -> Option<&'static str> { use crate::Scalar; Some(match self { Scalar::F16 => "f16", Scalar::F32 => "f32", Scalar::F64 => "f64", Scalar::I32 => "i32", Scalar::U32 => "u32", Scalar::I64 => "i64", Scalar::U64 => "u64", Scalar::BOOL => "bool", _ => return None, }) } fn to_wgsl_for_diagnostics(self) -> String { match self.try_to_wgsl() { Some(static_string) => static_string.to_string(), None => match self.kind { crate::ScalarKind::Sint | crate::ScalarKind::Uint | crate::ScalarKind::Float | crate::ScalarKind::Bool => format!("{{non-WGSL scalar {self:?}}}"), crate::ScalarKind::AbstractInt => "{AbstractInt}".to_string(), crate::ScalarKind::AbstractFloat => "{AbstractFloat}".to_string(), }, } } } impl ToWgsl for crate::CooperativeRole { fn to_wgsl(self) -> &'static str { match self { Self::A => "A", Self::B => "B", Self::C => "C", } } } impl ToWgsl for crate::ImageDimension { fn to_wgsl(self) -> &'static str { match self { Self::D1 => "1d", Self::D2 => "2d", Self::D3 => "3d", Self::Cube => "cube", } } } /// Return the WGSL address space and access mode strings for `space`. /// /// Why don't we implement [`ToWgsl`] for [`AddressSpace`]? /// /// In WGSL, the full form of a pointer type is `ptr`, where: /// - `AS` is the address space, /// - `T` is the store type, and /// - `AM` is the access mode. /// /// Since the type `T` intervenes between the address space and the /// access mode, there isn't really any individual WGSL grammar /// production that corresponds to an [`AddressSpace`], so [`ToWgsl`] /// is too simple-minded for this case. /// /// Furthermore, we want to write `var` for most address /// spaces, but we want to just write `var foo: T` for handle types. /// /// [`AddressSpace`]: crate::AddressSpace pub const fn address_space_str( space: crate::AddressSpace, ) -> (Option<&'static str>, Option<&'static str>) { use crate::AddressSpace as As; ( Some(match space { As::Private => "private", As::Uniform => "uniform", As::Storage { access } => { if access.contains(crate::StorageAccess::ATOMIC) { return (Some("storage"), Some("atomic")); } else if access.contains(crate::StorageAccess::STORE) { return (Some("storage"), Some("read_write")); } else { "storage" } } As::Immediate => "immediate", As::WorkGroup => "workgroup", As::Handle => return (None, None), As::Function => "function", As::TaskPayload => "task_payload", As::IncomingRayPayload => "incoming_ray_payload", As::RayPayload => "ray_payload", }), None, ) } ================================================ FILE: naga/src/common/wgsl/types.rs ================================================ //! Code for formatting Naga IR types as WGSL source code. use super::{address_space_str, ToWgsl, TryToWgsl}; use crate::common; use crate::proc::TypeResolution; use crate::{Handle, Scalar, TypeInner}; use alloc::string::String; use core::fmt::Write; /// A context for printing Naga IR types as WGSL. /// /// This trait's default methods [`write_type`] and /// [`write_type_inner`] do the work of formatting types as WGSL. /// Implementors must provide the remaining methods, to customize /// behavior for the context at hand. /// /// For example, the WGSL backend would provide an implementation of /// [`type_name`] that handles hygienic renaming, whereas the WGSL /// front end would simply show the name that was given in the source. /// /// [`write_type`]: TypeContext::write_type /// [`write_type_inner`]: TypeContext::write_type_inner /// [`type_name`]: TypeContext::type_name pub trait TypeContext { /// Return the [`Type`] referred to by `handle`. /// /// [`Type`]: crate::Type fn lookup_type(&self, handle: Handle) -> &crate::Type; /// Return the name to be used for the type referred to by /// `handle`. fn type_name(&self, handle: Handle) -> &str; /// Write the WGSL form of `override` to `out`. fn write_override( &self, r#override: Handle, out: &mut W, ) -> core::fmt::Result; /// Write a [`TypeInner::Struct`] for which we are unable to find a name. /// /// The names of struct types are only available if we have `Handle`, /// not from [`TypeInner`]. For logging and debugging, it's fine to just /// write something helpful to the developer, but for generating WGSL, /// this should be unreachable. fn write_unnamed_struct(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result; /// Write a [`TypeInner`] that has no representation as WGSL source, /// even including Naga extensions. /// /// A backend might implement this with a call to the [`unreachable!`] /// macro, since backends are allowed to assume that the module has passed /// validation. /// /// The default implementation is appropriate for generating type names to /// appear in error messages. It punts to `TypeInner`'s [`core::fmt::Debug`] /// implementation, since it's probably best to show the user something they /// can act on. fn write_non_wgsl_inner(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result { write!(out, "{{non-WGSL Naga type {inner:?}}}") } /// Write a [`Scalar`] that has no representation as WGSL source, /// even including Naga extensions. /// /// A backend might implement this with a call to the [`unreachable!`] /// macro, since backends are allowed to assume that the module has passed /// validation. /// /// The default implementation is appropriate for generating type names to /// appear in error messages. It punts to `Scalar`'s [`core::fmt::Debug`] /// implementation, since it's probably best to show the user something they /// can act on. fn write_non_wgsl_scalar(&self, scalar: Scalar, out: &mut W) -> core::fmt::Result { match scalar.kind { crate::ScalarKind::Sint | crate::ScalarKind::Uint | crate::ScalarKind::Float | crate::ScalarKind::Bool => write!(out, "{{non-WGSL Naga scalar {scalar:?}}}"), // The abstract types are kind of an odd quasi-WGSL category: // they are definitely part of the spec, but they are not expressible // in WGSL itself. So we want to call them out by name in error messages, // but the WGSL backend should never generate these. crate::ScalarKind::AbstractInt => out.write_str("{AbstractInt}"), crate::ScalarKind::AbstractFloat => out.write_str("{AbstractFloat}"), } } /// Write the type `ty` as it would appear in a value's declaration. /// /// Write the type referred to by `ty` in `module` as it would appear in /// a `var`, `let`, etc. declaration, or in a function's argument list. fn write_type(&self, handle: Handle, out: &mut W) -> core::fmt::Result { let ty = self.lookup_type(handle); match ty.inner { TypeInner::Struct { .. } => out.write_str(self.type_name(handle))?, ref other => self.write_type_inner(other, out)?, } Ok(()) } /// Write the [`TypeInner`] `inner` as it would appear in a value's declaration. /// /// Write `inner` as it would appear in a `var`, `let`, etc. /// declaration, or in a function's argument list. /// /// Note that this cannot handle writing [`Struct`] types: those /// must be referred to by name, but the name isn't available in /// [`TypeInner`]. /// /// [`Struct`]: TypeInner::Struct fn write_type_inner(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result { match try_write_type_inner(self, inner, out) { Ok(()) => Ok(()), Err(WriteTypeError::Format(err)) => Err(err), Err(WriteTypeError::NonWgsl) => self.write_non_wgsl_inner(inner, out), } } /// Write the [`Scalar`] `scalar` as a WGSL type. fn write_scalar(&self, scalar: Scalar, out: &mut W) -> core::fmt::Result { match scalar.try_to_wgsl() { Some(string) => out.write_str(string), None => self.write_non_wgsl_scalar(scalar, out), } } /// Write the [`TypeResolution`] `resolution` as a WGSL type. fn write_type_resolution( &self, resolution: &TypeResolution, out: &mut W, ) -> core::fmt::Result { match *resolution { TypeResolution::Handle(handle) => self.write_type(handle, out), TypeResolution::Value(ref inner) => self.write_type_inner(inner, out), } } fn write_type_conclusion( &self, conclusion: &crate::proc::Conclusion, out: &mut W, ) -> core::fmt::Result { use crate::proc::Conclusion as Co; match *conclusion { Co::Value(ref inner) => self.write_type_inner(inner, out), Co::Predeclared(ref predeclared) => out.write_str(&predeclared.struct_name()), } } fn write_type_rule( &self, name: &str, rule: &crate::proc::Rule, out: &mut W, ) -> core::fmt::Result { write!(out, "fn {name}(")?; for (i, arg) in rule.arguments.iter().enumerate() { if i > 0 { out.write_str(", ")?; } self.write_type_resolution(arg, out)? } out.write_str(") -> ")?; self.write_type_conclusion(&rule.conclusion, out)?; Ok(()) } fn type_to_string(&self, handle: Handle) -> String { let mut buf = String::new(); self.write_type(handle, &mut buf).unwrap(); buf } fn type_resolution_to_string(&self, resolution: &TypeResolution) -> String { let mut buf = String::new(); self.write_type_resolution(resolution, &mut buf).unwrap(); buf } fn type_rule_to_string(&self, name: &str, rule: &crate::proc::Rule) -> String { let mut buf = String::new(); self.write_type_rule(name, rule, &mut buf).unwrap(); buf } } fn try_write_type_inner(ctx: &C, inner: &TypeInner, out: &mut W) -> Result<(), WriteTypeError> where C: TypeContext + ?Sized, W: Write, { match *inner { TypeInner::Vector { size, scalar } => { write!(out, "vec{}<", common::vector_size_str(size))?; ctx.write_scalar(scalar, out)?; out.write_str(">")?; } TypeInner::Sampler { comparison: false } => { write!(out, "sampler")?; } TypeInner::Sampler { comparison: true } => { write!(out, "sampler_comparison")?; } TypeInner::Image { dim, arrayed, class, } => { // More about texture types: https://gpuweb.github.io/gpuweb/wgsl/#sampled-texture-type use crate::ImageClass as Ic; let dim_str = dim.to_wgsl(); let arrayed_str = if arrayed { "_array" } else { "" }; match class { Ic::Sampled { kind, multi } => { let multisampled_str = if multi { "multisampled_" } else { "" }; write!(out, "texture_{multisampled_str}{dim_str}{arrayed_str}<")?; ctx.write_scalar(Scalar { kind, width: 4 }, out)?; out.write_str(">")?; } Ic::Depth { multi } => { let multisampled_str = if multi { "multisampled_" } else { "" }; write!( out, "texture_depth_{multisampled_str}{dim_str}{arrayed_str}" )?; } Ic::Storage { format, access } => { let format_str = format.to_wgsl(); let access_str = if access.contains(crate::StorageAccess::ATOMIC) { ",atomic" } else if access .contains(crate::StorageAccess::LOAD | crate::StorageAccess::STORE) { ",read_write" } else if access.contains(crate::StorageAccess::LOAD) { ",read" } else { ",write" }; write!( out, "texture_storage_{dim_str}{arrayed_str}<{format_str}{access_str}>" )?; } Ic::External => { write!(out, "texture_external")?; } } } TypeInner::Scalar(scalar) => { ctx.write_scalar(scalar, out)?; } TypeInner::Atomic(scalar) => { out.write_str("atomic<")?; ctx.write_scalar(scalar, out)?; out.write_str(">")?; } TypeInner::Array { base, size, stride: _, } => { // More info https://gpuweb.github.io/gpuweb/wgsl/#array-types // array -- Constant array // array -- Dynamic array write!(out, "array<")?; match size { crate::ArraySize::Constant(len) => { ctx.write_type(base, out)?; write!(out, ", {len}")?; } crate::ArraySize::Pending(r#override) => { ctx.write_override(r#override, out)?; } crate::ArraySize::Dynamic => { ctx.write_type(base, out)?; } } write!(out, ">")?; } TypeInner::BindingArray { base, size } => { // More info https://github.com/gpuweb/gpuweb/issues/2105 write!(out, "binding_array<")?; match size { crate::ArraySize::Constant(len) => { ctx.write_type(base, out)?; write!(out, ", {len}")?; } crate::ArraySize::Pending(r#override) => { ctx.write_override(r#override, out)?; } crate::ArraySize::Dynamic => { ctx.write_type(base, out)?; } } write!(out, ">")?; } TypeInner::Matrix { columns, rows, scalar, } => { write!( out, "mat{}x{}<", common::vector_size_str(columns), common::vector_size_str(rows), )?; ctx.write_scalar(scalar, out)?; out.write_str(">")?; } TypeInner::CooperativeMatrix { columns, rows, scalar, role, } => { write!( out, "coop_mat{}x{}<{},{}>", columns as u32, rows as u32, scalar.try_to_wgsl().unwrap_or_default(), role.to_wgsl(), )?; } TypeInner::Pointer { base, space } => { let (address, maybe_access) = address_space_str(space); // Everything but `AddressSpace::Handle` gives us a `address` name, but // Naga IR never produces pointers to handles, so it doesn't matter much // how we write such a type. Just write it as the base type alone. if let Some(space) = address { write!(out, "ptr<{space}, ")?; } ctx.write_type(base, out)?; if address.is_some() { if let Some(access) = maybe_access { write!(out, ", {access}")?; } write!(out, ">")?; } } TypeInner::ValuePointer { size: None, scalar, space, } => { let (address, maybe_access) = address_space_str(space); if let Some(space) = address { write!(out, "ptr<{space}, ")?; ctx.write_scalar(scalar, out)?; if let Some(access) = maybe_access { write!(out, ", {access}")?; } write!(out, ">")?; } else { return Err(WriteTypeError::NonWgsl); } } TypeInner::ValuePointer { size: Some(size), scalar, space, } => { let (address, maybe_access) = address_space_str(space); if let Some(space) = address { write!(out, "ptr<{}, vec{}<", space, common::vector_size_str(size),)?; ctx.write_scalar(scalar, out)?; out.write_str(">")?; if let Some(access) = maybe_access { write!(out, ", {access}")?; } write!(out, ">")?; } else { return Err(WriteTypeError::NonWgsl); } write!(out, ">")?; } TypeInner::AccelerationStructure { vertex_return } => { let caps = if vertex_return { "" } else { "" }; write!(out, "acceleration_structure{caps}")? } TypeInner::Struct { .. } => { ctx.write_unnamed_struct(inner, out)?; } TypeInner::RayQuery { vertex_return } => { let caps = if vertex_return { "" } else { "" }; write!(out, "ray_query{caps}")? } } Ok(()) } /// Error type returned by `try_write_type_inner`. /// /// This type is private to the module. enum WriteTypeError { Format(core::fmt::Error), NonWgsl, } impl From for WriteTypeError { fn from(err: core::fmt::Error) -> Self { Self::Format(err) } } /// Format types as WGSL based on a [`GlobalCtx`]. /// /// This is probably good enough for diagnostic output, but it has some /// limitations: /// /// - It does not apply [`Namer`] renamings, to avoid collisions. /// /// - It generates invalid WGSL for anonymous struct types. /// /// - It doesn't write the lengths of override-expression-sized arrays /// correctly, unless the expression is just the override identifier. /// /// [`GlobalCtx`]: crate::proc::GlobalCtx /// [`Namer`]: crate::proc::Namer impl TypeContext for crate::proc::GlobalCtx<'_> { fn lookup_type(&self, handle: Handle) -> &crate::Type { &self.types[handle] } fn type_name(&self, handle: Handle) -> &str { self.types[handle] .name .as_deref() .unwrap_or("{anonymous type}") } fn write_unnamed_struct(&self, _: &TypeInner, out: &mut W) -> core::fmt::Result { write!(out, "{{unnamed struct}}") } fn write_override( &self, handle: Handle, out: &mut W, ) -> core::fmt::Result { match self.overrides[handle].name { Some(ref name) => out.write_str(name), None => write!(out, "{{anonymous override {handle:?}}}"), } } } /// Format types as WGSL based on a `UniqueArena`. /// /// This is probably only good enough for logging: /// /// - It does not apply any kind of [`Namer`] renamings. /// /// - It generates invalid WGSL for anonymous struct types. /// /// - It doesn't write override-sized arrays properly. /// /// [`Namer`]: crate::proc::Namer impl TypeContext for crate::UniqueArena { fn lookup_type(&self, handle: Handle) -> &crate::Type { &self[handle] } fn type_name(&self, handle: Handle) -> &str { self[handle].name.as_deref().unwrap_or("{anonymous type}") } fn write_unnamed_struct(&self, inner: &TypeInner, out: &mut W) -> core::fmt::Result { write!(out, "{{unnamed struct {inner:?}}}") } fn write_override( &self, handle: Handle, out: &mut W, ) -> core::fmt::Result { write!(out, "{{override {handle:?}}}") } } ================================================ FILE: naga/src/compact/expressions.rs ================================================ use super::{HandleMap, HandleSet, ModuleMap}; use crate::arena::{Arena, Handle}; pub struct ExpressionTracer<'tracer> { pub constants: &'tracer Arena, pub overrides: &'tracer Arena, /// The arena in which we are currently tracing expressions. pub expressions: &'tracer Arena, /// The used map for `types`. pub types_used: &'tracer mut HandleSet, /// The used map for global variables. pub global_variables_used: &'tracer mut HandleSet, /// The used map for `constants`. pub constants_used: &'tracer mut HandleSet, /// The used map for `overrides`. pub overrides_used: &'tracer mut HandleSet, /// The used set for `arena`. /// /// This points to whatever arena holds the expressions we are /// currently tracing: either a function's expression arena, or /// the module's constant expression arena. pub expressions_used: &'tracer mut HandleSet, /// The used set for the module's `global_expressions` arena. /// /// If `None`, we are already tracing the constant expressions, /// and `expressions_used` already refers to their handle set. pub global_expressions_used: Option<&'tracer mut HandleSet>, } impl ExpressionTracer<'_> { /// Propagate usage through `self.expressions`, starting with `self.expressions_used`. /// /// Treat `self.expressions_used` as the initial set of "known /// live" expressions, and follow through to identify all /// transitively used expressions. /// /// Mark types, constants, and constant expressions used directly /// by `self.expressions` as used. Items used indirectly are not /// marked. /// /// [fe]: crate::Function::expressions /// [ce]: crate::Module::global_expressions pub fn trace_expressions(&mut self) { log::trace!( "entering trace_expression of {}", if self.global_expressions_used.is_some() { "function expressions" } else { "const expressions" } ); // We don't need recursion or a work list. Because an // expression may only refer to other expressions that precede // it in the arena, it suffices to make a single pass over the // arena from back to front, marking the referents of used // expressions as used themselves. for (handle, expr) in self.expressions.iter().rev() { // If this expression isn't used, it doesn't matter what it uses. if !self.expressions_used.contains(handle) { continue; } log::trace!("tracing new expression {expr:?}"); self.trace_expression(expr); } } pub fn trace_expression(&mut self, expr: &crate::Expression) { use crate::Expression as Ex; match *expr { // Expressions that do not contain handles that need to be traced. Ex::Literal(_) | Ex::FunctionArgument(_) | Ex::LocalVariable(_) | Ex::SubgroupBallotResult | Ex::RayQueryProceedResult => {} // Expressions can refer to constants and overrides, which can refer // in turn to expressions, which complicates our nice one-pass // algorithm. But since constants and overrides don't refer to each // other directly, only via expressions, we can get around this by // looking *through* each constant/override and marking its // initializer expression as used immediately. Since `expr` refers // to the constant/override, which then refers to the initializer, // the initializer must precede `expr` in the arena, so we know we // have yet to visit the initializer, so it's not too late to mark // it. Ex::Constant(handle) => { self.constants_used.insert(handle); let constant = &self.constants[handle]; self.types_used.insert(constant.ty); match self.global_expressions_used { Some(ref mut used) => used.insert(constant.init), None => self.expressions_used.insert(constant.init), }; } Ex::Override(handle) => { self.overrides_used.insert(handle); let r#override = &self.overrides[handle]; self.types_used.insert(r#override.ty); if let Some(init) = r#override.init { match self.global_expressions_used { Some(ref mut used) => used.insert(init), None => self.expressions_used.insert(init), }; } } Ex::ZeroValue(ty) => { self.types_used.insert(ty); } Ex::Compose { ty, ref components } => { self.types_used.insert(ty); self.expressions_used .insert_iter(components.iter().cloned()); } Ex::Access { base, index } => self.expressions_used.insert_iter([base, index]), Ex::AccessIndex { base, index: _ } => { self.expressions_used.insert(base); } Ex::Splat { size: _, value } => { self.expressions_used.insert(value); } Ex::Swizzle { size: _, vector, pattern: _, } => { self.expressions_used.insert(vector); } Ex::GlobalVariable(handle) => { self.global_variables_used.insert(handle); } Ex::Load { pointer } => { self.expressions_used.insert(pointer); } Ex::ImageSample { image, sampler, gather: _, coordinate, array_index, offset, ref level, depth_ref, clamp_to_edge: _, } => { self.expressions_used .insert_iter([image, sampler, coordinate]); self.expressions_used.insert_iter(array_index); self.expressions_used.insert_iter(offset); use crate::SampleLevel as Sl; match *level { Sl::Auto | Sl::Zero => {} Sl::Exact(expr) | Sl::Bias(expr) => { self.expressions_used.insert(expr); } Sl::Gradient { x, y } => self.expressions_used.insert_iter([x, y]), } self.expressions_used.insert_iter(depth_ref); } Ex::ImageLoad { image, coordinate, array_index, sample, level, } => { self.expressions_used.insert(image); self.expressions_used.insert(coordinate); self.expressions_used.insert_iter(array_index); self.expressions_used.insert_iter(sample); self.expressions_used.insert_iter(level); } Ex::ImageQuery { image, ref query } => { self.expressions_used.insert(image); use crate::ImageQuery as Iq; match *query { Iq::Size { level } => self.expressions_used.insert_iter(level), Iq::NumLevels | Iq::NumLayers | Iq::NumSamples => {} } } Ex::RayQueryVertexPositions { query, committed: _, } => { self.expressions_used.insert(query); } Ex::Unary { op: _, expr } => { self.expressions_used.insert(expr); } Ex::Binary { op: _, left, right } => { self.expressions_used.insert_iter([left, right]); } Ex::Select { condition, accept, reject, } => self .expressions_used .insert_iter([condition, accept, reject]), Ex::Derivative { axis: _, ctrl: _, expr, } => { self.expressions_used.insert(expr); } Ex::Relational { fun: _, argument } => { self.expressions_used.insert(argument); } Ex::Math { fun: _, arg, arg1, arg2, arg3, } => { self.expressions_used.insert(arg); self.expressions_used.insert_iter(arg1); self.expressions_used.insert_iter(arg2); self.expressions_used.insert_iter(arg3); } Ex::As { expr, kind: _, convert: _, } => { self.expressions_used.insert(expr); } Ex::ArrayLength(expr) => { self.expressions_used.insert(expr); } // `CallResult` expressions do contain a function handle, but any used // `CallResult` expression should have an associated `ir::Statement::Call` // that we will trace. Ex::CallResult(_) => {} Ex::AtomicResult { ty, comparison: _ } | Ex::WorkGroupUniformLoadResult { ty } | Ex::SubgroupOperationResult { ty } => { self.types_used.insert(ty); } Ex::RayQueryGetIntersection { query, committed: _, } => { self.expressions_used.insert(query); } Ex::CooperativeLoad { ref data, .. } => { self.expressions_used.insert(data.pointer); self.expressions_used.insert(data.stride); } Ex::CooperativeMultiplyAdd { a, b, c } => { self.expressions_used.insert(a); self.expressions_used.insert(b); self.expressions_used.insert(c); } } } } impl ModuleMap { /// Fix up all handles in `expr`. /// /// Use the expression handle remappings in `operand_map`, and all /// other mappings from `self`. pub fn adjust_expression( &self, expr: &mut crate::Expression, operand_map: &HandleMap, ) { let adjust = |expr: &mut Handle| { operand_map.adjust(expr); }; use crate::Expression as Ex; match *expr { // Expressions that do not contain handles that need to be adjusted. Ex::Literal(_) | Ex::FunctionArgument(_) | Ex::LocalVariable(_) | Ex::SubgroupBallotResult | Ex::RayQueryProceedResult => {} // Expressions that contain handles that need to be adjusted. Ex::Constant(ref mut constant) => self.constants.adjust(constant), Ex::Override(ref mut r#override) => self.overrides.adjust(r#override), Ex::ZeroValue(ref mut ty) => self.types.adjust(ty), Ex::Compose { ref mut ty, ref mut components, } => { self.types.adjust(ty); for component in components { adjust(component); } } Ex::Access { ref mut base, ref mut index, } => { adjust(base); adjust(index); } Ex::AccessIndex { ref mut base, index: _, } => adjust(base), Ex::Splat { size: _, ref mut value, } => adjust(value), Ex::Swizzle { size: _, ref mut vector, pattern: _, } => adjust(vector), Ex::GlobalVariable(ref mut handle) => self.globals.adjust(handle), Ex::Load { ref mut pointer } => adjust(pointer), Ex::ImageSample { ref mut image, ref mut sampler, gather: _, ref mut coordinate, ref mut array_index, ref mut offset, ref mut level, ref mut depth_ref, clamp_to_edge: _, } => { adjust(image); adjust(sampler); adjust(coordinate); operand_map.adjust_option(array_index); operand_map.adjust_option(offset); self.adjust_sample_level(level, operand_map); operand_map.adjust_option(depth_ref); } Ex::ImageLoad { ref mut image, ref mut coordinate, ref mut array_index, ref mut sample, ref mut level, } => { adjust(image); adjust(coordinate); operand_map.adjust_option(array_index); operand_map.adjust_option(sample); operand_map.adjust_option(level); } Ex::ImageQuery { ref mut image, ref mut query, } => { adjust(image); self.adjust_image_query(query, operand_map); } Ex::Unary { op: _, ref mut expr, } => adjust(expr), Ex::Binary { op: _, ref mut left, ref mut right, } => { adjust(left); adjust(right); } Ex::Select { ref mut condition, ref mut accept, ref mut reject, } => { adjust(condition); adjust(accept); adjust(reject); } Ex::Derivative { axis: _, ctrl: _, ref mut expr, } => adjust(expr), Ex::Relational { fun: _, ref mut argument, } => adjust(argument), Ex::Math { fun: _, ref mut arg, ref mut arg1, ref mut arg2, ref mut arg3, } => { adjust(arg); operand_map.adjust_option(arg1); operand_map.adjust_option(arg2); operand_map.adjust_option(arg3); } Ex::As { ref mut expr, kind: _, convert: _, } => adjust(expr), Ex::CallResult(ref mut function) => { self.functions.adjust(function); } Ex::AtomicResult { ref mut ty, comparison: _, } => self.types.adjust(ty), Ex::WorkGroupUniformLoadResult { ref mut ty } => self.types.adjust(ty), Ex::SubgroupOperationResult { ref mut ty } => self.types.adjust(ty), Ex::ArrayLength(ref mut expr) => adjust(expr), Ex::RayQueryGetIntersection { ref mut query, committed: _, } => adjust(query), Ex::RayQueryVertexPositions { ref mut query, committed: _, } => adjust(query), Ex::CooperativeLoad { ref mut data, .. } => { adjust(&mut data.pointer); adjust(&mut data.stride); } Ex::CooperativeMultiplyAdd { ref mut a, ref mut b, ref mut c, } => { adjust(a); adjust(b); adjust(c); } } } fn adjust_sample_level( &self, level: &mut crate::SampleLevel, operand_map: &HandleMap, ) { let adjust = |expr: &mut Handle| operand_map.adjust(expr); use crate::SampleLevel as Sl; match *level { Sl::Auto | Sl::Zero => {} Sl::Exact(ref mut expr) => adjust(expr), Sl::Bias(ref mut expr) => adjust(expr), Sl::Gradient { ref mut x, ref mut y, } => { adjust(x); adjust(y); } } } fn adjust_image_query( &self, query: &mut crate::ImageQuery, operand_map: &HandleMap, ) { use crate::ImageQuery as Iq; match *query { Iq::Size { ref mut level } => operand_map.adjust_option(level), Iq::NumLevels | Iq::NumLayers | Iq::NumSamples => {} } } } ================================================ FILE: naga/src/compact/functions.rs ================================================ use super::arena::HandleSet; use super::{FunctionMap, ModuleMap}; pub struct FunctionTracer<'a> { pub function: &'a crate::Function, pub constants: &'a crate::Arena, pub overrides: &'a crate::Arena, pub functions_pending: &'a mut HandleSet, pub functions_used: &'a mut HandleSet, pub types_used: &'a mut HandleSet, pub global_variables_used: &'a mut HandleSet, pub constants_used: &'a mut HandleSet, pub overrides_used: &'a mut HandleSet, pub global_expressions_used: &'a mut HandleSet, /// Function-local expressions used. pub expressions_used: HandleSet, } impl FunctionTracer<'_> { pub fn trace_call(&mut self, function: crate::Handle) { if !self.functions_used.contains(function) { self.functions_used.insert(function); self.functions_pending.insert(function); } } pub fn trace(&mut self) { for argument in self.function.arguments.iter() { self.types_used.insert(argument.ty); } if let Some(ref result) = self.function.result { self.types_used.insert(result.ty); } for (_, local) in self.function.local_variables.iter() { self.types_used.insert(local.ty); if let Some(init) = local.init { self.expressions_used.insert(init); } } // Treat named expressions as alive, for the sake of our test suite, // which uses `let blah = expr;` to exercise lots of things. for (&value, _name) in &self.function.named_expressions { self.expressions_used.insert(value); } self.trace_block(&self.function.body); // Given that `trace_block` has marked the expressions used // directly by statements, walk the arena to find all // expressions used, directly or indirectly. self.as_expression().trace_expressions(); } const fn as_expression(&mut self) -> super::expressions::ExpressionTracer<'_> { super::expressions::ExpressionTracer { constants: self.constants, overrides: self.overrides, expressions: &self.function.expressions, types_used: self.types_used, global_variables_used: self.global_variables_used, constants_used: self.constants_used, overrides_used: self.overrides_used, expressions_used: &mut self.expressions_used, global_expressions_used: Some(&mut self.global_expressions_used), } } } impl FunctionMap { pub fn compact( &self, function: &mut crate::Function, module_map: &ModuleMap, reuse: &mut crate::NamedExpressions, ) { assert!(reuse.is_empty()); for argument in function.arguments.iter_mut() { module_map.types.adjust(&mut argument.ty); } if let Some(ref mut result) = function.result { module_map.types.adjust(&mut result.ty); } for (_, local) in function.local_variables.iter_mut() { log::trace!("adjusting local variable {:?}", local.name); module_map.types.adjust(&mut local.ty); if let Some(ref mut init) = local.init { self.expressions.adjust(init); } } // Drop unused expressions, reusing existing storage. function.expressions.retain_mut(|handle, expr| { if self.expressions.used(handle) { module_map.adjust_expression(expr, &self.expressions); true } else { false } }); // Adjust named expressions. for (mut handle, name) in function.named_expressions.drain(..) { self.expressions.adjust(&mut handle); reuse.insert(handle, name); } core::mem::swap(&mut function.named_expressions, reuse); assert!(reuse.is_empty()); // Adjust statements. self.adjust_body(function, &module_map.functions); } } ================================================ FILE: naga/src/compact/handle_set_map.rs ================================================ use alloc::vec::Vec; use crate::arena::{Arena, Handle, HandleSet, Range}; type Index = crate::non_max_u32::NonMaxU32; /// A map keyed by handles. /// /// In most cases, this is used to map from old handle indices to new, /// compressed handle indices. #[derive(Debug)] pub struct HandleMap { /// The indices assigned to handles in the compacted module. /// /// If `new_index[i]` is `Some(n)`, then `n` is the `Index` of the /// compacted `Handle` corresponding to the pre-compacted `Handle` /// whose index is `i`. new_index: Vec>, /// This type is indexed by values of type `T`. as_keys: core::marker::PhantomData, } impl HandleMap { pub fn with_capacity(capacity: usize) -> Self { Self { new_index: Vec::with_capacity(capacity), as_keys: core::marker::PhantomData, } } pub fn get(&self, handle: Handle) -> Option<&U> { self.new_index.get(handle.index()).unwrap_or(&None).as_ref() } pub fn insert(&mut self, handle: Handle, value: U) -> Option { if self.new_index.len() <= handle.index() { self.new_index.resize_with(handle.index() + 1, || None); } self.new_index[handle.index()].replace(value) } } impl HandleMap { pub fn from_set(set: HandleSet) -> Self { let mut next_index = Index::new(0).unwrap(); Self { new_index: set .all_possible() .map(|handle| { if set.contains(handle) { // This handle will be retained in the compacted version, // so assign it a new index. let this = next_index; next_index = next_index.checked_add(1).unwrap(); Some(this) } else { // This handle will be omitted in the compacted version. None } }) .collect(), as_keys: core::marker::PhantomData, } } /// Return true if `old` is used in the compacted module. pub fn used(&self, old: Handle) -> bool { self.new_index[old.index()].is_some() } /// Return the counterpart to `old` in the compacted module. /// /// If we thought `old` wouldn't be used in the compacted module, return /// `None`. pub fn try_adjust(&self, old: Handle) -> Option> { log::trace!( "adjusting {} handle [{}] -> [{:?}]", core::any::type_name::(), old.index(), self.new_index[old.index()] ); self.new_index[old.index()].map(Handle::new) } /// Return the counterpart to `old` in the compacted module. /// /// If we thought `old` wouldn't be used in the compacted module, panic. pub fn adjust(&self, handle: &mut Handle) { *handle = self.try_adjust(*handle).unwrap(); } /// Like `adjust`, but for optional handles. pub fn adjust_option(&self, handle: &mut Option>) { if let Some(ref mut handle) = *handle { self.adjust(handle); } } /// Shrink `range` to include only used handles. /// /// Fortunately, compaction doesn't arbitrarily scramble the expressions /// in the arena, but instead preserves the order of the elements while /// squeezing out unused ones. That means that a contiguous range in the /// pre-compacted arena always maps to a contiguous range in the /// post-compacted arena. So we just need to adjust the endpoints. /// /// Compaction may have eliminated the endpoints themselves. /// /// Use `compacted_arena` to bounds-check the result. pub fn adjust_range(&self, range: &mut Range, compacted_arena: &Arena) { let mut index_range = range.index_range(); let compacted; if let Some(first) = index_range.find_map(|i| self.new_index[i as usize]) { // The first call to `find_map` mutated `index_range` to hold the // remainder of original range, which is exactly the range we need // to search for the new last handle. if let Some(last) = index_range.rev().find_map(|i| self.new_index[i as usize]) { // Build an end-exclusive range, given the two included indices // `first` and `last`. compacted = first.get()..last.get() + 1; } else { // The range contains only a single live handle, which // we identified with the first `find_map` call. compacted = first.get()..first.get() + 1; } } else { compacted = 0..0; }; *range = Range::from_index_range(compacted, compacted_arena); } } ================================================ FILE: naga/src/compact/mod.rs ================================================ mod expressions; mod functions; mod handle_set_map; mod statements; mod types; use alloc::vec::Vec; use crate::{ arena::{self, HandleSet}, compact::functions::FunctionTracer, ir, }; use handle_set_map::HandleMap; #[cfg(test)] use alloc::{format, string::ToString}; /// Configuration option for [`compact`]. See [`compact`] for details. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum KeepUnused { No, Yes, } impl From for bool { fn from(keep_unused: KeepUnused) -> Self { match keep_unused { KeepUnused::No => false, KeepUnused::Yes => true, } } } /// Remove most unused objects from `module`, which must be valid. /// /// Always removes the following unused objects: /// - anonymous types, overrides, and constants /// - abstract-typed constants /// - expressions /// /// If `keep_unused` is `Yes`, the following are never considered unused, /// otherwise, they will also be removed if unused: /// - functions /// - global variables /// - named types and overrides /// /// The following are never removed: /// - named constants with a concrete type /// - special types /// - entry points /// - within an entry point or a used function: /// - arguments /// - local variables /// - named expressions /// /// After removing items according to the rules above, all handles in the /// remaining objects are adjusted as necessary. When `KeepUnused` is `Yes`, the /// resulting module should have all the named objects (except abstract-typed /// constants) present in the original, and those objects should be functionally /// identical. When `KeepUnused` is `No`, the resulting module should have the /// entry points present in the original, and those entry points should be /// functionally identical. /// /// # Panics /// /// If `module` would not pass validation, this may panic. pub fn compact(module: &mut crate::Module, keep_unused: KeepUnused) { // The trickiest part of compaction is determining what is used and what is // not. Once we have computed that correctly, it's easy enough to call // `retain_mut` on each arena, drop unused elements, and fix up the handles // in what's left. // // For every compactable arena in a `Module`, whether global to the `Module` // or local to a function or entry point, the `ModuleTracer` type holds a // bitmap indicating which elements of that arena are used. Our task is to // populate those bitmaps correctly. // // First, we mark everything that is considered used by definition, as // described in this function's documentation. // // Since functions and entry points are considered used by definition, we // traverse their statement trees, and mark the referents of all handles // appearing in those statements as used. // // Once we've marked which elements of an arena are referred to directly by // handles elsewhere (for example, which of a function's expressions are // referred to by handles in its body statements), we can mark all the other // arena elements that are used indirectly in a single pass, traversing the // arena from back to front. Since Naga allows arena elements to refer only // to prior elements, we know that by the time we reach an element, all // other elements that could possibly refer to it have already been visited. // Thus, if the present element has not been marked as used, then it is // definitely unused, and compaction can remove it. Otherwise, the element // is used and must be retained, so we must mark everything it refers to. // // The final step is to mark the global expressions and types, which must be // traversed simultaneously; see `ModuleTracer::type_expression_tandem`'s // documentation for details. // // # A definition and a rule of thumb // // In this module, to "trace" something is to mark everything else it refers // to as used, on the assumption that the thing itself is used. For example, // to trace an `Expression` is to mark its subexpressions as used, as well // as any types, constants, overrides, etc. that it refers to. This is what // `ExpressionTracer::trace_expression` does. // // Given that we we want to visit each thing only once (to keep compaction // linear in the size of the module), this definition of "trace" implies // that things that are not "used by definition" must be marked as used // *before* we trace them. // // Thus, whenever you are marking something as used, it's a good idea to ask // yourself how you know that thing will be traced in the future. If you're // not sure, then you could be marking it too late to be noticed. The thing // itself will be retained by compaction, but since it will not be traced, // anything it refers to could be compacted away. let mut module_tracer = ModuleTracer::new(module); // Observe what each entry point actually uses. log::trace!("tracing entry points"); let entry_point_maps = module .entry_points .iter() .map(|e| { log::trace!("tracing entry point {:?}", e.function.name); if let Some(sizes) = e.workgroup_size_overrides { for size in sizes.iter().filter_map(|x| *x) { module_tracer.global_expressions_used.insert(size); } } if let Some(task_payload) = e.task_payload { module_tracer.global_variables_used.insert(task_payload); } if let Some(ref mesh_info) = e.mesh_info { module_tracer .global_variables_used .insert(mesh_info.output_variable); module_tracer .types_used .insert(mesh_info.vertex_output_type); module_tracer .types_used .insert(mesh_info.primitive_output_type); if let Some(max_vertices_override) = mesh_info.max_vertices_override { module_tracer .global_expressions_used .insert(max_vertices_override); } if let Some(max_primitives_override) = mesh_info.max_primitives_override { module_tracer .global_expressions_used .insert(max_primitives_override); } } if e.stage == crate::ShaderStage::Task || e.stage == crate::ShaderStage::Mesh { // Mesh shaders always need a u32 type, as it is e.g. the type of some // expressions. We tolerate its absence here because compaction is // infallible, but the module will fail validation. if let Some(u32_type) = module.types.iter().find_map(|tuple| { (tuple.1.inner == crate::TypeInner::Scalar(crate::Scalar::U32)) .then_some(tuple.0) }) { module_tracer.types_used.insert(u32_type); } } let mut used = module_tracer.as_function(&e.function); used.trace(); FunctionMap::from(used) }) .collect::>(); // Observe which types, constant expressions, constants, and expressions // each function uses, and produce maps for each function from // pre-compaction to post-compaction expression handles. // // The function tracing logic here works in conjunction with // `FunctionTracer::trace_call`, which, when tracing a `Statement::Call` // to a function not already identified as used, adds the called function // to both `functions_used` and `functions_pending`. // // Called functions are required to appear before their callers in the // functions arena (recursion is disallowed). We have already traced the // entry point(s) and added any functions called directly by the entry // point(s) to `functions_pending`. We proceed by repeatedly tracing the // last function in `functions_pending`. By an inductive argument, any // functions after the last function in `functions_pending` must be unused. // // When `KeepUnused` is active, we simply mark all functions as pending, // and then trace all of them. log::trace!("tracing functions"); let mut function_maps = HandleMap::with_capacity(module.functions.len()); if keep_unused.into() { module_tracer.functions_used.add_all(); module_tracer.functions_pending.add_all(); } while let Some(handle) = module_tracer.functions_pending.pop() { let function = &module.functions[handle]; log::trace!("tracing function {function:?}"); let mut function_tracer = module_tracer.as_function(function); function_tracer.trace(); function_maps.insert(handle, FunctionMap::from(function_tracer)); } // We treat all special types as used by definition. log::trace!("tracing special types"); module_tracer.trace_special_types(&module.special_types); log::trace!("tracing global variables"); if keep_unused.into() { module_tracer.global_variables_used.add_all(); } for global in module_tracer.global_variables_used.iter() { log::trace!("tracing global {:?}", module.global_variables[global].name); module_tracer .types_used .insert(module.global_variables[global].ty); if let Some(init) = module.global_variables[global].init { module_tracer.global_expressions_used.insert(init); } } // We treat all named constants as used by definition, unless they have an // abstract type as we do not want those reaching the validator. log::trace!("tracing named constants"); for (handle, constant) in module.constants.iter() { if constant.name.is_none() || module.types[constant.ty].inner.is_abstract(&module.types) { continue; } log::trace!("tracing constant {:?}", constant.name.as_ref().unwrap()); module_tracer.constants_used.insert(handle); module_tracer.types_used.insert(constant.ty); module_tracer.global_expressions_used.insert(constant.init); } if keep_unused.into() { // Treat all named overrides as used. for (handle, r#override) in module.overrides.iter() { if r#override.name.is_some() && module_tracer.overrides_used.insert(handle) { module_tracer.types_used.insert(r#override.ty); if let Some(init) = r#override.init { module_tracer.global_expressions_used.insert(init); } } } // Treat all named types as used. for (handle, ty) in module.types.iter() { if ty.name.is_some() { module_tracer.types_used.insert(handle); } } } module_tracer.type_expression_tandem(); // Now that we know what is used and what is never touched, // produce maps from the `Handle`s that appear in `module` now to // the corresponding `Handle`s that will refer to the same items // in the compacted module. let module_map = ModuleMap::from(module_tracer); // Drop unused types from the type arena. // // `FastIndexSet`s don't have an underlying Vec that we can // steal, compact in place, and then rebuild the `FastIndexSet` // from. So we have to rebuild the type arena from scratch. log::trace!("compacting types"); let mut new_types = arena::UniqueArena::new(); for (old_handle, mut ty, span) in module.types.drain_all() { if let Some(expected_new_handle) = module_map.types.try_adjust(old_handle) { module_map.adjust_type(&mut ty); let actual_new_handle = new_types.insert(ty, span); assert_eq!(actual_new_handle, expected_new_handle); } } module.types = new_types; log::trace!("adjusting special types"); module_map.adjust_special_types(&mut module.special_types); // Drop unused constant expressions, reusing existing storage. log::trace!("adjusting constant expressions"); module.global_expressions.retain_mut(|handle, expr| { if module_map.global_expressions.used(handle) { module_map.adjust_expression(expr, &module_map.global_expressions); true } else { false } }); // Drop unused constants in place, reusing existing storage. log::trace!("adjusting constants"); module.constants.retain_mut(|handle, constant| { if module_map.constants.used(handle) { module_map.types.adjust(&mut constant.ty); module_map.global_expressions.adjust(&mut constant.init); true } else { false } }); // Drop unused overrides in place, reusing existing storage. log::trace!("adjusting overrides"); module.overrides.retain_mut(|handle, r#override| { if module_map.overrides.used(handle) { module_map.types.adjust(&mut r#override.ty); if let Some(ref mut init) = r#override.init { module_map.global_expressions.adjust(init); } true } else { false } }); // Adjust workgroup_size_overrides log::trace!("adjusting workgroup_size_overrides"); for e in module.entry_points.iter_mut() { if let Some(sizes) = e.workgroup_size_overrides.as_mut() { for size in sizes.iter_mut() { if let Some(expr) = size.as_mut() { module_map.global_expressions.adjust(expr); } } } } // Drop unused global variables, reusing existing storage. // Adjust used global variables' types and initializers. log::trace!("adjusting global variables"); module.global_variables.retain_mut(|handle, global| { if module_map.globals.used(handle) { log::trace!("retaining global variable {:?}", global.name); module_map.types.adjust(&mut global.ty); if let Some(ref mut init) = global.init { module_map.global_expressions.adjust(init); } true } else { log::trace!("dropping global variable {:?}", global.name); false } }); // Adjust doc comments if let Some(ref mut doc_comments) = module.doc_comments { module_map.adjust_doc_comments(doc_comments.as_mut()); } // Temporary storage to help us reuse allocations of existing // named expression tables. let mut reused_named_expressions = crate::NamedExpressions::default(); // Drop unused functions. Compact and adjust used functions. module.functions.retain_mut(|handle, function| { if let Some(map) = function_maps.get(handle) { log::trace!("retaining and compacting function {:?}", function.name); map.compact(function, &module_map, &mut reused_named_expressions); true } else { log::trace!("dropping function {:?}", function.name); false } }); // Compact each entry point. for (entry, map) in module.entry_points.iter_mut().zip(entry_point_maps.iter()) { log::trace!("compacting entry point {:?}", entry.function.name); map.compact( &mut entry.function, &module_map, &mut reused_named_expressions, ); if let Some(ref mut task_payload) = entry.task_payload { module_map.globals.adjust(task_payload); } if let Some(ref mut mesh_info) = entry.mesh_info { module_map.globals.adjust(&mut mesh_info.output_variable); module_map.types.adjust(&mut mesh_info.vertex_output_type); module_map .types .adjust(&mut mesh_info.primitive_output_type); if let Some(ref mut max_vertices_override) = mesh_info.max_vertices_override { module_map.global_expressions.adjust(max_vertices_override); } if let Some(ref mut max_primitives_override) = mesh_info.max_primitives_override { module_map .global_expressions .adjust(max_primitives_override); } } } } struct ModuleTracer<'module> { module: &'module crate::Module, /// The subset of functions in `functions_used` that have not yet been /// traced. functions_pending: HandleSet, functions_used: HandleSet, types_used: HandleSet, global_variables_used: HandleSet, constants_used: HandleSet, overrides_used: HandleSet, global_expressions_used: HandleSet, } impl<'module> ModuleTracer<'module> { fn new(module: &'module crate::Module) -> Self { Self { module, functions_pending: HandleSet::for_arena(&module.functions), functions_used: HandleSet::for_arena(&module.functions), types_used: HandleSet::for_arena(&module.types), global_variables_used: HandleSet::for_arena(&module.global_variables), constants_used: HandleSet::for_arena(&module.constants), overrides_used: HandleSet::for_arena(&module.overrides), global_expressions_used: HandleSet::for_arena(&module.global_expressions), } } fn trace_special_types(&mut self, special_types: &crate::SpecialTypes) { let crate::SpecialTypes { ref ray_desc, ref ray_intersection, ref ray_vertex_return, ref predeclared_types, ref external_texture_params, ref external_texture_transfer_function, } = *special_types; if let Some(ray_desc) = *ray_desc { self.types_used.insert(ray_desc); } if let Some(ray_intersection) = *ray_intersection { self.types_used.insert(ray_intersection); } if let Some(ray_vertex_return) = *ray_vertex_return { self.types_used.insert(ray_vertex_return); } // The `external_texture_params` type is generated purely as a // convenience to the backends. While it will never actually be used in // the IR, it must be marked as used so that it survives compaction. if let Some(external_texture_params) = *external_texture_params { self.types_used.insert(external_texture_params); } if let Some(external_texture_transfer_function) = *external_texture_transfer_function { self.types_used.insert(external_texture_transfer_function); } for (_, &handle) in predeclared_types { self.types_used.insert(handle); } } /// Traverse types and global expressions in tandem to determine which are used. /// /// Assuming that all types and global expressions used by other parts of /// the module have been added to [`types_used`] and /// [`global_expressions_used`], expand those sets to include all types and /// global expressions reachable from those. /// /// [`types_used`]: ModuleTracer::types_used /// [`global_expressions_used`]: ModuleTracer::global_expressions_used fn type_expression_tandem(&mut self) { // For each type T, compute the latest global expression E that T and // its predecessors refer to. Given the ordering rules on types and // global expressions in valid modules, we can do this with a single // forward scan of the type arena. The rules further imply that T can // only be referred to by expressions after E. let mut max_dep = Vec::with_capacity(self.module.types.len()); let mut previous = None; for (_handle, ty) in self.module.types.iter() { previous = core::cmp::max( previous, match ty.inner { crate::TypeInner::Array { size, .. } | crate::TypeInner::BindingArray { size, .. } => match size { crate::ArraySize::Constant(_) | crate::ArraySize::Dynamic => None, crate::ArraySize::Pending(handle) => self.module.overrides[handle].init, }, _ => None, }, ); max_dep.push(previous); } // Visit types and global expressions from youngest to oldest. // // The outer loop visits types. Before visiting each type, the inner // loop ensures that all global expressions that could possibly refer to // it have been visited. And since the inner loop stop at the latest // expression that the type could possibly refer to, we know that we // have previously visited any types that might refer to each expression // we visit. // // This lets us assume that any type or expression that is *not* marked // as used by the time we visit it is genuinely unused, and can be // ignored. let mut exprs = self.module.global_expressions.iter().rev().peekable(); for ((ty_handle, ty), dep) in self.module.types.iter().zip(max_dep).rev() { while let Some((expr_handle, expr)) = exprs.next_if(|&(h, _)| Some(h) > dep) { if self.global_expressions_used.contains(expr_handle) { self.as_const_expression().trace_expression(expr); } } if self.types_used.contains(ty_handle) { self.as_type().trace_type(ty); } } // Visit any remaining expressions. for (expr_handle, expr) in exprs { if self.global_expressions_used.contains(expr_handle) { self.as_const_expression().trace_expression(expr); } } } const fn as_type(&mut self) -> types::TypeTracer<'_> { types::TypeTracer { overrides: &self.module.overrides, types_used: &mut self.types_used, expressions_used: &mut self.global_expressions_used, overrides_used: &mut self.overrides_used, } } const fn as_const_expression(&mut self) -> expressions::ExpressionTracer<'_> { expressions::ExpressionTracer { constants: &self.module.constants, overrides: &self.module.overrides, expressions: &self.module.global_expressions, types_used: &mut self.types_used, global_variables_used: &mut self.global_variables_used, constants_used: &mut self.constants_used, expressions_used: &mut self.global_expressions_used, overrides_used: &mut self.overrides_used, global_expressions_used: None, } } pub fn as_function<'tracer>( &'tracer mut self, function: &'tracer crate::Function, ) -> FunctionTracer<'tracer> { FunctionTracer { function, constants: &self.module.constants, overrides: &self.module.overrides, functions_pending: &mut self.functions_pending, functions_used: &mut self.functions_used, types_used: &mut self.types_used, global_variables_used: &mut self.global_variables_used, constants_used: &mut self.constants_used, overrides_used: &mut self.overrides_used, global_expressions_used: &mut self.global_expressions_used, expressions_used: HandleSet::for_arena(&function.expressions), } } } struct ModuleMap { functions: HandleMap, types: HandleMap, globals: HandleMap, constants: HandleMap, overrides: HandleMap, global_expressions: HandleMap, } impl From> for ModuleMap { fn from(used: ModuleTracer) -> Self { ModuleMap { functions: HandleMap::from_set(used.functions_used), types: HandleMap::from_set(used.types_used), globals: HandleMap::from_set(used.global_variables_used), constants: HandleMap::from_set(used.constants_used), overrides: HandleMap::from_set(used.overrides_used), global_expressions: HandleMap::from_set(used.global_expressions_used), } } } impl ModuleMap { fn adjust_special_types(&self, special: &mut crate::SpecialTypes) { let crate::SpecialTypes { ref mut ray_desc, ref mut ray_intersection, ref mut ray_vertex_return, ref mut predeclared_types, ref mut external_texture_params, ref mut external_texture_transfer_function, } = *special; if let Some(ref mut ray_desc) = *ray_desc { self.types.adjust(ray_desc); } if let Some(ref mut ray_intersection) = *ray_intersection { self.types.adjust(ray_intersection); } if let Some(ref mut ray_vertex_return) = *ray_vertex_return { self.types.adjust(ray_vertex_return); } if let Some(ref mut external_texture_params) = *external_texture_params { self.types.adjust(external_texture_params); } if let Some(ref mut external_texture_transfer_function) = *external_texture_transfer_function { self.types.adjust(external_texture_transfer_function); } for handle in predeclared_types.values_mut() { self.types.adjust(handle); } } fn adjust_doc_comments(&self, doc_comments: &mut ir::DocComments) { let crate::DocComments { module: _, types: ref mut doc_types, struct_members: ref mut doc_struct_members, entry_points: _, functions: ref mut doc_functions, constants: ref mut doc_constants, global_variables: ref mut doc_globals, } = *doc_comments; log::trace!("adjusting doc comments for types"); for (mut ty, doc_comment) in core::mem::take(doc_types) { if !self.types.used(ty) { continue; } self.types.adjust(&mut ty); doc_types.insert(ty, doc_comment); } log::trace!("adjusting doc comments for struct members"); for ((mut ty, index), doc_comment) in core::mem::take(doc_struct_members) { if !self.types.used(ty) { continue; } self.types.adjust(&mut ty); doc_struct_members.insert((ty, index), doc_comment); } log::trace!("adjusting doc comments for functions"); for (mut handle, doc_comment) in core::mem::take(doc_functions) { if !self.functions.used(handle) { continue; } self.functions.adjust(&mut handle); doc_functions.insert(handle, doc_comment); } log::trace!("adjusting doc comments for constants"); for (mut constant, doc_comment) in core::mem::take(doc_constants) { if !self.constants.used(constant) { continue; } self.constants.adjust(&mut constant); doc_constants.insert(constant, doc_comment); } log::trace!("adjusting doc comments for globals"); for (mut handle, doc_comment) in core::mem::take(doc_globals) { if !self.globals.used(handle) { continue; } self.globals.adjust(&mut handle); doc_globals.insert(handle, doc_comment); } } } struct FunctionMap { expressions: HandleMap, } impl From> for FunctionMap { fn from(used: FunctionTracer) -> Self { FunctionMap { expressions: HandleMap::from_set(used.expressions_used), } } } #[test] fn type_expression_interdependence() { let mut module: crate::Module = Default::default(); let u32 = module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Uint, width: 4, }), }, crate::Span::default(), ); let expr = module.global_expressions.append( crate::Expression::Literal(crate::Literal::U32(0)), crate::Span::default(), ); let type_needs_expression = |module: &mut crate::Module, handle| { let override_handle = module.overrides.append( crate::Override { name: None, id: None, ty: u32, init: Some(handle), }, crate::Span::default(), ); module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Array { base: u32, size: crate::ArraySize::Pending(override_handle), stride: 4, }, }, crate::Span::default(), ) }; let expression_needs_type = |module: &mut crate::Module, handle| { module .global_expressions .append(crate::Expression::ZeroValue(handle), crate::Span::default()) }; let expression_needs_expression = |module: &mut crate::Module, handle| { module.global_expressions.append( crate::Expression::Load { pointer: handle }, crate::Span::default(), ) }; let type_needs_type = |module: &mut crate::Module, handle| { module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Array { base: handle, size: crate::ArraySize::Dynamic, stride: 0, }, }, crate::Span::default(), ) }; let mut type_name_counter = 0; let mut type_needed = |module: &mut crate::Module, handle| { let name = Some(format!("type{type_name_counter}")); type_name_counter += 1; module.types.insert( crate::Type { name, inner: crate::TypeInner::Array { base: handle, size: crate::ArraySize::Dynamic, stride: 0, }, }, crate::Span::default(), ) }; let mut override_name_counter = 0; let mut expression_needed = |module: &mut crate::Module, handle| { let name = Some(format!("override{override_name_counter}")); override_name_counter += 1; module.overrides.append( crate::Override { name, id: None, ty: u32, init: Some(handle), }, crate::Span::default(), ) }; let cmp_modules = |mod0: &crate::Module, mod1: &crate::Module| { (mod0.types.iter().collect::>() == mod1.types.iter().collect::>()) && (mod0.global_expressions.iter().collect::>() == mod1.global_expressions.iter().collect::>()) }; // borrow checker breaks without the tmp variables as of Rust 1.83.0 let expr_end = type_needs_expression(&mut module, expr); let ty_trace = type_needs_type(&mut module, expr_end); let expr_init = expression_needs_type(&mut module, ty_trace); expression_needed(&mut module, expr_init); let ty_end = expression_needs_type(&mut module, u32); let expr_trace = expression_needs_expression(&mut module, ty_end); let ty_init = type_needs_expression(&mut module, expr_trace); type_needed(&mut module, ty_init); let untouched = module.clone(); compact(&mut module, KeepUnused::Yes); assert!(cmp_modules(&module, &untouched)); let unused_expr = module.global_expressions.append( crate::Expression::Literal(crate::Literal::U32(1)), crate::Span::default(), ); type_needs_expression(&mut module, unused_expr); assert!(!cmp_modules(&module, &untouched)); compact(&mut module, KeepUnused::Yes); assert!(cmp_modules(&module, &untouched)); } #[test] fn array_length_override() { let mut module: crate::Module = Default::default(); let ty_bool = module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::BOOL), }, crate::Span::default(), ); let ty_u32 = module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::U32), }, crate::Span::default(), ); let one = module.global_expressions.append( crate::Expression::Literal(crate::Literal::U32(1)), crate::Span::default(), ); let _unused_override = module.overrides.append( crate::Override { name: None, id: Some(40), ty: ty_u32, init: None, }, crate::Span::default(), ); let o = module.overrides.append( crate::Override { name: None, id: Some(42), ty: ty_u32, init: Some(one), }, crate::Span::default(), ); let _ty_array = module.types.insert( crate::Type { name: Some("array".to_string()), inner: crate::TypeInner::Array { base: ty_bool, size: crate::ArraySize::Pending(o), stride: 4, }, }, crate::Span::default(), ); let mut validator = super::valid::Validator::new( super::valid::ValidationFlags::all(), super::valid::Capabilities::all(), ); assert!(validator.validate(&module).is_ok()); compact(&mut module, KeepUnused::Yes); assert!(validator.validate(&module).is_ok()); } /// Test mutual references between types and expressions via override /// lengths. #[test] fn array_length_override_mutual() { use crate::Expression as Ex; use crate::Scalar as Sc; use crate::TypeInner as Ti; let nowhere = crate::Span::default(); let mut module = crate::Module::default(); let ty_u32 = module.types.insert( crate::Type { name: None, inner: Ti::Scalar(Sc::U32), }, nowhere, ); // This type is only referred to by the override's init // expression, so if we visit that too early, this type will be // removed incorrectly. let ty_i32 = module.types.insert( crate::Type { name: None, inner: Ti::Scalar(Sc::I32), }, nowhere, ); // An override that the other override's init can refer to. let first_override = module.overrides.append( crate::Override { name: None, // so it is not considered used by definition id: Some(41), ty: ty_i32, init: None, }, nowhere, ); // Initializer expression for the override: // // (first_override + 0) as u32 // // The `first_override` makes it an override expression; the `0` // gets a use of `ty_i32` in there; and the `as` makes it match // the type of `second_override` without actually making // `second_override` point at `ty_i32` directly. let first_override_expr = module .global_expressions .append(Ex::Override(first_override), nowhere); let zero = module .global_expressions .append(Ex::ZeroValue(ty_i32), nowhere); let sum = module.global_expressions.append( Ex::Binary { op: crate::BinaryOperator::Add, left: first_override_expr, right: zero, }, nowhere, ); let init = module.global_expressions.append( Ex::As { expr: sum, kind: crate::ScalarKind::Uint, convert: None, }, nowhere, ); // Override that serves as the array's length. let second_override = module.overrides.append( crate::Override { name: None, // so it is not considered used by definition id: Some(42), ty: ty_u32, init: Some(init), }, nowhere, ); // Array type that uses the overload as its length. // Since this is named, it is considered used by definition. let _ty_array = module.types.insert( crate::Type { name: Some("delicious_array".to_string()), inner: Ti::Array { base: ty_u32, size: crate::ArraySize::Pending(second_override), stride: 4, }, }, nowhere, ); let mut validator = super::valid::Validator::new( super::valid::ValidationFlags::all(), super::valid::Capabilities::all(), ); assert!(validator.validate(&module).is_ok()); compact(&mut module, KeepUnused::Yes); assert!(validator.validate(&module).is_ok()); } #[test] fn array_length_expression() { let mut module: crate::Module = Default::default(); let ty_u32 = module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::U32), }, crate::Span::default(), ); let _unused_zero = module.global_expressions.append( crate::Expression::Literal(crate::Literal::U32(0)), crate::Span::default(), ); let one = module.global_expressions.append( crate::Expression::Literal(crate::Literal::U32(1)), crate::Span::default(), ); let override_one = module.overrides.append( crate::Override { name: None, id: None, ty: ty_u32, init: Some(one), }, crate::Span::default(), ); let _ty_array = module.types.insert( crate::Type { name: Some("array".to_string()), inner: crate::TypeInner::Array { base: ty_u32, size: crate::ArraySize::Pending(override_one), stride: 4, }, }, crate::Span::default(), ); let mut validator = super::valid::Validator::new( super::valid::ValidationFlags::all(), super::valid::Capabilities::all(), ); assert!(validator.validate(&module).is_ok()); compact(&mut module, KeepUnused::Yes); assert!(validator.validate(&module).is_ok()); } #[test] fn global_expression_override() { let mut module: crate::Module = Default::default(); let ty_u32 = module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::U32), }, crate::Span::default(), ); // This will only be retained if we trace the initializers // of overrides referred to by `Expression::Override` // in global expressions. let expr1 = module.global_expressions.append( crate::Expression::Literal(crate::Literal::U32(1)), crate::Span::default(), ); // This will only be traced via a global `Expression::Override`. let o = module.overrides.append( crate::Override { name: None, id: Some(42), ty: ty_u32, init: Some(expr1), }, crate::Span::default(), ); // This is retained by _p. let expr2 = module .global_expressions .append(crate::Expression::Override(o), crate::Span::default()); // Since this is named, it will be retained. let _p = module.overrides.append( crate::Override { name: Some("p".to_string()), id: None, ty: ty_u32, init: Some(expr2), }, crate::Span::default(), ); let mut validator = super::valid::Validator::new( super::valid::ValidationFlags::all(), super::valid::Capabilities::all(), ); assert!(validator.validate(&module).is_ok()); compact(&mut module, KeepUnused::Yes); assert!(validator.validate(&module).is_ok()); } #[test] fn local_expression_override() { let mut module: crate::Module = Default::default(); let ty_u32 = module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::U32), }, crate::Span::default(), ); // This will only be retained if we trace the initializers // of overrides referred to by `Expression::Override` in a function. let expr1 = module.global_expressions.append( crate::Expression::Literal(crate::Literal::U32(1)), crate::Span::default(), ); // This will be removed by compaction. let _unused_override = module.overrides.append( crate::Override { name: None, id: Some(41), ty: ty_u32, init: None, }, crate::Span::default(), ); // This will only be traced via an `Expression::Override` in a function. let o = module.overrides.append( crate::Override { name: None, id: Some(42), ty: ty_u32, init: Some(expr1), }, crate::Span::default(), ); let mut fun = crate::Function { result: Some(crate::FunctionResult { ty: ty_u32, binding: None, }), ..crate::Function::default() }; // This is used by the `Return` statement. let o_expr = fun .expressions .append(crate::Expression::Override(o), crate::Span::default()); fun.body.push( crate::Statement::Return { value: Some(o_expr), }, crate::Span::default(), ); module.functions.append(fun, crate::Span::default()); let mut validator = super::valid::Validator::new( super::valid::ValidationFlags::all(), super::valid::Capabilities::all(), ); assert!(validator.validate(&module).is_ok()); compact(&mut module, KeepUnused::Yes); assert!(validator.validate(&module).is_ok()); } #[test] fn unnamed_constant_type() { let mut module = crate::Module::default(); let nowhere = crate::Span::default(); // This type is used only by the unnamed constant. let ty_u32 = module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::U32), }, nowhere, ); // This type is used by the named constant. let ty_vec_u32 = module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Vector { size: crate::VectorSize::Bi, scalar: crate::Scalar::U32, }, }, nowhere, ); let unnamed_init = module .global_expressions .append(crate::Expression::Literal(crate::Literal::U32(0)), nowhere); let unnamed_constant = module.constants.append( crate::Constant { name: None, ty: ty_u32, init: unnamed_init, }, nowhere, ); // The named constant is initialized using a Splat expression, to // give the named constant a type distinct from the unnamed // constant's. let unnamed_constant_expr = module .global_expressions .append(crate::Expression::Constant(unnamed_constant), nowhere); let named_init = module.global_expressions.append( crate::Expression::Splat { size: crate::VectorSize::Bi, value: unnamed_constant_expr, }, nowhere, ); let _named_constant = module.constants.append( crate::Constant { name: Some("totally_named".to_string()), ty: ty_vec_u32, init: named_init, }, nowhere, ); let mut validator = super::valid::Validator::new( super::valid::ValidationFlags::all(), super::valid::Capabilities::all(), ); assert!(validator.validate(&module).is_ok()); compact(&mut module, KeepUnused::Yes); assert!(validator.validate(&module).is_ok()); } #[test] fn unnamed_override_type() { let mut module = crate::Module::default(); let nowhere = crate::Span::default(); // This type is used only by the unnamed override. let ty_u32 = module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::U32), }, nowhere, ); // This type is used by the named override. let ty_i32 = module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::I32), }, nowhere, ); let unnamed_init = module .global_expressions .append(crate::Expression::Literal(crate::Literal::U32(0)), nowhere); let unnamed_override = module.overrides.append( crate::Override { name: None, id: Some(42), ty: ty_u32, init: Some(unnamed_init), }, nowhere, ); // The named override is initialized using a Splat expression, to // give the named override a type distinct from the unnamed // override's. let unnamed_override_expr = module .global_expressions .append(crate::Expression::Override(unnamed_override), nowhere); let named_init = module.global_expressions.append( crate::Expression::As { expr: unnamed_override_expr, kind: crate::ScalarKind::Sint, convert: None, }, nowhere, ); let _named_override = module.overrides.append( crate::Override { name: Some("totally_named".to_string()), id: None, ty: ty_i32, init: Some(named_init), }, nowhere, ); let mut validator = super::valid::Validator::new( super::valid::ValidationFlags::all(), super::valid::Capabilities::all(), ); assert!(validator.validate(&module).is_ok()); compact(&mut module, KeepUnused::Yes); assert!(validator.validate(&module).is_ok()); } ================================================ FILE: naga/src/compact/statements.rs ================================================ use alloc::{vec, vec::Vec}; use super::functions::FunctionTracer; use super::FunctionMap; use crate::arena::Handle; use crate::compact::handle_set_map::HandleMap; impl FunctionTracer<'_> { pub fn trace_block(&mut self, block: &[crate::Statement]) { let mut worklist: Vec<&[crate::Statement]> = vec![block]; while let Some(last) = worklist.pop() { for stmt in last { use crate::Statement as St; match *stmt { St::Emit(ref _range) => { // If we come across a statement that actually uses an // expression in this range, it'll get traced from // there. But since evaluating expressions has no // effect, we don't need to assume that everything // emitted is live. } St::Block(ref block) => worklist.push(block), St::If { condition, ref accept, ref reject, } => { self.expressions_used.insert(condition); worklist.push(accept); worklist.push(reject); } St::Switch { selector, ref cases, } => { self.expressions_used.insert(selector); for case in cases { worklist.push(&case.body); } } St::Loop { ref body, ref continuing, break_if, } => { if let Some(break_if) = break_if { self.expressions_used.insert(break_if); } worklist.push(body); worklist.push(continuing); } St::Return { value: Some(value) } => { self.expressions_used.insert(value); } St::Store { pointer, value } => { self.expressions_used.insert(pointer); self.expressions_used.insert(value); } St::ImageStore { image, coordinate, array_index, value, } => { self.expressions_used.insert(image); self.expressions_used.insert(coordinate); if let Some(array_index) = array_index { self.expressions_used.insert(array_index); } self.expressions_used.insert(value); } St::Atomic { pointer, ref fun, value, result, } => { self.expressions_used.insert(pointer); self.trace_atomic_function(fun); self.expressions_used.insert(value); if let Some(result) = result { self.expressions_used.insert(result); } } St::ImageAtomic { image, coordinate, array_index, fun: _, value, } => { self.expressions_used.insert(image); self.expressions_used.insert(coordinate); if let Some(array_index) = array_index { self.expressions_used.insert(array_index); } self.expressions_used.insert(value); } St::WorkGroupUniformLoad { pointer, result } => { self.expressions_used.insert(pointer); self.expressions_used.insert(result); } St::Call { function, ref arguments, result, } => { self.trace_call(function); for expr in arguments { self.expressions_used.insert(*expr); } if let Some(result) = result { self.expressions_used.insert(result); } } St::RayQuery { query, ref fun } => { self.expressions_used.insert(query); self.trace_ray_query_function(fun); } St::SubgroupBallot { result, predicate } => { if let Some(predicate) = predicate { self.expressions_used.insert(predicate); } self.expressions_used.insert(result); } St::SubgroupCollectiveOperation { op: _, collective_op: _, argument, result, } => { self.expressions_used.insert(argument); self.expressions_used.insert(result); } St::SubgroupGather { mode, argument, result, } => { match mode { crate::GatherMode::BroadcastFirst => {} crate::GatherMode::Broadcast(index) | crate::GatherMode::Shuffle(index) | crate::GatherMode::ShuffleDown(index) | crate::GatherMode::ShuffleUp(index) | crate::GatherMode::ShuffleXor(index) | crate::GatherMode::QuadBroadcast(index) => { self.expressions_used.insert(index); } crate::GatherMode::QuadSwap(_) => {} } self.expressions_used.insert(argument); self.expressions_used.insert(result); } St::CooperativeStore { target, ref data } => { self.expressions_used.insert(target); self.expressions_used.insert(data.pointer); self.expressions_used.insert(data.stride); } St::RayPipelineFunction(func) => match func { crate::RayPipelineFunction::TraceRay { acceleration_structure, descriptor, payload, } => { self.expressions_used.insert(acceleration_structure); self.expressions_used.insert(descriptor); self.expressions_used.insert(payload); } }, // Trivial statements. St::Break | St::Continue | St::Kill | St::ControlBarrier(_) | St::MemoryBarrier(_) | St::Return { value: None } => {} } } } } fn trace_atomic_function(&mut self, fun: &crate::AtomicFunction) { use crate::AtomicFunction as Af; match *fun { Af::Exchange { compare: Some(expr), } => { self.expressions_used.insert(expr); } Af::Exchange { compare: None } | Af::Add | Af::Subtract | Af::And | Af::ExclusiveOr | Af::InclusiveOr | Af::Min | Af::Max => {} } } fn trace_ray_query_function(&mut self, fun: &crate::RayQueryFunction) { use crate::RayQueryFunction as Qf; match *fun { Qf::Initialize { acceleration_structure, descriptor, } => { self.expressions_used.insert(acceleration_structure); self.expressions_used.insert(descriptor); } Qf::Proceed { result } => { self.expressions_used.insert(result); } Qf::GenerateIntersection { hit_t } => { self.expressions_used.insert(hit_t); } Qf::ConfirmIntersection => {} Qf::Terminate => {} } } } impl FunctionMap { /// Adjust statements in the body of `function`. /// /// Adjusts expressions using `self.expressions`, and adjusts calls to other /// functions using `function_map`. pub fn adjust_body( &self, function: &mut crate::Function, function_map: &HandleMap, ) { let block = &mut function.body; let mut worklist: Vec<&mut [crate::Statement]> = vec![block]; let adjust = |handle: &mut Handle| { self.expressions.adjust(handle); }; while let Some(last) = worklist.pop() { for stmt in last { use crate::Statement as St; match *stmt { St::Emit(ref mut range) => { self.expressions.adjust_range(range, &function.expressions); } St::Block(ref mut block) => worklist.push(block), St::If { ref mut condition, ref mut accept, ref mut reject, } => { adjust(condition); worklist.push(accept); worklist.push(reject); } St::Switch { ref mut selector, ref mut cases, } => { adjust(selector); for case in cases { worklist.push(&mut case.body); } } St::Loop { ref mut body, ref mut continuing, ref mut break_if, } => { if let Some(ref mut break_if) = *break_if { adjust(break_if); } worklist.push(body); worklist.push(continuing); } St::Return { value: Some(ref mut value), } => adjust(value), St::Store { ref mut pointer, ref mut value, } => { adjust(pointer); adjust(value); } St::ImageStore { ref mut image, ref mut coordinate, ref mut array_index, ref mut value, } => { adjust(image); adjust(coordinate); if let Some(ref mut array_index) = *array_index { adjust(array_index); } adjust(value); } St::Atomic { ref mut pointer, ref mut fun, ref mut value, ref mut result, } => { adjust(pointer); self.adjust_atomic_function(fun); adjust(value); if let Some(ref mut result) = *result { adjust(result); } } St::ImageAtomic { ref mut image, ref mut coordinate, ref mut array_index, fun: _, ref mut value, } => { adjust(image); adjust(coordinate); if let Some(ref mut array_index) = *array_index { adjust(array_index); } adjust(value); } St::WorkGroupUniformLoad { ref mut pointer, ref mut result, } => { adjust(pointer); adjust(result); } St::Call { ref mut function, ref mut arguments, ref mut result, } => { function_map.adjust(function); for expr in arguments { adjust(expr); } if let Some(ref mut result) = *result { adjust(result); } } St::RayQuery { ref mut query, ref mut fun, } => { adjust(query); self.adjust_ray_query_function(fun); } St::SubgroupBallot { ref mut result, ref mut predicate, } => { if let Some(ref mut predicate) = *predicate { adjust(predicate); } adjust(result); } St::SubgroupCollectiveOperation { op: _, collective_op: _, ref mut argument, ref mut result, } => { adjust(argument); adjust(result); } St::SubgroupGather { ref mut mode, ref mut argument, ref mut result, } => { match *mode { crate::GatherMode::BroadcastFirst => {} crate::GatherMode::Broadcast(ref mut index) | crate::GatherMode::Shuffle(ref mut index) | crate::GatherMode::ShuffleDown(ref mut index) | crate::GatherMode::ShuffleUp(ref mut index) | crate::GatherMode::ShuffleXor(ref mut index) | crate::GatherMode::QuadBroadcast(ref mut index) => adjust(index), crate::GatherMode::QuadSwap(_) => {} } adjust(argument); adjust(result); } St::CooperativeStore { ref mut target, ref mut data, } => { adjust(target); adjust(&mut data.pointer); adjust(&mut data.stride); } St::RayPipelineFunction(ref mut func) => match *func { crate::RayPipelineFunction::TraceRay { ref mut acceleration_structure, ref mut descriptor, ref mut payload, } => { adjust(acceleration_structure); adjust(descriptor); adjust(payload); } }, // Trivial statements. St::Break | St::Continue | St::Kill | St::ControlBarrier(_) | St::MemoryBarrier(_) | St::Return { value: None } => {} } } } } fn adjust_atomic_function(&self, fun: &mut crate::AtomicFunction) { use crate::AtomicFunction as Af; match *fun { Af::Exchange { compare: Some(ref mut expr), } => { self.expressions.adjust(expr); } Af::Exchange { compare: None } | Af::Add | Af::Subtract | Af::And | Af::ExclusiveOr | Af::InclusiveOr | Af::Min | Af::Max => {} } } fn adjust_ray_query_function(&self, fun: &mut crate::RayQueryFunction) { use crate::RayQueryFunction as Qf; match *fun { Qf::Initialize { ref mut acceleration_structure, ref mut descriptor, } => { self.expressions.adjust(acceleration_structure); self.expressions.adjust(descriptor); } Qf::Proceed { ref mut result } => { self.expressions.adjust(result); } Qf::GenerateIntersection { ref mut hit_t } => { self.expressions.adjust(hit_t); } Qf::ConfirmIntersection => {} Qf::Terminate => {} } } } ================================================ FILE: naga/src/compact/types.rs ================================================ use super::{HandleSet, ModuleMap}; use crate::Handle; pub struct TypeTracer<'a> { pub overrides: &'a crate::Arena, pub types_used: &'a mut HandleSet, pub expressions_used: &'a mut HandleSet, pub overrides_used: &'a mut HandleSet, } impl TypeTracer<'_> { pub fn trace_type(&mut self, ty: &crate::Type) { use crate::TypeInner as Ti; match ty.inner { // Types that do not contain handles. Ti::Scalar { .. } | Ti::Vector { .. } | Ti::Matrix { .. } | Ti::CooperativeMatrix { .. } | Ti::Atomic { .. } | Ti::ValuePointer { .. } | Ti::Image { .. } | Ti::Sampler { .. } | Ti::AccelerationStructure { .. } | Ti::RayQuery { .. } => {} // Types that do contain handles. Ti::Array { base, size, stride: _, } | Ti::BindingArray { base, size } => { self.types_used.insert(base); match size { crate::ArraySize::Pending(handle) => { self.overrides_used.insert(handle); let r#override = &self.overrides[handle]; self.types_used.insert(r#override.ty); if let Some(expr) = r#override.init { self.expressions_used.insert(expr); } } crate::ArraySize::Constant(_) | crate::ArraySize::Dynamic => {} } } Ti::Pointer { base, space: _ } => { self.types_used.insert(base); } Ti::Struct { ref members, span: _, } => { self.types_used.insert_iter(members.iter().map(|m| m.ty)); } } } } impl ModuleMap { pub fn adjust_type(&self, ty: &mut crate::Type) { let adjust = |ty: &mut Handle| self.types.adjust(ty); use crate::TypeInner as Ti; match ty.inner { // Types that do not contain handles. Ti::Scalar(_) | Ti::Vector { .. } | Ti::Matrix { .. } | Ti::CooperativeMatrix { .. } | Ti::Atomic(_) | Ti::ValuePointer { .. } | Ti::Image { .. } | Ti::Sampler { .. } | Ti::AccelerationStructure { .. } | Ti::RayQuery { .. } => {} // Types that do contain handles. Ti::Pointer { ref mut base, space: _, } => adjust(base), Ti::Array { ref mut base, ref mut size, stride: _, } | Ti::BindingArray { ref mut base, ref mut size, } => { adjust(base); match *size { crate::ArraySize::Pending(ref mut r#override) => { self.overrides.adjust(r#override); } crate::ArraySize::Constant(_) | crate::ArraySize::Dynamic => {} } } Ti::Struct { ref mut members, span: _, } => { for member in members { self.types.adjust(&mut member.ty); } } }; } } ================================================ FILE: naga/src/diagnostic_filter.rs ================================================ //! [`DiagnosticFilter`]s and supporting functionality. use alloc::boxed::Box; use crate::{Arena, Handle}; #[cfg(feature = "wgsl-in")] use crate::FastIndexMap; #[cfg(feature = "wgsl-in")] use crate::Span; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; #[cfg(feature = "deserialize")] use serde::Deserialize; #[cfg(feature = "serialize")] use serde::Serialize; /// A severity set on a [`DiagnosticFilter`]. /// /// #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum Severity { Off, Info, Warning, Error, } impl Severity { /// Checks whether this severity is [`Self::Error`]. /// /// Naga does not yet support diagnostic items at lesser severities than /// [`Severity::Error`]. When this is implemented, this method should be deleted, and the /// severity should be used directly for reporting diagnostics. pub(crate) fn report_diag( self, err: E, log_handler: impl FnOnce(E, log::Level), ) -> Result<(), E> { let log_level = match self { Severity::Off => return Ok(()), // NOTE: These severities are not yet reported. Severity::Info => log::Level::Info, Severity::Warning => log::Level::Warn, Severity::Error => return Err(err), }; log_handler(err, log_level); Ok(()) } } /// A filterable triggering rule in a [`DiagnosticFilter`]. /// /// #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum FilterableTriggeringRule { Standard(StandardFilterableTriggeringRule), Unknown(Box), User(Box<[Box; 2]>), } /// A filterable triggering rule in a [`DiagnosticFilter`]. /// /// #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum StandardFilterableTriggeringRule { DerivativeUniformity, } impl StandardFilterableTriggeringRule { /// The default severity associated with this triggering rule. /// /// See for a table of default /// severities. pub(crate) const fn default_severity(self) -> Severity { match self { Self::DerivativeUniformity => Severity::Error, } } } /// A filtering rule that modifies how diagnostics are emitted for shaders. /// /// #[derive(Clone, Debug)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct DiagnosticFilter { pub new_severity: Severity, pub triggering_rule: FilterableTriggeringRule, } /// Determines whether [`DiagnosticFilterMap::add`] should consider full duplicates a conflict. /// /// In WGSL, directive position does not consider this case a conflict, while attribute position /// does. #[cfg(feature = "wgsl-in")] pub(crate) enum ShouldConflictOnFullDuplicate { /// Use this for attributes in WGSL. Yes, /// Use this for directives in WGSL. No, } /// A map from diagnostic filters to their severity and span. /// /// Front ends can use this to collect the set of filters applied to a /// particular language construct, and detect duplicate/conflicting filters. /// /// For example, WGSL has global diagnostic filters that apply to the entire /// module, and diagnostic range filter attributes that apply to a specific /// function, statement, or other smaller construct. The set of filters applied /// to any given construct must not conflict, but they can be overridden by /// filters on other constructs nested within it. A front end can use a /// `DiagnosticFilterMap` to collect the filters applied to a single construct, /// using the [`add`] method's error checking to forbid conflicts. /// /// For each filter it contains, a `DiagnosticFilterMap` records the requested /// severity, and the source span of the filter itself. /// /// [`add`]: DiagnosticFilterMap::add #[derive(Clone, Debug, Default)] #[cfg(feature = "wgsl-in")] pub(crate) struct DiagnosticFilterMap(FastIndexMap); #[cfg(feature = "wgsl-in")] impl DiagnosticFilterMap { pub(crate) fn new() -> Self { Self::default() } /// Add the given `diagnostic_filter` parsed at the given `span` to this map. pub(crate) fn add( &mut self, diagnostic_filter: DiagnosticFilter, span: Span, should_conflict_on_full_duplicate: ShouldConflictOnFullDuplicate, ) -> Result<(), ConflictingDiagnosticRuleError> { use indexmap::map::Entry; let &mut Self(ref mut diagnostic_filters) = self; let DiagnosticFilter { new_severity, triggering_rule, } = diagnostic_filter; match diagnostic_filters.entry(triggering_rule.clone()) { Entry::Vacant(entry) => { entry.insert((new_severity, span)); } Entry::Occupied(entry) => { let &(first_severity, first_span) = entry.get(); let should_conflict_on_full_duplicate = match should_conflict_on_full_duplicate { ShouldConflictOnFullDuplicate::Yes => true, ShouldConflictOnFullDuplicate::No => false, }; if first_severity != new_severity || should_conflict_on_full_duplicate { return Err(ConflictingDiagnosticRuleError { triggering_rule_spans: [first_span, span], }); } } } Ok(()) } /// Were any rules specified? pub(crate) fn is_empty(&self) -> bool { let &Self(ref map) = self; map.is_empty() } /// Returns the spans of all contained rules. pub(crate) fn spans(&self) -> impl Iterator + '_ { let &Self(ref map) = self; map.iter().map(|(_, &(_, span))| span) } } #[cfg(feature = "wgsl-in")] impl IntoIterator for DiagnosticFilterMap { type Item = (FilterableTriggeringRule, (Severity, Span)); type IntoIter = indexmap::map::IntoIter; fn into_iter(self) -> Self::IntoIter { let Self(this) = self; this.into_iter() } } /// An error returned by [`DiagnosticFilterMap::add`] when it encounters conflicting rules. #[cfg(feature = "wgsl-in")] #[derive(Clone, Debug)] pub(crate) struct ConflictingDiagnosticRuleError { pub triggering_rule_spans: [Span; 2], } /// Represents a single parent-linking node in a tree of [`DiagnosticFilter`]s backed by a /// [`crate::Arena`]. /// /// A single element of a _tree_ of diagnostic filter rules stored in /// [`crate::Module::diagnostic_filters`]. When nodes are built by a front-end, module-applicable /// filter rules are chained together in runs based on parse site. For instance, given the /// following: /// /// - Module-applicable rules `a` and `b`. /// - Rules `c` and `d`, applicable to an entry point called `c_and_d_func`. /// - Rule `e`, applicable to an entry point called `e_func`. /// /// The tree would be represented as follows: /// /// ```text /// a <- b /// ^ /// |- c <- d /// | /// \- e /// ``` /// /// ...where: /// /// - `d` is the first leaf consulted by validation in `c_and_d_func`. /// - `e` is the first leaf consulted by validation in `e_func`. #[derive(Clone, Debug)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct DiagnosticFilterNode { pub inner: DiagnosticFilter, pub parent: Option>, } impl DiagnosticFilterNode { /// Finds the most specific filter rule applicable to `triggering_rule` from the chain of /// diagnostic filter rules in `arena`, starting with `node`, and returns its severity. If none /// is found, return the value of [`StandardFilterableTriggeringRule::default_severity`]. /// /// When `triggering_rule` is not applicable to this node, its parent is consulted recursively. pub(crate) fn search( node: Option>, arena: &Arena, triggering_rule: StandardFilterableTriggeringRule, ) -> Severity { let mut next = node; while let Some(handle) = next { let node = &arena[handle]; let &Self { ref inner, parent } = node; let &DiagnosticFilter { triggering_rule: ref rule, new_severity, } = inner; if rule == &FilterableTriggeringRule::Standard(triggering_rule) { return new_severity; } next = parent; } triggering_rule.default_severity() } } ================================================ FILE: naga/src/error.rs ================================================ use alloc::{borrow::Cow, boxed::Box, string::String}; use core::{error::Error, fmt}; #[derive(Clone, Debug)] pub struct ShaderError { /// The source code of the shader. pub source: String, pub label: Option, pub inner: Box, } #[cfg(feature = "wgsl-in")] impl fmt::Display for ShaderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let label = self.label.as_deref().unwrap_or_default(); let string = self.inner.emit_to_string(&self.source); write!(f, "\nShader '{label}' parsing {string}") } } #[cfg(feature = "glsl-in")] impl fmt::Display for ShaderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let label = self.label.as_deref().unwrap_or_default(); let string = self.inner.emit_to_string(&self.source); write!(f, "\nShader '{label}' parsing {string}") } } #[cfg(feature = "spv-in")] impl fmt::Display for ShaderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let label = self.label.as_deref().unwrap_or_default(); let string = self.inner.emit_to_string(&self.source); write!(f, "\nShader '{label}' parsing {string}") } } impl fmt::Display for ShaderError> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use codespan_reporting::{files::SimpleFile, term}; let label = self.label.as_deref().unwrap_or_default(); let files = SimpleFile::new(label, replace_control_chars(&self.source)); let config = term::Config::default(); let mut writer = DiagnosticBuffer::new(); writer .emit_to_self(&config, &files, &self.inner.diagnostic()) .expect("cannot write error"); let writer = writer.into_string(); write!(f, "\nShader validation {writer}") } } cfg_if::cfg_if! { if #[cfg(feature = "termcolor")] { type DiagnosticBufferInner = codespan_reporting::term::termcolor::NoColor>; } else if #[cfg(feature = "stderr")] { type DiagnosticBufferInner = alloc::vec::Vec; } else { type DiagnosticBufferInner = String; } } cfg_if::cfg_if! { if #[cfg(all(feature = "stderr", feature = "termcolor"))] { pub(crate) use codespan_reporting::term::termcolor::WriteColor as _ErrorWrite; } else if #[cfg(feature = "stderr")] { pub(crate) use std::io::Write as _ErrorWrite; } } #[cfg(feature = "stderr")] pub(crate) use _ErrorWrite as ErrorWrite; #[cfg(feature = "stderr")] #[cfg_attr( not(any(feature = "spv-in", feature = "glsl-in")), expect( dead_code, reason = "only need `emit_to_writer` with an appropriate front-end." ) )] pub(crate) fn emit_to_writer<'files, F: codespan_reporting::files::Files<'files> + ?Sized>( writer: &mut impl ErrorWrite, config: &codespan_reporting::term::Config, files: &'files F, diagnostic: &codespan_reporting::diagnostic::Diagnostic, ) -> Result<(), codespan_reporting::files::Error> { cfg_if::cfg_if! { if #[cfg(feature = "termcolor")] { codespan_reporting::term::emit_to_write_style(writer, config, files, diagnostic) } else { codespan_reporting::term::emit_to_io_write(writer, config, files, diagnostic) } } } pub(crate) struct DiagnosticBuffer { inner: DiagnosticBufferInner, } impl DiagnosticBuffer { #[cfg_attr( not(feature = "termcolor"), expect( clippy::missing_const_for_fn, reason = "`NoColor::new` isn't `const`, but other `inner`s are." ) )] pub fn new() -> Self { cfg_if::cfg_if! { if #[cfg(feature = "termcolor")] { let inner = codespan_reporting::term::termcolor::NoColor::new(alloc::vec::Vec::new()); } else if #[cfg(feature = "stderr")] { let inner = alloc::vec::Vec::new(); } else { let inner = String::new(); } }; Self { inner } } pub fn emit_to_self<'files, F: codespan_reporting::files::Files<'files> + ?Sized>( &mut self, config: &codespan_reporting::term::Config, files: &'files F, diagnostic: &codespan_reporting::diagnostic::Diagnostic, ) -> Result<(), codespan_reporting::files::Error> { cfg_if::cfg_if! { if #[cfg(feature = "termcolor")] { codespan_reporting::term::emit_to_write_style(&mut self.inner, config, files, diagnostic) } else if #[cfg(feature = "stderr")] { codespan_reporting::term::emit_to_io_write(&mut self.inner, config, files, diagnostic) } else { codespan_reporting::term::emit_to_string(&mut self.inner, config, files, diagnostic) } } } pub fn into_string(self) -> String { let Self { inner } = self; cfg_if::cfg_if! { if #[cfg(feature = "termcolor")] { String::from_utf8(inner.into_inner()).unwrap() } else if #[cfg(feature = "stderr")] { String::from_utf8(inner).unwrap() } else { inner } } } } impl Error for ShaderError where ShaderError: fmt::Display, E: Error + 'static, { fn source(&self) -> Option<&(dyn Error + 'static)> { self.inner.source() } } pub(crate) fn replace_control_chars(s: &str) -> Cow<'_, str> { const REPLACEMENT_CHAR: &str = "\u{FFFD}"; debug_assert_eq!( REPLACEMENT_CHAR.chars().next().unwrap(), char::REPLACEMENT_CHARACTER ); let mut res = Cow::Borrowed(s); let mut offset = 0; while let Some(found_pos) = res[offset..].find(|c: char| c.is_control() && !c.is_whitespace()) { offset += found_pos; let found_len = res[offset..].chars().next().unwrap().len_utf8(); res.to_mut() .replace_range(offset..offset + found_len, REPLACEMENT_CHAR); offset += REPLACEMENT_CHAR.len(); } res } #[test] fn test_replace_control_chars() { // The UTF-8 encoding of \u{0080} is multiple bytes. let input = "Foo\u{0080}Bar\u{0001}Baz\n"; let expected = "Foo\u{FFFD}Bar\u{FFFD}Baz\n"; assert_eq!(replace_control_chars(input), expected); } ================================================ FILE: naga/src/front/atomic_upgrade.rs ================================================ //! Upgrade the types of scalars observed to be accessed as atomics to [`Atomic`] types. //! //! In SPIR-V, atomic operations can be applied to any scalar value, but in Naga //! IR atomic operations can only be applied to values of type [`Atomic`]. Naga //! IR's restriction matches Metal Shading Language and WGSL, so we don't want //! to relax that. Instead, when the SPIR-V front end observes a value being //! accessed using atomic instructions, it promotes the value's type from //! [`Scalar`] to [`Atomic`]. This module implements `Module::upgrade_atomics`, //! the function that makes that change. //! //! Atomics can only appear in global variables in the [`Storage`] and //! [`Workgroup`] address spaces. These variables can either have `Atomic` types //! themselves, or be [`Array`]s of such, or be [`Struct`]s containing such. //! So we only need to change the types of globals and struct fields. //! //! Naga IR [`Load`] expressions and [`Store`] statements can operate directly //! on [`Atomic`] values, retrieving and depositing ordinary [`Scalar`] values, //! so changing the types doesn't have much effect on the code that operates on //! those values. //! //! Future work: //! //! - The GLSL front end could use this transformation as well. //! //! [`Atomic`]: TypeInner::Atomic //! [`Scalar`]: TypeInner::Scalar //! [`Storage`]: crate::AddressSpace::Storage //! [`WorkGroup`]: crate::AddressSpace::WorkGroup //! [`Array`]: TypeInner::Array //! [`Struct`]: TypeInner::Struct //! [`Load`]: crate::Expression::Load //! [`Store`]: crate::Statement::Store use alloc::{format, sync::Arc}; use core::sync::atomic::AtomicUsize; use crate::{GlobalVariable, Handle, Module, Type, TypeInner}; #[derive(Clone, Debug, thiserror::Error)] pub enum Error { #[error("encountered an unsupported expression")] Unsupported, #[error("unexpected end of struct field access indices")] UnexpectedEndOfIndices, #[error("encountered unsupported global initializer in an atomic variable")] GlobalInitUnsupported, #[error("expected to find a global variable")] GlobalVariableMissing, #[error("atomic compare exchange requires a scalar base type")] CompareExchangeNonScalarBaseType, } #[derive(Clone, Default)] struct Padding(Arc); impl core::fmt::Display for Padding { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { for _ in 0..self.0.load(core::sync::atomic::Ordering::Relaxed) { f.write_str(" ")?; } Ok(()) } } impl Drop for Padding { fn drop(&mut self) { let _ = self.0.fetch_sub(1, core::sync::atomic::Ordering::Relaxed); } } impl Padding { fn trace(&self, msg: impl core::fmt::Display, t: impl core::fmt::Debug) { format!("{msg} {t:#?}") .split('\n') .for_each(|ln| log::trace!("{self}{ln}")); } fn debug(&self, msg: impl core::fmt::Display, t: impl core::fmt::Debug) { format!("{msg} {t:#?}") .split('\n') .for_each(|ln| log::debug!("{self}{ln}")); } fn inc_padding(&self) -> Padding { let _ = self.0.fetch_add(1, core::sync::atomic::Ordering::Relaxed); self.clone() } } #[derive(Debug, Default)] pub struct Upgrades { /// Global variables that we've accessed using atomic operations. /// /// This includes globals with composite types (arrays, structs) where we've /// only accessed some components (elements, fields) atomically. globals: crate::arena::HandleSet, /// Struct fields that we've accessed using atomic operations. /// /// Each key refers to some [`Struct`] type, and each value is a set of /// the indices of the fields in that struct that have been accessed /// atomically. /// /// This includes fields with composite types (arrays, structs) /// of which we've only accessed some components (elements, fields) /// atomically. /// /// [`Struct`]: crate::TypeInner::Struct fields: crate::FastHashMap, bit_set::BitSet>, } impl Upgrades { pub fn insert_global(&mut self, global: Handle) { self.globals.insert(global); } pub fn insert_field(&mut self, struct_type: Handle, field: usize) { self.fields.entry(struct_type).or_default().insert(field); } pub fn is_empty(&self) -> bool { self.globals.is_empty() } } struct UpgradeState<'a> { padding: Padding, module: &'a mut Module, /// A map from old types to their upgraded versions. /// /// This ensures we never try to rebuild a type more than once. upgraded_types: crate::FastHashMap, Handle>, } impl UpgradeState<'_> { fn inc_padding(&self) -> Padding { self.padding.inc_padding() } /// Get a type equivalent to `ty`, but with [`Scalar`] leaves upgraded to [`Atomic`] scalars. /// /// If such a type already exists in `self.module.types`, return its handle. /// Otherwise, construct a new one and return that handle. /// /// If `ty` is a [`Pointer`], [`Array`], [`BindingArray`], recurse into the /// type and upgrade its leaf types. /// /// If `ty` is a [`Struct`], recurse into it and upgrade only those fields /// whose indices appear in `field_indices`. /// /// The existing type is not affected. /// /// [`Scalar`]: crate::TypeInner::Scalar /// [`Atomic`]: crate::TypeInner::Atomic /// [`Pointer`]: crate::TypeInner::Pointer /// [`Array`]: crate::TypeInner::Array /// [`Struct`]: crate::TypeInner::Struct /// [`BindingArray`]: crate::TypeInner::BindingArray fn upgrade_type( &mut self, ty: Handle, upgrades: &Upgrades, ) -> Result, Error> { let padding = self.inc_padding(); padding.trace("visiting type: ", ty); // If we've already upgraded this type, return the handle we produced at // the time. if let Some(&new) = self.upgraded_types.get(&ty) { return Ok(new); } let inner = match self.module.types[ty].inner { TypeInner::Scalar(scalar) => { log::trace!("{padding}hit the scalar leaf, replacing with an atomic"); TypeInner::Atomic(scalar) } TypeInner::Pointer { base, space } => TypeInner::Pointer { base: self.upgrade_type(base, upgrades)?, space, }, TypeInner::Array { base, size, stride } => TypeInner::Array { base: self.upgrade_type(base, upgrades)?, size, stride, }, TypeInner::Struct { ref members, span } => { // If no field or subfield of this struct was ever accessed // atomically, no change is needed. We should never have arrived here. let Some(fields) = upgrades.fields.get(&ty) else { unreachable!("global or field incorrectly flagged as atomically accessed"); }; let mut new_members = members.clone(); for field in fields { new_members[field].ty = self.upgrade_type(new_members[field].ty, upgrades)?; } TypeInner::Struct { members: new_members, span, } } TypeInner::BindingArray { base, size } => TypeInner::BindingArray { base: self.upgrade_type(base, upgrades)?, size, }, _ => return Ok(ty), }; // At this point, we have a `TypeInner` that is the upgraded version of // `ty`. Find a suitable `Type` for this, creating a new one if // necessary, and return its handle. let r#type = &self.module.types[ty]; let span = self.module.types.get_span(ty); let new_type = Type { name: r#type.name.clone(), inner, }; padding.debug("ty: ", ty); padding.debug("from: ", r#type); padding.debug("to: ", &new_type); let new_handle = self.module.types.insert(new_type, span); self.upgraded_types.insert(ty, new_handle); Ok(new_handle) } fn upgrade_all(&mut self, upgrades: &Upgrades) -> Result<(), Error> { for handle in upgrades.globals.iter() { let padding = self.inc_padding(); let global = &self.module.global_variables[handle]; padding.trace("visiting global variable: ", handle); padding.trace("var: ", global); if global.init.is_some() { return Err(Error::GlobalInitUnsupported); } let var_ty = global.ty; let new_ty = self.upgrade_type(var_ty, upgrades)?; if new_ty != var_ty { padding.debug("upgrading global variable: ", handle); padding.debug("from ty: ", var_ty); padding.debug("to ty: ", new_ty); self.module.global_variables[handle].ty = new_ty; } } Ok(()) } } impl Module { /// Upgrade `global_var_handles` to have [`Atomic`] leaf types. /// /// [`Atomic`]: TypeInner::Atomic pub(crate) fn upgrade_atomics(&mut self, upgrades: &Upgrades) -> Result<(), Error> { let mut state = UpgradeState { padding: Default::default(), module: self, upgraded_types: crate::FastHashMap::with_capacity_and_hasher( upgrades.fields.len(), Default::default(), ), }; state.upgrade_all(upgrades)?; Ok(()) } } ================================================ FILE: naga/src/front/glsl/ast.rs ================================================ use alloc::{borrow::Cow, string::String, vec::Vec}; use core::fmt; use super::{builtins::MacroCall, Span}; use crate::{ AddressSpace, BinaryOperator, Binding, Constant, Expression, Function, GlobalVariable, Handle, Interpolation, Literal, Override, Sampling, StorageAccess, Type, UnaryOperator, }; #[derive(Debug, Clone, Copy)] pub enum GlobalLookupKind { Variable(Handle), Constant(Handle, Handle), Override(Handle, Handle), BlockSelect(Handle, u32), } #[derive(Debug, Clone, Copy)] pub struct GlobalLookup { pub kind: GlobalLookupKind, pub entry_arg: Option, pub mutable: bool, } #[derive(Debug, Clone)] pub struct ParameterInfo { pub qualifier: ParameterQualifier, /// Whether the parameter should be treated as a depth image instead of a /// sampled image. pub depth: bool, } /// How the function is implemented #[derive(Clone, Copy)] pub enum FunctionKind { /// The function is user defined Call(Handle), /// The function is a builtin Macro(MacroCall), } impl fmt::Debug for FunctionKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Self::Call(_) => write!(f, "Call"), Self::Macro(_) => write!(f, "Macro"), } } } #[derive(Debug)] pub struct Overload { /// Normalized function parameters, modifiers are not applied pub parameters: Vec>, pub parameters_info: Vec, /// How the function is implemented pub kind: FunctionKind, /// Whether this function was already defined or is just a prototype pub defined: bool, /// Whether this overload is the one provided by the language or has /// been redeclared by the user (builtins only) pub internal: bool, /// Whether or not this function returns void (nothing) pub void: bool, } bitflags::bitflags! { /// Tracks the variations of the builtin already generated, this is needed because some /// builtins overloads can't be generated unless explicitly used, since they might cause /// unneeded capabilities to be requested #[derive(Default)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct BuiltinVariations: u32 { /// Request the standard overloads const STANDARD = 1 << 0; /// Request overloads that use the double type const DOUBLE = 1 << 1; /// Request overloads that use `samplerCubeArray(Shadow)` const CUBE_TEXTURES_ARRAY = 1 << 2; /// Request overloads that use `sampler2DMSArray` const D2_MULTI_TEXTURES_ARRAY = 1 << 3; } } #[derive(Debug, Default)] pub struct FunctionDeclaration { pub overloads: Vec, /// Tracks the builtin overload variations that were already generated pub variations: BuiltinVariations, } #[derive(Debug)] pub struct EntryArg { pub name: Option, pub binding: Binding, pub handle: Handle, pub storage: StorageQualifier, } #[derive(Debug, Clone)] pub struct VariableReference { pub expr: Handle, /// Whether the variable is of a pointer type (and needs loading) or not pub load: bool, /// Whether the value of the variable can be changed or not pub mutable: bool, pub constant: Option<(Handle, Handle)>, pub entry_arg: Option, } #[derive(Debug, Clone)] pub struct HirExpr { pub kind: HirExprKind, pub meta: Span, } #[derive(Debug, Clone)] pub enum HirExprKind { /// Represents a sequence of expressions. It returns the type and value of the last (i.e. right-most) expression. Sequence { exprs: Vec>, }, Access { base: Handle, index: Handle, }, Select { base: Handle, field: String, }, Literal(Literal), Binary { left: Handle, op: BinaryOperator, right: Handle, }, Unary { op: UnaryOperator, expr: Handle, }, Variable(VariableReference), Call(FunctionCall), /// Represents the ternary operator in glsl (`:?`) Conditional { /// The expression that will decide which branch to take, must evaluate to a boolean condition: Handle, /// The expression that will be evaluated if [`condition`] returns `true` /// /// [`condition`]: Self::Conditional::condition accept: Handle, /// The expression that will be evaluated if [`condition`] returns `false` /// /// [`condition`]: Self::Conditional::condition reject: Handle, }, Assign { tgt: Handle, value: Handle, }, /// A prefix/postfix operator like `++` PrePostfix { /// The operation to be performed op: BinaryOperator, /// Whether this is a postfix or a prefix postfix: bool, /// The target expression expr: Handle, }, /// A method call like `what.something(a, b, c)` Method { /// expression the method call applies to (`what` in the example) expr: Handle, /// the method name (`something` in the example) name: String, /// the arguments to the method (`a`, `b`, and `c` in the example) args: Vec>, }, } #[derive(Debug, Hash, PartialEq, Eq)] pub enum QualifierKey<'a> { String(Cow<'a, str>), /// Used for `std140` and `std430` layout qualifiers Layout, /// Used for image formats Format, /// Used for `index` layout qualifiers Index, } #[derive(Debug)] pub enum QualifierValue { None, Uint(u32), Layout(StructLayout), Format(crate::StorageFormat), } #[derive(Debug, Default)] pub struct TypeQualifiers<'a> { pub span: Span, pub storage: (StorageQualifier, Span), pub invariant: Option, pub interpolation: Option<(Interpolation, Span)>, pub precision: Option<(Precision, Span)>, pub sampling: Option<(Sampling, Span)>, /// Memory qualifiers used in the declaration to set the storage access to be used /// in declarations that support it (storage images and buffers) pub storage_access: Option<(StorageAccess, Span)>, pub layout_qualifiers: crate::FastHashMap, (QualifierValue, Span)>, } impl<'a> TypeQualifiers<'a> { /// Appends `errors` with errors for all unused qualifiers pub fn unused_errors(&self, errors: &mut Vec) { if let Some(meta) = self.invariant { errors.push(super::Error { kind: super::ErrorKind::SemanticError( "Invariant qualifier can only be used in in/out variables".into(), ), meta, }); } if let Some((_, meta)) = self.interpolation { errors.push(super::Error { kind: super::ErrorKind::SemanticError( "Interpolation qualifiers can only be used in in/out variables".into(), ), meta, }); } if let Some((_, meta)) = self.sampling { errors.push(super::Error { kind: super::ErrorKind::SemanticError( "Sampling qualifiers can only be used in in/out variables".into(), ), meta, }); } if let Some((_, meta)) = self.storage_access { errors.push(super::Error { kind: super::ErrorKind::SemanticError( "Memory qualifiers can only be used in storage variables".into(), ), meta, }); } for &(_, meta) in self.layout_qualifiers.values() { errors.push(super::Error { kind: super::ErrorKind::SemanticError("Unexpected qualifier".into()), meta, }); } } /// Removes the layout qualifier with `name`, if it exists and adds an error if it isn't /// a [`QualifierValue::Uint`] pub fn uint_layout_qualifier( &mut self, name: &'a str, errors: &mut Vec, ) -> Option { match self .layout_qualifiers .remove(&QualifierKey::String(name.into())) { Some((QualifierValue::Uint(v), _)) => Some(v), Some((_, meta)) => { errors.push(super::Error { kind: super::ErrorKind::SemanticError("Qualifier expects a uint value".into()), meta, }); // Return a dummy value instead of `None` to differentiate from // the qualifier not existing, since some parts might require the // qualifier to exist and throwing another error that it doesn't // exist would be unhelpful Some(0) } _ => None, } } /// Removes the layout qualifier with `name`, if it exists and adds an error if it isn't /// a [`QualifierValue::None`] pub fn none_layout_qualifier(&mut self, name: &'a str, errors: &mut Vec) -> bool { match self .layout_qualifiers .remove(&QualifierKey::String(name.into())) { Some((QualifierValue::None, _)) => true, Some((_, meta)) => { errors.push(super::Error { kind: super::ErrorKind::SemanticError( "Qualifier doesn't expect a value".into(), ), meta, }); // Return a `true` to since the qualifier is defined and adding // another error for it not being defined would be unhelpful true } _ => false, } } } #[derive(Debug, Clone)] pub enum FunctionCallKind { TypeConstructor(Handle), Function(String), } #[derive(Debug, Clone)] pub struct FunctionCall { pub kind: FunctionCallKind, pub args: Vec>, } #[derive(Debug, Clone, Copy, PartialEq)] pub enum StorageQualifier { AddressSpace(AddressSpace), Input, Output, Const, } impl Default for StorageQualifier { fn default() -> Self { StorageQualifier::AddressSpace(AddressSpace::Function) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StructLayout { Std140, Std430, } // TODO: Encode precision hints in the IR /// A precision hint used in GLSL declarations. /// /// Precision hints can be used to either speed up shader execution or control /// the precision of arithmetic operations. /// /// To use a precision hint simply add it before the type in the declaration. /// ```glsl /// mediump float a; /// ``` /// /// The default when no precision is declared is `highp` which means that all /// operations operate with the type defined width. /// /// For `mediump` and `lowp` operations follow the spir-v /// [`RelaxedPrecision`][RelaxedPrecision] decoration semantics. /// /// [RelaxedPrecision]: https://www.khronos.org/registry/SPIR-V/specs/unified1/SPIRV.html#_a_id_relaxedprecisionsection_a_relaxed_precision #[derive(Debug, Clone, PartialEq, Copy)] pub enum Precision { /// `lowp` precision Low, /// `mediump` precision Medium, /// `highp` precision High, } #[derive(Debug, Clone, PartialEq, Copy)] pub enum ParameterQualifier { In, Out, InOut, Const, } impl ParameterQualifier { /// Returns true if the argument should be passed as a lhs expression pub const fn is_lhs(&self) -> bool { match *self { ParameterQualifier::Out | ParameterQualifier::InOut => true, _ => false, } } } /// The GLSL profile used by a shader. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Profile { /// The `core` profile, default when no profile is specified. Core, } ================================================ FILE: naga/src/front/glsl/builtins.rs ================================================ use alloc::{vec, vec::Vec}; use super::{ ast::{ BuiltinVariations, FunctionDeclaration, FunctionKind, Overload, ParameterInfo, ParameterQualifier, }, context::Context, Error, ErrorKind, Frontend, Result, }; use crate::{ BinaryOperator, DerivativeAxis as Axis, DerivativeControl as Ctrl, Expression, Handle, ImageClass, ImageDimension as Dim, ImageQuery, MathFunction, Module, RelationalFunction, SampleLevel, Scalar, ScalarKind as Sk, Span, Type, TypeInner, UnaryOperator, VectorSize, }; impl crate::ScalarKind { const fn dummy_storage_format(&self) -> crate::StorageFormat { match *self { Sk::Sint => crate::StorageFormat::R16Sint, Sk::Uint => crate::StorageFormat::R16Uint, _ => crate::StorageFormat::R16Float, } } } impl Module { /// Helper function, to create a function prototype for a builtin fn add_builtin(&mut self, args: Vec, builtin: MacroCall) -> Overload { let mut parameters = Vec::with_capacity(args.len()); let mut parameters_info = Vec::with_capacity(args.len()); for arg in args { parameters.push(self.types.insert( Type { name: None, inner: arg, }, Span::default(), )); parameters_info.push(ParameterInfo { qualifier: ParameterQualifier::In, depth: false, }); } Overload { parameters, parameters_info, kind: FunctionKind::Macro(builtin), defined: false, internal: true, void: false, } } } const fn make_coords_arg(number_of_components: usize, kind: Sk) -> TypeInner { let scalar = Scalar { kind, width: 4 }; match number_of_components { 1 => TypeInner::Scalar(scalar), _ => TypeInner::Vector { size: match number_of_components { 2 => VectorSize::Bi, 3 => VectorSize::Tri, _ => VectorSize::Quad, }, scalar, }, } } /// Inject builtins into the declaration /// /// This is done to not add a large startup cost and not increase memory /// usage if it isn't needed. pub fn inject_builtin( declaration: &mut FunctionDeclaration, module: &mut Module, name: &str, mut variations: BuiltinVariations, ) { log::trace!( "{} variations: {:?} {:?}", name, variations, declaration.variations ); // Don't regeneate variations variations.remove(declaration.variations); declaration.variations |= variations; if variations.contains(BuiltinVariations::STANDARD) { inject_standard_builtins(declaration, module, name) } if variations.contains(BuiltinVariations::DOUBLE) { inject_double_builtin(declaration, module, name) } match name { "texture" | "textureGrad" | "textureGradOffset" | "textureLod" | "textureLodOffset" | "textureOffset" | "textureProj" | "textureProjGrad" | "textureProjGradOffset" | "textureProjLod" | "textureProjLodOffset" | "textureProjOffset" => { let f = |kind, dim, arrayed, multi, shadow| { for bits in 0..=0b11 { let variant = bits & 0b1 != 0; let bias = bits & 0b10 != 0; let (proj, offset, level_type) = match name { // texture(gsampler, gvec P, [float bias]); "texture" => (false, false, TextureLevelType::None), // textureGrad(gsampler, gvec P, gvec dPdx, gvec dPdy); "textureGrad" => (false, false, TextureLevelType::Grad), // textureGradOffset(gsampler, gvec P, gvec dPdx, gvec dPdy, ivec offset); "textureGradOffset" => (false, true, TextureLevelType::Grad), // textureLod(gsampler, gvec P, float lod); "textureLod" => (false, false, TextureLevelType::Lod), // textureLodOffset(gsampler, gvec P, float lod, ivec offset); "textureLodOffset" => (false, true, TextureLevelType::Lod), // textureOffset(gsampler, gvec+1 P, ivec offset, [float bias]); "textureOffset" => (false, true, TextureLevelType::None), // textureProj(gsampler, gvec+1 P, [float bias]); "textureProj" => (true, false, TextureLevelType::None), // textureProjGrad(gsampler, gvec+1 P, gvec dPdx, gvec dPdy); "textureProjGrad" => (true, false, TextureLevelType::Grad), // textureProjGradOffset(gsampler, gvec+1 P, gvec dPdx, gvec dPdy, ivec offset); "textureProjGradOffset" => (true, true, TextureLevelType::Grad), // textureProjLod(gsampler, gvec+1 P, float lod); "textureProjLod" => (true, false, TextureLevelType::Lod), // textureProjLodOffset(gsampler, gvec+1 P, gvec dPdx, gvec dPdy, ivec offset); "textureProjLodOffset" => (true, true, TextureLevelType::Lod), // textureProjOffset(gsampler, gvec+1 P, ivec offset, [float bias]); "textureProjOffset" => (true, true, TextureLevelType::None), _ => unreachable!(), }; let builtin = MacroCall::Texture { proj, offset, shadow, level_type, }; // Parse out the variant settings. let grad = level_type == TextureLevelType::Grad; let lod = level_type == TextureLevelType::Lod; let supports_variant = proj && !shadow; if variant && !supports_variant { continue; } if bias && !matches!(level_type, TextureLevelType::None) { continue; } // Proj doesn't work with arrayed or Cube if proj && (arrayed || dim == Dim::Cube) { continue; } // texture operations with offset are not supported for cube maps if dim == Dim::Cube && offset { continue; } // sampler2DArrayShadow can't be used in textureLod or in texture with bias if (lod || bias) && arrayed && shadow && dim == Dim::D2 { continue; } // TODO: glsl supports using bias with depth samplers but naga doesn't if bias && shadow { continue; } let class = match shadow { true => ImageClass::Depth { multi }, false => ImageClass::Sampled { kind, multi }, }; let image = TypeInner::Image { dim, arrayed, class, }; let num_coords_from_dim = image_dims_to_coords_size(dim).min(3); let mut num_coords = num_coords_from_dim; if shadow && proj { num_coords = 4; } else if dim == Dim::D1 && shadow { num_coords = 3; } else if shadow { num_coords += 1; } else if proj { if variant && num_coords == 4 { // Normal form already has 4 components, no need to have a variant form. continue; } else if variant { num_coords = 4; } else { num_coords += 1; } } if !(dim == Dim::D1 && shadow) { num_coords += arrayed as usize; } // Special case: texture(gsamplerCubeArrayShadow) kicks the shadow compare ref to a separate argument, // since it would otherwise take five arguments. It also can't take a bias, nor can it be proj/grad/lod/offset // (presumably because nobody asked for it, and implementation complexity?) if num_coords >= 5 { if lod || grad || offset || proj || bias { continue; } debug_assert!(dim == Dim::Cube && shadow && arrayed); } debug_assert!(num_coords <= 5); let vector = make_coords_arg(num_coords, Sk::Float); let mut args = vec![image, vector]; if num_coords == 5 { args.push(TypeInner::Scalar(Scalar::F32)); } match level_type { TextureLevelType::Lod => { args.push(TypeInner::Scalar(Scalar::F32)); } TextureLevelType::Grad => { args.push(make_coords_arg(num_coords_from_dim, Sk::Float)); args.push(make_coords_arg(num_coords_from_dim, Sk::Float)); } TextureLevelType::None => {} }; if offset { args.push(make_coords_arg(num_coords_from_dim, Sk::Sint)); } if bias { args.push(TypeInner::Scalar(Scalar::F32)); } declaration .overloads .push(module.add_builtin(args, builtin)); } }; texture_args_generator(TextureArgsOptions::SHADOW | variations.into(), f) } "textureSize" => { let f = |kind, dim, arrayed, multi, shadow| { let class = match shadow { true => ImageClass::Depth { multi }, false => ImageClass::Sampled { kind, multi }, }; let image = TypeInner::Image { dim, arrayed, class, }; let mut args = vec![image]; if !multi { args.push(TypeInner::Scalar(Scalar::I32)) } declaration .overloads .push(module.add_builtin(args, MacroCall::TextureSize { arrayed })) }; texture_args_generator( TextureArgsOptions::SHADOW | TextureArgsOptions::MULTI | variations.into(), f, ) } "textureQueryLevels" => { let f = |kind, dim, arrayed, multi, shadow| { let class = match shadow { true => ImageClass::Depth { multi }, false => ImageClass::Sampled { kind, multi }, }; let image = TypeInner::Image { dim, arrayed, class, }; declaration .overloads .push(module.add_builtin(vec![image], MacroCall::TextureQueryLevels)) }; texture_args_generator(TextureArgsOptions::SHADOW | variations.into(), f) } "texelFetch" | "texelFetchOffset" => { let offset = "texelFetchOffset" == name; let f = |kind, dim, arrayed, multi, _shadow| { // Cube images aren't supported if let Dim::Cube = dim { return; } let image = TypeInner::Image { dim, arrayed, class: ImageClass::Sampled { kind, multi }, }; let dim_value = image_dims_to_coords_size(dim); let coordinates = make_coords_arg(dim_value + arrayed as usize, Sk::Sint); let mut args = vec![image, coordinates, TypeInner::Scalar(Scalar::I32)]; if offset { args.push(make_coords_arg(dim_value, Sk::Sint)); } declaration .overloads .push(module.add_builtin(args, MacroCall::ImageLoad { multi })) }; // Don't generate shadow images since they aren't supported texture_args_generator(TextureArgsOptions::MULTI | variations.into(), f) } "imageSize" => { let f = |kind: Sk, dim, arrayed, _, _| { // Naga doesn't support cube images and it's usefulness // is questionable, so they won't be supported for now if dim == Dim::Cube { return; } let image = TypeInner::Image { dim, arrayed, class: ImageClass::Storage { format: kind.dummy_storage_format(), access: crate::StorageAccess::empty(), }, }; declaration .overloads .push(module.add_builtin(vec![image], MacroCall::TextureSize { arrayed })) }; texture_args_generator(variations.into(), f) } "imageLoad" => { let f = |kind: Sk, dim, arrayed, _, _| { // Naga doesn't support cube images and it's usefulness // is questionable, so they won't be supported for now if dim == Dim::Cube { return; } let image = TypeInner::Image { dim, arrayed, class: ImageClass::Storage { format: kind.dummy_storage_format(), access: crate::StorageAccess::LOAD, }, }; let dim_value = image_dims_to_coords_size(dim); let mut coord_size = dim_value + arrayed as usize; // > Every OpenGL API call that operates on cubemap array // > textures takes layer-faces, not array layers // // So this means that imageCubeArray only takes a three component // vector coordinate and the third component is a layer index. if Dim::Cube == dim && arrayed { coord_size = 3 } let coordinates = make_coords_arg(coord_size, Sk::Sint); let args = vec![image, coordinates]; declaration .overloads .push(module.add_builtin(args, MacroCall::ImageLoad { multi: false })) }; // Don't generate shadow nor multisampled images since they aren't supported texture_args_generator(variations.into(), f) } "imageStore" => { let f = |kind: Sk, dim, arrayed, _, _| { // Naga doesn't support cube images and it's usefulness // is questionable, so they won't be supported for now if dim == Dim::Cube { return; } let image = TypeInner::Image { dim, arrayed, class: ImageClass::Storage { format: kind.dummy_storage_format(), access: crate::StorageAccess::STORE, }, }; let dim_value = image_dims_to_coords_size(dim); let mut coord_size = dim_value + arrayed as usize; // > Every OpenGL API call that operates on cubemap array // > textures takes layer-faces, not array layers // // So this means that imageCubeArray only takes a three component // vector coordinate and the third component is a layer index. if Dim::Cube == dim && arrayed { coord_size = 3 } let coordinates = make_coords_arg(coord_size, Sk::Sint); let args = vec![ image, coordinates, TypeInner::Vector { size: VectorSize::Quad, scalar: Scalar { kind, width: 4 }, }, ]; let mut overload = module.add_builtin(args, MacroCall::ImageStore); overload.void = true; declaration.overloads.push(overload) }; // Don't generate shadow nor multisampled images since they aren't supported texture_args_generator(variations.into(), f) } _ => {} } } /// Injects the builtins into declaration that don't need any special variations fn inject_standard_builtins( declaration: &mut FunctionDeclaration, module: &mut Module, name: &str, ) { // Some samplers (sampler1D, etc...) can be float, int, or uint let anykind_sampler = if name.starts_with("sampler") { Some((name, Sk::Float)) } else if name.starts_with("usampler") { Some((&name[1..], Sk::Uint)) } else if name.starts_with("isampler") { Some((&name[1..], Sk::Sint)) } else { None }; if let Some((sampler, kind)) = anykind_sampler { match sampler { "sampler1D" | "sampler1DArray" | "sampler2D" | "sampler2DArray" | "sampler2DMS" | "sampler2DMSArray" | "sampler3D" | "samplerCube" | "samplerCubeArray" => { declaration.overloads.push(module.add_builtin( vec![ TypeInner::Image { dim: match sampler { "sampler1D" | "sampler1DArray" => Dim::D1, "sampler2D" | "sampler2DArray" | "sampler2DMS" | "sampler2DMSArray" => Dim::D2, "sampler3D" => Dim::D3, _ => Dim::Cube, }, arrayed: matches!( sampler, "sampler1DArray" | "sampler2DArray" | "sampler2DMSArray" | "samplerCubeArray" ), class: ImageClass::Sampled { kind, multi: matches!(sampler, "sampler2DMS" | "sampler2DMSArray"), }, }, TypeInner::Sampler { comparison: false }, ], MacroCall::Sampler, )); return; } _ => (), } } match name { // Shadow sampler can only be of kind `Sk::Float` "sampler1DShadow" | "sampler1DArrayShadow" | "sampler2DShadow" | "sampler2DArrayShadow" | "samplerCubeShadow" | "samplerCubeArrayShadow" => { let dim = match name { "sampler1DShadow" | "sampler1DArrayShadow" => Dim::D1, "sampler2DShadow" | "sampler2DArrayShadow" => Dim::D2, _ => Dim::Cube, }; let arrayed = matches!( name, "sampler1DArrayShadow" | "sampler2DArrayShadow" | "samplerCubeArrayShadow" ); for i in 0..2 { let ty = TypeInner::Image { dim, arrayed, class: match i { 0 => ImageClass::Sampled { kind: Sk::Float, multi: false, }, _ => ImageClass::Depth { multi: false }, }, }; declaration.overloads.push(module.add_builtin( vec![ty, TypeInner::Sampler { comparison: true }], MacroCall::SamplerShadow, )) } } "sin" | "exp" | "exp2" | "sinh" | "cos" | "cosh" | "tan" | "tanh" | "acos" | "asin" | "log" | "log2" | "radians" | "degrees" | "asinh" | "acosh" | "atanh" | "floatBitsToInt" | "floatBitsToUint" | "dFdx" | "dFdxFine" | "dFdxCoarse" | "dFdy" | "dFdyFine" | "dFdyCoarse" | "fwidth" | "fwidthFine" | "fwidthCoarse" => { // bits layout // bit 0 through 1 - dims for bits in 0..0b100 { let size = match bits { 0b00 => None, 0b01 => Some(VectorSize::Bi), 0b10 => Some(VectorSize::Tri), _ => Some(VectorSize::Quad), }; let scalar = Scalar::F32; declaration.overloads.push(module.add_builtin( vec![match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }], match name { "sin" => MacroCall::MathFunction(MathFunction::Sin), "exp" => MacroCall::MathFunction(MathFunction::Exp), "exp2" => MacroCall::MathFunction(MathFunction::Exp2), "sinh" => MacroCall::MathFunction(MathFunction::Sinh), "cos" => MacroCall::MathFunction(MathFunction::Cos), "cosh" => MacroCall::MathFunction(MathFunction::Cosh), "tan" => MacroCall::MathFunction(MathFunction::Tan), "tanh" => MacroCall::MathFunction(MathFunction::Tanh), "acos" => MacroCall::MathFunction(MathFunction::Acos), "asin" => MacroCall::MathFunction(MathFunction::Asin), "log" => MacroCall::MathFunction(MathFunction::Log), "log2" => MacroCall::MathFunction(MathFunction::Log2), "asinh" => MacroCall::MathFunction(MathFunction::Asinh), "acosh" => MacroCall::MathFunction(MathFunction::Acosh), "atanh" => MacroCall::MathFunction(MathFunction::Atanh), "radians" => MacroCall::MathFunction(MathFunction::Radians), "degrees" => MacroCall::MathFunction(MathFunction::Degrees), "floatBitsToInt" => MacroCall::BitCast(Sk::Sint), "floatBitsToUint" => MacroCall::BitCast(Sk::Uint), "dFdxCoarse" => MacroCall::Derivate(Axis::X, Ctrl::Coarse), "dFdyCoarse" => MacroCall::Derivate(Axis::Y, Ctrl::Coarse), "fwidthCoarse" => MacroCall::Derivate(Axis::Width, Ctrl::Coarse), "dFdxFine" => MacroCall::Derivate(Axis::X, Ctrl::Fine), "dFdyFine" => MacroCall::Derivate(Axis::Y, Ctrl::Fine), "fwidthFine" => MacroCall::Derivate(Axis::Width, Ctrl::Fine), "dFdx" => MacroCall::Derivate(Axis::X, Ctrl::None), "dFdy" => MacroCall::Derivate(Axis::Y, Ctrl::None), "fwidth" => MacroCall::Derivate(Axis::Width, Ctrl::None), _ => unreachable!(), }, )) } } "intBitsToFloat" | "uintBitsToFloat" => { // bits layout // bit 0 through 1 - dims for bits in 0..0b100 { let size = match bits { 0b00 => None, 0b01 => Some(VectorSize::Bi), 0b10 => Some(VectorSize::Tri), _ => Some(VectorSize::Quad), }; let scalar = match name { "intBitsToFloat" => Scalar::I32, _ => Scalar::U32, }; declaration.overloads.push(module.add_builtin( vec![match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }], MacroCall::BitCast(Sk::Float), )) } } "pow" => { // bits layout // bit 0 through 1 - dims for bits in 0..0b100 { let size = match bits { 0b00 => None, 0b01 => Some(VectorSize::Bi), 0b10 => Some(VectorSize::Tri), _ => Some(VectorSize::Quad), }; let scalar = Scalar::F32; let ty = || match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }; declaration.overloads.push( module .add_builtin(vec![ty(), ty()], MacroCall::MathFunction(MathFunction::Pow)), ) } } "abs" | "sign" => { // bits layout // bit 0 through 1 - dims // bit 2 - float/sint for bits in 0..0b1000 { let size = match bits & 0b11 { 0b00 => None, 0b01 => Some(VectorSize::Bi), 0b10 => Some(VectorSize::Tri), _ => Some(VectorSize::Quad), }; let scalar = match bits >> 2 { 0b0 => Scalar::F32, _ => Scalar::I32, }; let args = vec![match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }]; declaration.overloads.push(module.add_builtin( args, MacroCall::MathFunction(match name { "abs" => MathFunction::Abs, "sign" => MathFunction::Sign, _ => unreachable!(), }), )) } } "bitCount" | "bitfieldReverse" | "bitfieldExtract" | "bitfieldInsert" | "findLSB" | "findMSB" => { let fun = match name { "bitCount" => MathFunction::CountOneBits, "bitfieldReverse" => MathFunction::ReverseBits, "bitfieldExtract" => MathFunction::ExtractBits, "bitfieldInsert" => MathFunction::InsertBits, "findLSB" => MathFunction::FirstTrailingBit, "findMSB" => MathFunction::FirstLeadingBit, _ => unreachable!(), }; let mc = match fun { MathFunction::ExtractBits => MacroCall::BitfieldExtract, MathFunction::InsertBits => MacroCall::BitfieldInsert, _ => MacroCall::MathFunction(fun), }; // bits layout // bit 0 - int/uint // bit 1 through 2 - dims for bits in 0..0b1000 { let scalar = match bits & 0b1 { 0b0 => Scalar::I32, _ => Scalar::U32, }; let size = match bits >> 1 { 0b00 => None, 0b01 => Some(VectorSize::Bi), 0b10 => Some(VectorSize::Tri), _ => Some(VectorSize::Quad), }; let ty = || match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }; let mut args = vec![ty()]; match fun { MathFunction::ExtractBits => { args.push(TypeInner::Scalar(Scalar::I32)); args.push(TypeInner::Scalar(Scalar::I32)); } MathFunction::InsertBits => { args.push(ty()); args.push(TypeInner::Scalar(Scalar::I32)); args.push(TypeInner::Scalar(Scalar::I32)); } _ => {} } // we need to cast the return type of findLsb / findMsb let mc = if scalar.kind == Sk::Uint { match mc { MacroCall::MathFunction(MathFunction::FirstTrailingBit) => { MacroCall::FindLsbUint } MacroCall::MathFunction(MathFunction::FirstLeadingBit) => { MacroCall::FindMsbUint } mc => mc, } } else { mc }; declaration.overloads.push(module.add_builtin(args, mc)) } } "packSnorm4x8" | "packUnorm4x8" | "packSnorm2x16" | "packUnorm2x16" | "packHalf2x16" => { let fun = match name { "packSnorm4x8" => MathFunction::Pack4x8snorm, "packUnorm4x8" => MathFunction::Pack4x8unorm, "packSnorm2x16" => MathFunction::Pack2x16unorm, "packUnorm2x16" => MathFunction::Pack2x16snorm, "packHalf2x16" => MathFunction::Pack2x16float, _ => unreachable!(), }; let ty = match fun { MathFunction::Pack4x8snorm | MathFunction::Pack4x8unorm => TypeInner::Vector { size: VectorSize::Quad, scalar: Scalar::F32, }, MathFunction::Pack2x16unorm | MathFunction::Pack2x16snorm | MathFunction::Pack2x16float => TypeInner::Vector { size: VectorSize::Bi, scalar: Scalar::F32, }, _ => unreachable!(), }; let args = vec![ty]; declaration .overloads .push(module.add_builtin(args, MacroCall::MathFunction(fun))); } "unpackSnorm4x8" | "unpackUnorm4x8" | "unpackSnorm2x16" | "unpackUnorm2x16" | "unpackHalf2x16" => { let fun = match name { "unpackSnorm4x8" => MathFunction::Unpack4x8snorm, "unpackUnorm4x8" => MathFunction::Unpack4x8unorm, "unpackSnorm2x16" => MathFunction::Unpack2x16snorm, "unpackUnorm2x16" => MathFunction::Unpack2x16unorm, "unpackHalf2x16" => MathFunction::Unpack2x16float, _ => unreachable!(), }; let args = vec![TypeInner::Scalar(Scalar::U32)]; declaration .overloads .push(module.add_builtin(args, MacroCall::MathFunction(fun))); } "atan" => { // bits layout // bit 0 - atan/atan2 // bit 1 through 2 - dims for bits in 0..0b1000 { let fun = match bits & 0b1 { 0b0 => MathFunction::Atan, _ => MathFunction::Atan2, }; let size = match bits >> 1 { 0b00 => None, 0b01 => Some(VectorSize::Bi), 0b10 => Some(VectorSize::Tri), _ => Some(VectorSize::Quad), }; let scalar = Scalar::F32; let ty = || match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }; let mut args = vec![ty()]; if fun == MathFunction::Atan2 { args.push(ty()) } declaration .overloads .push(module.add_builtin(args, MacroCall::MathFunction(fun))) } } "all" | "any" | "not" => { // bits layout // bit 0 through 1 - dims for bits in 0..0b11 { let size = match bits { 0b00 => VectorSize::Bi, 0b01 => VectorSize::Tri, _ => VectorSize::Quad, }; let args = vec![TypeInner::Vector { size, scalar: Scalar::BOOL, }]; let fun = match name { "all" => MacroCall::Relational(RelationalFunction::All), "any" => MacroCall::Relational(RelationalFunction::Any), "not" => MacroCall::Unary(UnaryOperator::LogicalNot), _ => unreachable!(), }; declaration.overloads.push(module.add_builtin(args, fun)) } } "lessThan" | "greaterThan" | "lessThanEqual" | "greaterThanEqual" => { for bits in 0..0b1001 { let (size, scalar) = match bits { 0b0000 => (VectorSize::Bi, Scalar::F32), 0b0001 => (VectorSize::Tri, Scalar::F32), 0b0010 => (VectorSize::Quad, Scalar::F32), 0b0011 => (VectorSize::Bi, Scalar::I32), 0b0100 => (VectorSize::Tri, Scalar::I32), 0b0101 => (VectorSize::Quad, Scalar::I32), 0b0110 => (VectorSize::Bi, Scalar::U32), 0b0111 => (VectorSize::Tri, Scalar::U32), _ => (VectorSize::Quad, Scalar::U32), }; let ty = || TypeInner::Vector { size, scalar }; let args = vec![ty(), ty()]; let fun = MacroCall::Binary(match name { "lessThan" => BinaryOperator::Less, "greaterThan" => BinaryOperator::Greater, "lessThanEqual" => BinaryOperator::LessEqual, "greaterThanEqual" => BinaryOperator::GreaterEqual, _ => unreachable!(), }); declaration.overloads.push(module.add_builtin(args, fun)) } } "equal" | "notEqual" => { for bits in 0..0b1100 { let (size, scalar) = match bits { 0b0000 => (VectorSize::Bi, Scalar::F32), 0b0001 => (VectorSize::Tri, Scalar::F32), 0b0010 => (VectorSize::Quad, Scalar::F32), 0b0011 => (VectorSize::Bi, Scalar::I32), 0b0100 => (VectorSize::Tri, Scalar::I32), 0b0101 => (VectorSize::Quad, Scalar::I32), 0b0110 => (VectorSize::Bi, Scalar::U32), 0b0111 => (VectorSize::Tri, Scalar::U32), 0b1000 => (VectorSize::Quad, Scalar::U32), 0b1001 => (VectorSize::Bi, Scalar::BOOL), 0b1010 => (VectorSize::Tri, Scalar::BOOL), _ => (VectorSize::Quad, Scalar::BOOL), }; let ty = || TypeInner::Vector { size, scalar }; let args = vec![ty(), ty()]; let fun = MacroCall::Binary(match name { "equal" => BinaryOperator::Equal, "notEqual" => BinaryOperator::NotEqual, _ => unreachable!(), }); declaration.overloads.push(module.add_builtin(args, fun)) } } "min" | "max" => { // bits layout // bit 0 through 1 - scalar kind // bit 2 through 4 - dims for bits in 0..0b11100 { let scalar = match bits & 0b11 { 0b00 => Scalar::F32, 0b01 => Scalar::I32, 0b10 => Scalar::U32, _ => continue, }; let (size, second_size) = match bits >> 2 { 0b000 => (None, None), 0b001 => (Some(VectorSize::Bi), None), 0b010 => (Some(VectorSize::Tri), None), 0b011 => (Some(VectorSize::Quad), None), 0b100 => (Some(VectorSize::Bi), Some(VectorSize::Bi)), 0b101 => (Some(VectorSize::Tri), Some(VectorSize::Tri)), _ => (Some(VectorSize::Quad), Some(VectorSize::Quad)), }; let args = vec![ match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }, match second_size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }, ]; let fun = match name { "max" => MacroCall::Splatted(MathFunction::Max, size, 1), "min" => MacroCall::Splatted(MathFunction::Min, size, 1), _ => unreachable!(), }; declaration.overloads.push(module.add_builtin(args, fun)) } } "mix" => { // bits layout // bit 0 through 1 - dims // bit 2 through 4 - types // // 0b10011 is the last element since splatted single elements // were already added for bits in 0..0b10011 { let size = match bits & 0b11 { 0b00 => Some(VectorSize::Bi), 0b01 => Some(VectorSize::Tri), 0b10 => Some(VectorSize::Quad), _ => None, }; let (scalar, splatted, boolean) = match bits >> 2 { 0b000 => (Scalar::I32, false, true), 0b001 => (Scalar::U32, false, true), 0b010 => (Scalar::F32, false, true), 0b011 => (Scalar::F32, false, false), _ => (Scalar::F32, true, false), }; let ty = |scalar| match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }; let args = vec![ ty(scalar), ty(scalar), match (boolean, splatted) { (true, _) => ty(Scalar::BOOL), (_, false) => TypeInner::Scalar(scalar), _ => ty(scalar), }, ]; declaration.overloads.push(module.add_builtin( args, match boolean { true => MacroCall::MixBoolean, false => MacroCall::Splatted(MathFunction::Mix, size, 2), }, )) } } "clamp" => { // bits layout // bit 0 through 1 - float/int/uint // bit 2 through 3 - dims // bit 4 - splatted // // 0b11010 is the last element since splatted single elements // were already added for bits in 0..0b11011 { let scalar = match bits & 0b11 { 0b00 => Scalar::F32, 0b01 => Scalar::I32, 0b10 => Scalar::U32, _ => continue, }; let size = match (bits >> 2) & 0b11 { 0b00 => Some(VectorSize::Bi), 0b01 => Some(VectorSize::Tri), 0b10 => Some(VectorSize::Quad), _ => None, }; let splatted = bits & 0b10000 == 0b10000; let base_ty = || match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }; let limit_ty = || match splatted { true => TypeInner::Scalar(scalar), false => base_ty(), }; let args = vec![base_ty(), limit_ty(), limit_ty()]; declaration .overloads .push(module.add_builtin(args, MacroCall::Clamp(size))) } } "barrier" => declaration .overloads .push(module.add_builtin(Vec::new(), MacroCall::Barrier)), // Add common builtins with floats _ => inject_common_builtin(declaration, module, name, 4), } } /// Injects the builtins into declaration that need doubles fn inject_double_builtin(declaration: &mut FunctionDeclaration, module: &mut Module, name: &str) { match name { "abs" | "sign" => { // bits layout // bit 0 through 1 - dims for bits in 0..0b100 { let size = match bits { 0b00 => None, 0b01 => Some(VectorSize::Bi), 0b10 => Some(VectorSize::Tri), _ => Some(VectorSize::Quad), }; let scalar = Scalar::F64; let args = vec![match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }]; declaration.overloads.push(module.add_builtin( args, MacroCall::MathFunction(match name { "abs" => MathFunction::Abs, "sign" => MathFunction::Sign, _ => unreachable!(), }), )) } } "min" | "max" => { // bits layout // bit 0 through 2 - dims for bits in 0..0b111 { let (size, second_size) = match bits { 0b000 => (None, None), 0b001 => (Some(VectorSize::Bi), None), 0b010 => (Some(VectorSize::Tri), None), 0b011 => (Some(VectorSize::Quad), None), 0b100 => (Some(VectorSize::Bi), Some(VectorSize::Bi)), 0b101 => (Some(VectorSize::Tri), Some(VectorSize::Tri)), _ => (Some(VectorSize::Quad), Some(VectorSize::Quad)), }; let scalar = Scalar::F64; let args = vec![ match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }, match second_size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }, ]; let fun = match name { "max" => MacroCall::Splatted(MathFunction::Max, size, 1), "min" => MacroCall::Splatted(MathFunction::Min, size, 1), _ => unreachable!(), }; declaration.overloads.push(module.add_builtin(args, fun)) } } "mix" => { // bits layout // bit 0 through 1 - dims // bit 2 through 3 - splatted/boolean // // 0b1010 is the last element since splatted with single elements // is equal to normal single elements for bits in 0..0b1011 { let size = match bits & 0b11 { 0b00 => Some(VectorSize::Quad), 0b01 => Some(VectorSize::Bi), 0b10 => Some(VectorSize::Tri), _ => None, }; let scalar = Scalar::F64; let (splatted, boolean) = match bits >> 2 { 0b00 => (false, false), 0b01 => (false, true), _ => (true, false), }; let ty = |scalar| match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }; let args = vec![ ty(scalar), ty(scalar), match (boolean, splatted) { (true, _) => ty(Scalar::BOOL), (_, false) => TypeInner::Scalar(scalar), _ => ty(scalar), }, ]; declaration.overloads.push(module.add_builtin( args, match boolean { true => MacroCall::MixBoolean, false => MacroCall::Splatted(MathFunction::Mix, size, 2), }, )) } } "clamp" => { // bits layout // bit 0 through 1 - dims // bit 2 - splatted // // 0b110 is the last element since splatted with single elements // is equal to normal single elements for bits in 0..0b111 { let scalar = Scalar::F64; let size = match bits & 0b11 { 0b00 => Some(VectorSize::Bi), 0b01 => Some(VectorSize::Tri), 0b10 => Some(VectorSize::Quad), _ => None, }; let splatted = bits & 0b100 == 0b100; let base_ty = || match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }; let limit_ty = || match splatted { true => TypeInner::Scalar(scalar), false => base_ty(), }; let args = vec![base_ty(), limit_ty(), limit_ty()]; declaration .overloads .push(module.add_builtin(args, MacroCall::Clamp(size))) } } "lessThan" | "greaterThan" | "lessThanEqual" | "greaterThanEqual" | "equal" | "notEqual" => { let scalar = Scalar::F64; for bits in 0..0b11 { let size = match bits { 0b00 => VectorSize::Bi, 0b01 => VectorSize::Tri, _ => VectorSize::Quad, }; let ty = || TypeInner::Vector { size, scalar }; let args = vec![ty(), ty()]; let fun = MacroCall::Binary(match name { "lessThan" => BinaryOperator::Less, "greaterThan" => BinaryOperator::Greater, "lessThanEqual" => BinaryOperator::LessEqual, "greaterThanEqual" => BinaryOperator::GreaterEqual, "equal" => BinaryOperator::Equal, "notEqual" => BinaryOperator::NotEqual, _ => unreachable!(), }); declaration.overloads.push(module.add_builtin(args, fun)) } } // Add common builtins with doubles _ => inject_common_builtin(declaration, module, name, 8), } } /// Injects the builtins into declaration that can used either float or doubles fn inject_common_builtin( declaration: &mut FunctionDeclaration, module: &mut Module, name: &str, float_width: crate::Bytes, ) { let float_scalar = Scalar { kind: Sk::Float, width: float_width, }; match name { "ceil" | "round" | "roundEven" | "floor" | "fract" | "trunc" | "sqrt" | "inversesqrt" | "normalize" | "length" | "isinf" | "isnan" => { // bits layout // bit 0 through 1 - dims for bits in 0..0b100 { let size = match bits { 0b00 => None, 0b01 => Some(VectorSize::Bi), 0b10 => Some(VectorSize::Tri), _ => Some(VectorSize::Quad), }; let args = vec![match size { Some(size) => TypeInner::Vector { size, scalar: float_scalar, }, None => TypeInner::Scalar(float_scalar), }]; let fun = match name { "ceil" => MacroCall::MathFunction(MathFunction::Ceil), "round" | "roundEven" => MacroCall::MathFunction(MathFunction::Round), "floor" => MacroCall::MathFunction(MathFunction::Floor), "fract" => MacroCall::MathFunction(MathFunction::Fract), "trunc" => MacroCall::MathFunction(MathFunction::Trunc), "sqrt" => MacroCall::MathFunction(MathFunction::Sqrt), "inversesqrt" => MacroCall::MathFunction(MathFunction::InverseSqrt), "normalize" => MacroCall::MathFunction(MathFunction::Normalize), "length" => MacroCall::MathFunction(MathFunction::Length), "isinf" => MacroCall::Relational(RelationalFunction::IsInf), "isnan" => MacroCall::Relational(RelationalFunction::IsNan), _ => unreachable!(), }; declaration.overloads.push(module.add_builtin(args, fun)) } } "dot" | "reflect" | "distance" | "ldexp" => { // bits layout // bit 0 through 1 - dims for bits in 0..0b100 { let size = match bits { 0b00 => None, 0b01 => Some(VectorSize::Bi), 0b10 => Some(VectorSize::Tri), _ => Some(VectorSize::Quad), }; let ty = |scalar| match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }; let fun = match name { "dot" => MacroCall::MathFunction(MathFunction::Dot), "reflect" => MacroCall::MathFunction(MathFunction::Reflect), "distance" => MacroCall::MathFunction(MathFunction::Distance), "ldexp" => MacroCall::MathFunction(MathFunction::Ldexp), _ => unreachable!(), }; let second_scalar = match fun { MacroCall::MathFunction(MathFunction::Ldexp) => Scalar::I32, _ => float_scalar, }; declaration .overloads .push(module.add_builtin(vec![ty(float_scalar), ty(second_scalar)], fun)) } } "transpose" => { // bits layout // bit 0 through 3 - dims for bits in 0..0b1001 { let (rows, columns) = match bits { 0b0000 => (VectorSize::Bi, VectorSize::Bi), 0b0001 => (VectorSize::Bi, VectorSize::Tri), 0b0010 => (VectorSize::Bi, VectorSize::Quad), 0b0011 => (VectorSize::Tri, VectorSize::Bi), 0b0100 => (VectorSize::Tri, VectorSize::Tri), 0b0101 => (VectorSize::Tri, VectorSize::Quad), 0b0110 => (VectorSize::Quad, VectorSize::Bi), 0b0111 => (VectorSize::Quad, VectorSize::Tri), _ => (VectorSize::Quad, VectorSize::Quad), }; declaration.overloads.push(module.add_builtin( vec![TypeInner::Matrix { columns, rows, scalar: float_scalar, }], MacroCall::MathFunction(MathFunction::Transpose), )) } } "inverse" | "determinant" => { // bits layout // bit 0 through 1 - dims for bits in 0..0b11 { let (rows, columns) = match bits { 0b00 => (VectorSize::Bi, VectorSize::Bi), 0b01 => (VectorSize::Tri, VectorSize::Tri), _ => (VectorSize::Quad, VectorSize::Quad), }; let args = vec![TypeInner::Matrix { columns, rows, scalar: float_scalar, }]; declaration.overloads.push(module.add_builtin( args, MacroCall::MathFunction(match name { "inverse" => MathFunction::Inverse, "determinant" => MathFunction::Determinant, _ => unreachable!(), }), )) } } "mod" | "step" => { // bits layout // bit 0 through 2 - dims for bits in 0..0b111 { let (size, second_size) = match bits { 0b000 => (None, None), 0b001 => (Some(VectorSize::Bi), None), 0b010 => (Some(VectorSize::Tri), None), 0b011 => (Some(VectorSize::Quad), None), 0b100 => (Some(VectorSize::Bi), Some(VectorSize::Bi)), 0b101 => (Some(VectorSize::Tri), Some(VectorSize::Tri)), _ => (Some(VectorSize::Quad), Some(VectorSize::Quad)), }; let mut args = Vec::with_capacity(2); let step = name == "step"; for i in 0..2 { let maybe_size = match i == step as u32 { true => size, false => second_size, }; args.push(match maybe_size { Some(size) => TypeInner::Vector { size, scalar: float_scalar, }, None => TypeInner::Scalar(float_scalar), }) } let fun = match name { "mod" => MacroCall::Mod(size), "step" => MacroCall::Splatted(MathFunction::Step, size, 0), _ => unreachable!(), }; declaration.overloads.push(module.add_builtin(args, fun)) } } // TODO: https://github.com/gfx-rs/naga/issues/2526 // "modf" | "frexp" => { ... } "cross" => { let args = vec![ TypeInner::Vector { size: VectorSize::Tri, scalar: float_scalar, }, TypeInner::Vector { size: VectorSize::Tri, scalar: float_scalar, }, ]; declaration .overloads .push(module.add_builtin(args, MacroCall::MathFunction(MathFunction::Cross))) } "outerProduct" => { // bits layout // bit 0 through 3 - dims for bits in 0..0b1001 { let (size1, size2) = match bits { 0b0000 => (VectorSize::Bi, VectorSize::Bi), 0b0001 => (VectorSize::Bi, VectorSize::Tri), 0b0010 => (VectorSize::Bi, VectorSize::Quad), 0b0011 => (VectorSize::Tri, VectorSize::Bi), 0b0100 => (VectorSize::Tri, VectorSize::Tri), 0b0101 => (VectorSize::Tri, VectorSize::Quad), 0b0110 => (VectorSize::Quad, VectorSize::Bi), 0b0111 => (VectorSize::Quad, VectorSize::Tri), _ => (VectorSize::Quad, VectorSize::Quad), }; let args = vec![ TypeInner::Vector { size: size1, scalar: float_scalar, }, TypeInner::Vector { size: size2, scalar: float_scalar, }, ]; declaration .overloads .push(module.add_builtin(args, MacroCall::MathFunction(MathFunction::Outer))) } } "faceforward" | "fma" => { // bits layout // bit 0 through 1 - dims for bits in 0..0b100 { let size = match bits { 0b00 => None, 0b01 => Some(VectorSize::Bi), 0b10 => Some(VectorSize::Tri), _ => Some(VectorSize::Quad), }; let ty = || match size { Some(size) => TypeInner::Vector { size, scalar: float_scalar, }, None => TypeInner::Scalar(float_scalar), }; let args = vec![ty(), ty(), ty()]; let fun = match name { "faceforward" => MacroCall::MathFunction(MathFunction::FaceForward), "fma" => MacroCall::MathFunction(MathFunction::Fma), _ => unreachable!(), }; declaration.overloads.push(module.add_builtin(args, fun)) } } "refract" => { // bits layout // bit 0 through 1 - dims for bits in 0..0b100 { let size = match bits { 0b00 => None, 0b01 => Some(VectorSize::Bi), 0b10 => Some(VectorSize::Tri), _ => Some(VectorSize::Quad), }; let ty = || match size { Some(size) => TypeInner::Vector { size, scalar: float_scalar, }, None => TypeInner::Scalar(float_scalar), }; let args = vec![ty(), ty(), TypeInner::Scalar(Scalar::F32)]; declaration .overloads .push(module.add_builtin(args, MacroCall::MathFunction(MathFunction::Refract))) } } "smoothstep" => { // bit 0 - splatted // bit 1 through 2 - dims for bits in 0..0b1000 { let splatted = bits & 0b1 == 0b1; let size = match bits >> 1 { 0b00 => None, 0b01 => Some(VectorSize::Bi), 0b10 => Some(VectorSize::Tri), _ => Some(VectorSize::Quad), }; if splatted && size.is_none() { continue; } let base_ty = || match size { Some(size) => TypeInner::Vector { size, scalar: float_scalar, }, None => TypeInner::Scalar(float_scalar), }; let ty = || match splatted { true => TypeInner::Scalar(float_scalar), false => base_ty(), }; declaration.overloads.push(module.add_builtin( vec![ty(), ty(), base_ty()], MacroCall::SmoothStep { splatted: size }, )) } } // The function isn't a builtin or we don't yet support it _ => {} } } #[derive(Clone, Copy, PartialEq, Debug)] pub enum TextureLevelType { None, Lod, Grad, } /// A compiler defined builtin function #[derive(Clone, Copy, PartialEq, Debug)] pub enum MacroCall { Sampler, SamplerShadow, Texture { proj: bool, offset: bool, shadow: bool, level_type: TextureLevelType, }, TextureSize { arrayed: bool, }, TextureQueryLevels, ImageLoad { multi: bool, }, ImageStore, MathFunction(MathFunction), FindLsbUint, FindMsbUint, BitfieldExtract, BitfieldInsert, Relational(RelationalFunction), Unary(UnaryOperator), Binary(BinaryOperator), Mod(Option), Splatted(MathFunction, Option, usize), MixBoolean, Clamp(Option), BitCast(Sk), Derivate(Axis, Ctrl), Barrier, /// SmoothStep needs a separate variant because it might need it's inputs /// to be splatted depending on the overload SmoothStep { /// The size of the splat operation if some splatted: Option, }, } impl MacroCall { /// Adds the necessary expressions and statements to the passed body and /// finally returns the final expression with the correct result pub fn call( &self, frontend: &mut Frontend, ctx: &mut Context, args: &mut [Handle], meta: Span, ) -> Result>> { Ok(Some(match *self { MacroCall::Sampler => { ctx.samplers.insert(args[0], args[1]); args[0] } MacroCall::SamplerShadow => { sampled_to_depth(ctx, args[0], meta, &mut frontend.errors); ctx.invalidate_expression(args[0], meta)?; ctx.samplers.insert(args[0], args[1]); args[0] } MacroCall::Texture { proj, offset, shadow, level_type, } => { let mut coords = args[1]; if proj { let size = match *ctx.resolve_type(coords, meta)? { TypeInner::Vector { size, .. } => size, _ => unreachable!(), }; let mut right = ctx.add_expression( Expression::AccessIndex { base: coords, index: size as u32 - 1, }, Span::default(), )?; let left = if let VectorSize::Bi = size { ctx.add_expression( Expression::AccessIndex { base: coords, index: 0, }, Span::default(), )? } else { let size = match size { VectorSize::Tri => VectorSize::Bi, _ => VectorSize::Tri, }; right = ctx.add_expression( Expression::Splat { size, value: right }, Span::default(), )?; ctx.vector_resize(size, coords, Span::default())? }; coords = ctx.add_expression( Expression::Binary { op: BinaryOperator::Divide, left, right, }, Span::default(), )?; } let extra = args.get(2).copied(); let comps = frontend.coordinate_components(ctx, args[0], coords, extra, meta)?; let mut num_args = 2; if comps.used_extra { num_args += 1; }; // Parse out explicit texture level. let mut level = match level_type { TextureLevelType::None => SampleLevel::Auto, TextureLevelType::Lod => { num_args += 1; if shadow { log::debug!("Assuming LOD {:?} is zero", args[2],); SampleLevel::Zero } else { SampleLevel::Exact(args[2]) } } TextureLevelType::Grad => { num_args += 2; if shadow { log::debug!( "Assuming gradients {:?} and {:?} are not greater than 1", args[2], args[3], ); SampleLevel::Zero } else { SampleLevel::Gradient { x: args[2], y: args[3], } } } }; let texture_offset = match offset { true => { let offset_arg = args[num_args]; num_args += 1; Some(offset_arg) } false => None, }; // Now go back and look for optional bias arg (if available) if let TextureLevelType::None = level_type { level = args .get(num_args) .copied() .map_or(SampleLevel::Auto, SampleLevel::Bias); } texture_call(ctx, args[0], level, comps, texture_offset, meta)? } MacroCall::TextureSize { arrayed } => { let mut expr = ctx.add_expression( Expression::ImageQuery { image: args[0], query: ImageQuery::Size { level: args.get(1).copied(), }, }, Span::default(), )?; if arrayed { let mut components = Vec::with_capacity(4); let size = match *ctx.resolve_type(expr, meta)? { TypeInner::Vector { size: ori_size, .. } => { for index in 0..(ori_size as u32) { components.push(ctx.add_expression( Expression::AccessIndex { base: expr, index }, Span::default(), )?) } match ori_size { VectorSize::Bi => VectorSize::Tri, _ => VectorSize::Quad, } } _ => { components.push(expr); VectorSize::Bi } }; components.push(ctx.add_expression( Expression::ImageQuery { image: args[0], query: ImageQuery::NumLayers, }, Span::default(), )?); let ty = ctx.module.types.insert( Type { name: None, inner: TypeInner::Vector { size, scalar: Scalar::U32, }, }, Span::default(), ); expr = ctx.add_expression(Expression::Compose { components, ty }, meta)? } ctx.add_expression( Expression::As { expr, kind: Sk::Sint, convert: Some(4), }, Span::default(), )? } MacroCall::TextureQueryLevels => { let expr = ctx.add_expression( Expression::ImageQuery { image: args[0], query: ImageQuery::NumLevels, }, Span::default(), )?; ctx.add_expression( Expression::As { expr, kind: Sk::Sint, convert: Some(4), }, Span::default(), )? } MacroCall::ImageLoad { multi } => { let comps = frontend.coordinate_components(ctx, args[0], args[1], None, meta)?; let (sample, level) = match (multi, args.get(2)) { (_, None) => (None, None), (true, Some(&arg)) => (Some(arg), None), (false, Some(&arg)) => (None, Some(arg)), }; ctx.add_expression( Expression::ImageLoad { image: args[0], coordinate: comps.coordinate, array_index: comps.array_index, sample, level, }, Span::default(), )? } MacroCall::ImageStore => { let comps = frontend.coordinate_components(ctx, args[0], args[1], None, meta)?; ctx.emit_restart(); ctx.body.push( crate::Statement::ImageStore { image: args[0], coordinate: comps.coordinate, array_index: comps.array_index, value: args[2], }, meta, ); return Ok(None); } MacroCall::MathFunction(fun) => ctx.add_expression( Expression::Math { fun, arg: args[0], arg1: args.get(1).copied(), arg2: args.get(2).copied(), arg3: args.get(3).copied(), }, Span::default(), )?, mc @ (MacroCall::FindLsbUint | MacroCall::FindMsbUint) => { let fun = match mc { MacroCall::FindLsbUint => MathFunction::FirstTrailingBit, MacroCall::FindMsbUint => MathFunction::FirstLeadingBit, _ => unreachable!(), }; let res = ctx.add_expression( Expression::Math { fun, arg: args[0], arg1: None, arg2: None, arg3: None, }, Span::default(), )?; ctx.add_expression( Expression::As { expr: res, kind: Sk::Sint, convert: Some(4), }, Span::default(), )? } MacroCall::BitfieldInsert => { let conv_arg_2 = ctx.add_expression( Expression::As { expr: args[2], kind: Sk::Uint, convert: Some(4), }, Span::default(), )?; let conv_arg_3 = ctx.add_expression( Expression::As { expr: args[3], kind: Sk::Uint, convert: Some(4), }, Span::default(), )?; ctx.add_expression( Expression::Math { fun: MathFunction::InsertBits, arg: args[0], arg1: Some(args[1]), arg2: Some(conv_arg_2), arg3: Some(conv_arg_3), }, Span::default(), )? } MacroCall::BitfieldExtract => { let conv_arg_1 = ctx.add_expression( Expression::As { expr: args[1], kind: Sk::Uint, convert: Some(4), }, Span::default(), )?; let conv_arg_2 = ctx.add_expression( Expression::As { expr: args[2], kind: Sk::Uint, convert: Some(4), }, Span::default(), )?; ctx.add_expression( Expression::Math { fun: MathFunction::ExtractBits, arg: args[0], arg1: Some(conv_arg_1), arg2: Some(conv_arg_2), arg3: None, }, Span::default(), )? } MacroCall::Relational(fun) => ctx.add_expression( Expression::Relational { fun, argument: args[0], }, Span::default(), )?, MacroCall::Unary(op) => { ctx.add_expression(Expression::Unary { op, expr: args[0] }, Span::default())? } MacroCall::Binary(op) => ctx.add_expression( Expression::Binary { op, left: args[0], right: args[1], }, Span::default(), )?, MacroCall::Mod(size) => { ctx.implicit_splat(&mut args[1], meta, size)?; // x - y * floor(x / y) let div = ctx.add_expression( Expression::Binary { op: BinaryOperator::Divide, left: args[0], right: args[1], }, Span::default(), )?; let floor = ctx.add_expression( Expression::Math { fun: MathFunction::Floor, arg: div, arg1: None, arg2: None, arg3: None, }, Span::default(), )?; let mult = ctx.add_expression( Expression::Binary { op: BinaryOperator::Multiply, left: floor, right: args[1], }, Span::default(), )?; ctx.add_expression( Expression::Binary { op: BinaryOperator::Subtract, left: args[0], right: mult, }, Span::default(), )? } MacroCall::Splatted(fun, size, i) => { ctx.implicit_splat(&mut args[i], meta, size)?; ctx.add_expression( Expression::Math { fun, arg: args[0], arg1: args.get(1).copied(), arg2: args.get(2).copied(), arg3: args.get(3).copied(), }, Span::default(), )? } MacroCall::MixBoolean => ctx.add_expression( Expression::Select { condition: args[2], accept: args[1], reject: args[0], }, Span::default(), )?, MacroCall::Clamp(size) => { ctx.implicit_splat(&mut args[1], meta, size)?; ctx.implicit_splat(&mut args[2], meta, size)?; ctx.add_expression( Expression::Math { fun: MathFunction::Clamp, arg: args[0], arg1: args.get(1).copied(), arg2: args.get(2).copied(), arg3: args.get(3).copied(), }, Span::default(), )? } MacroCall::BitCast(kind) => ctx.add_expression( Expression::As { expr: args[0], kind, convert: None, }, Span::default(), )?, MacroCall::Derivate(axis, ctrl) => ctx.add_expression( Expression::Derivative { axis, ctrl, expr: args[0], }, Span::default(), )?, MacroCall::Barrier => { ctx.emit_restart(); ctx.body.push( crate::Statement::ControlBarrier(crate::Barrier::all()), meta, ); return Ok(None); } MacroCall::SmoothStep { splatted } => { ctx.implicit_splat(&mut args[0], meta, splatted)?; ctx.implicit_splat(&mut args[1], meta, splatted)?; ctx.add_expression( Expression::Math { fun: MathFunction::SmoothStep, arg: args[0], arg1: args.get(1).copied(), arg2: args.get(2).copied(), arg3: None, }, Span::default(), )? } })) } } fn texture_call( ctx: &mut Context, image: Handle, level: SampleLevel, comps: CoordComponents, offset: Option>, meta: Span, ) -> Result> { if let Some(sampler) = ctx.samplers.get(&image).copied() { let mut array_index = comps.array_index; if let Some(ref mut array_index_expr) = array_index { ctx.conversion(array_index_expr, meta, Scalar::I32)?; } Ok(ctx.add_expression( Expression::ImageSample { image, sampler, gather: None, //TODO coordinate: comps.coordinate, array_index, offset, level, depth_ref: comps.depth_ref, clamp_to_edge: false, }, meta, )?) } else { Err(Error { kind: ErrorKind::SemanticError("Bad call".into()), meta, }) } } /// Helper struct for texture calls with the separate components from the vector argument /// /// Obtained by calling [`coordinate_components`](Frontend::coordinate_components) #[derive(Debug)] struct CoordComponents { coordinate: Handle, depth_ref: Option>, array_index: Option>, used_extra: bool, } impl Frontend { /// Helper function for texture calls, splits the vector argument into it's components fn coordinate_components( &mut self, ctx: &mut Context, image: Handle, coord: Handle, extra: Option>, meta: Span, ) -> Result { if let TypeInner::Image { dim, arrayed, class, } = *ctx.resolve_type(image, meta)? { let image_size = match dim { Dim::D1 => None, Dim::D2 => Some(VectorSize::Bi), Dim::D3 => Some(VectorSize::Tri), Dim::Cube => Some(VectorSize::Tri), }; let coord_size = match *ctx.resolve_type(coord, meta)? { TypeInner::Vector { size, .. } => Some(size), _ => None, }; let (shadow, storage) = match class { ImageClass::Depth { .. } => (true, false), ImageClass::Storage { .. } => (false, true), ImageClass::Sampled { .. } => (false, false), ImageClass::External => unreachable!(), }; let coordinate = match (image_size, coord_size) { (Some(size), Some(coord_s)) if size != coord_s => { ctx.vector_resize(size, coord, Span::default())? } (None, Some(_)) => ctx.add_expression( Expression::AccessIndex { base: coord, index: 0, }, Span::default(), )?, _ => coord, }; let mut coord_index = image_size.map_or(1, |s| s as u32); let array_index = if arrayed && !(storage && dim == Dim::Cube) { let index = coord_index; coord_index += 1; Some(ctx.add_expression( Expression::AccessIndex { base: coord, index }, Span::default(), )?) } else { None }; let mut used_extra = false; let depth_ref = match shadow { true => { let index = coord_index; if index == 4 { used_extra = true; extra } else { Some(ctx.add_expression( Expression::AccessIndex { base: coord, index }, Span::default(), )?) } } false => None, }; Ok(CoordComponents { coordinate, depth_ref, array_index, used_extra, }) } else { self.errors.push(Error { kind: ErrorKind::SemanticError("Type is not an image".into()), meta, }); Ok(CoordComponents { coordinate: coord, depth_ref: None, array_index: None, used_extra: false, }) } } } /// Helper function to cast a expression holding a sampled image to a /// depth image. pub fn sampled_to_depth( ctx: &mut Context, image: Handle, meta: Span, errors: &mut Vec, ) { // Get the a mutable type handle of the underlying image storage let ty = match ctx[image] { Expression::GlobalVariable(handle) => &mut ctx.module.global_variables.get_mut(handle).ty, Expression::FunctionArgument(i) => { // Mark the function argument as carrying a depth texture ctx.parameters_info[i as usize].depth = true; // NOTE: We need to later also change the parameter type &mut ctx.arguments[i as usize].ty } _ => { // Only globals and function arguments are allowed to carry an image return errors.push(Error { kind: ErrorKind::SemanticError("Not a valid texture expression".into()), meta, }); } }; match ctx.module.types[*ty].inner { // Update the image class to depth in case it already isn't TypeInner::Image { class, dim, arrayed, } => match class { ImageClass::Sampled { multi, .. } => { *ty = ctx.module.types.insert( Type { name: None, inner: TypeInner::Image { dim, arrayed, class: ImageClass::Depth { multi }, }, }, Span::default(), ) } ImageClass::Depth { .. } => {} // Other image classes aren't allowed to be transformed to depth ImageClass::Storage { .. } => errors.push(Error { kind: ErrorKind::SemanticError("Not a texture".into()), meta, }), ImageClass::External => unreachable!(), }, _ => errors.push(Error { kind: ErrorKind::SemanticError("Not a texture".into()), meta, }), }; // Copy the handle to allow borrowing the `ctx` again let ty = *ty; // If the image was passed through a function argument we also need to change // the corresponding parameter if let Expression::FunctionArgument(i) = ctx[image] { ctx.parameters[i as usize] = ty; } } bitflags::bitflags! { /// Influences the operation [`texture_args_generator`] struct TextureArgsOptions: u32 { /// Generates multisampled variants of images const MULTI = 1 << 0; /// Generates shadow variants of images const SHADOW = 1 << 1; /// Generates standard images const STANDARD = 1 << 2; /// Generates cube arrayed images const CUBE_ARRAY = 1 << 3; /// Generates cube arrayed images const D2_MULTI_ARRAY = 1 << 4; } } impl From for TextureArgsOptions { fn from(variations: BuiltinVariations) -> Self { let mut options = TextureArgsOptions::empty(); if variations.contains(BuiltinVariations::STANDARD) { options |= TextureArgsOptions::STANDARD } if variations.contains(BuiltinVariations::CUBE_TEXTURES_ARRAY) { options |= TextureArgsOptions::CUBE_ARRAY } if variations.contains(BuiltinVariations::D2_MULTI_TEXTURES_ARRAY) { options |= TextureArgsOptions::D2_MULTI_ARRAY } options } } /// Helper function to generate the image components for texture/image builtins /// /// Calls the passed function `f` with: /// ```text /// f(ScalarKind, ImageDimension, arrayed, multi, shadow) /// ``` /// /// `options` controls extra image variants generation like multisampling and depth, /// see the struct documentation fn texture_args_generator( options: TextureArgsOptions, mut f: impl FnMut(crate::ScalarKind, Dim, bool, bool, bool), ) { for kind in [Sk::Float, Sk::Uint, Sk::Sint].iter().copied() { for dim in [Dim::D1, Dim::D2, Dim::D3, Dim::Cube].iter().copied() { for arrayed in [false, true].iter().copied() { if dim == Dim::Cube && arrayed { if !options.contains(TextureArgsOptions::CUBE_ARRAY) { continue; } } else if Dim::D2 == dim && options.contains(TextureArgsOptions::MULTI) && arrayed && options.contains(TextureArgsOptions::D2_MULTI_ARRAY) { // multisampling for sampler2DMSArray f(kind, dim, arrayed, true, false); } else if !options.contains(TextureArgsOptions::STANDARD) { continue; } f(kind, dim, arrayed, false, false); // 3D images can't be neither arrayed nor shadow // so we break out early, this way arrayed will always // be false and we won't hit the shadow branch if let Dim::D3 = dim { break; } if Dim::D2 == dim && options.contains(TextureArgsOptions::MULTI) && !arrayed { // multisampling f(kind, dim, arrayed, true, false); } if Sk::Float == kind && options.contains(TextureArgsOptions::SHADOW) { // shadow f(kind, dim, arrayed, false, true); } } } } } /// Helper functions used to convert from a image dimension into a integer representing the /// number of components needed for the coordinates vector (1 means scalar instead of vector) const fn image_dims_to_coords_size(dim: Dim) -> usize { match dim { Dim::D1 => 1, Dim::D2 => 2, _ => 3, } } ================================================ FILE: naga/src/front/glsl/context.rs ================================================ use alloc::{format, string::String, vec::Vec}; use core::ops::Index; use super::{ ast::{ GlobalLookup, GlobalLookupKind, HirExpr, HirExprKind, ParameterInfo, ParameterQualifier, VariableReference, }, error::{Error, ErrorKind}, types::{scalar_components, type_power}, Frontend, Result, }; use crate::{ front::Typifier, proc::Emitter, proc::Layouter, AddressSpace, Arena, BinaryOperator, Block, Expression, FastHashMap, FunctionArgument, Handle, Literal, LocalVariable, RelationalFunction, Scalar, Span, Statement, Type, TypeInner, VectorSize, }; /// The position at which an expression is, used while lowering #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum ExprPos { /// The expression is in the left hand side of an assignment Lhs, /// The expression is in the right hand side of an assignment Rhs, /// The expression is an array being indexed, needed to allow constant /// arrays to be dynamically indexed AccessBase { /// The index is a constant constant_index: bool, }, } impl ExprPos { /// Returns an lhs position if the current position is lhs otherwise AccessBase const fn maybe_access_base(&self, constant_index: bool) -> Self { match *self { ExprPos::Lhs | ExprPos::AccessBase { constant_index: false, } => *self, _ => ExprPos::AccessBase { constant_index }, } } } #[derive(Debug)] pub(crate) struct Context<'a> { pub expressions: Arena, pub locals: Arena, /// The [`FunctionArgument`]s for the final [`crate::Function`]. /// /// Parameters with the `out` and `inout` qualifiers have [`Pointer`] types /// here. For example, an `inout vec2 a` argument would be a [`Pointer`] to /// a [`Vector`]. /// /// [`Pointer`]: crate::TypeInner::Pointer /// [`Vector`]: crate::TypeInner::Vector pub arguments: Vec, /// The parameter types given in the source code. /// /// The `out` and `inout` qualifiers don't affect the types that appear /// here. For example, an `inout vec2 a` argument would simply be a /// [`Vector`], not a pointer to one. /// /// [`Vector`]: crate::TypeInner::Vector pub parameters: Vec>, pub parameters_info: Vec, pub symbol_table: crate::front::SymbolTable, pub samplers: FastHashMap, Handle>, pub const_typifier: Typifier, pub typifier: Typifier, layouter: Layouter, emitter: Emitter, stmt_ctx: Option, pub body: Block, pub module: &'a mut crate::Module, pub is_const: bool, /// Tracks the expression kind of `Expression`s residing in `self.expressions` pub local_expression_kind_tracker: crate::proc::ExpressionKindTracker, /// Tracks the expression kind of `Expression`s residing in `self.module.global_expressions` pub global_expression_kind_tracker: &'a mut crate::proc::ExpressionKindTracker, } impl<'a> Context<'a> { pub fn new( frontend: &Frontend, module: &'a mut crate::Module, is_const: bool, global_expression_kind_tracker: &'a mut crate::proc::ExpressionKindTracker, ) -> Result { let mut this = Context { expressions: Arena::new(), locals: Arena::new(), arguments: Vec::new(), parameters: Vec::new(), parameters_info: Vec::new(), symbol_table: crate::front::SymbolTable::default(), samplers: FastHashMap::default(), const_typifier: Typifier::new(), typifier: Typifier::new(), layouter: Layouter::default(), emitter: Emitter::default(), stmt_ctx: Some(StmtContext::new()), body: Block::new(), module, is_const: false, local_expression_kind_tracker: crate::proc::ExpressionKindTracker::new(), global_expression_kind_tracker, }; this.emit_start(); for &(ref name, lookup) in frontend.global_variables.iter() { this.add_global(name, lookup)? } this.is_const = is_const; Ok(this) } pub fn new_body(&mut self, cb: F) -> Result where F: FnOnce(&mut Self) -> Result<()>, { self.new_body_with_ret(cb).map(|(b, _)| b) } pub fn new_body_with_ret(&mut self, cb: F) -> Result<(Block, R)> where F: FnOnce(&mut Self) -> Result, { self.emit_restart(); let old_body = core::mem::replace(&mut self.body, Block::new()); let res = cb(self); self.emit_restart(); let new_body = core::mem::replace(&mut self.body, old_body); res.map(|r| (new_body, r)) } pub fn with_body(&mut self, body: Block, cb: F) -> Result where F: FnOnce(&mut Self) -> Result<()>, { self.emit_restart(); let old_body = core::mem::replace(&mut self.body, body); let res = cb(self); self.emit_restart(); let body = core::mem::replace(&mut self.body, old_body); res.map(|_| body) } pub fn add_global( &mut self, name: &str, GlobalLookup { kind, entry_arg, mutable, }: GlobalLookup, ) -> Result<()> { let (expr, load, constant) = match kind { GlobalLookupKind::Variable(v) => { let span = self.module.global_variables.get_span(v); ( self.add_expression(Expression::GlobalVariable(v), span)?, self.module.global_variables[v].space != AddressSpace::Handle, None, ) } GlobalLookupKind::BlockSelect(handle, index) => { let span = self.module.global_variables.get_span(handle); let base = self.add_expression(Expression::GlobalVariable(handle), span)?; let expr = self.add_expression(Expression::AccessIndex { base, index }, span)?; ( expr, { let ty = self.module.global_variables[handle].ty; match self.module.types[ty].inner { TypeInner::Struct { ref members, .. } => { if let TypeInner::Array { size: crate::ArraySize::Dynamic, .. } = self.module.types[members[index as usize].ty].inner { false } else { true } } _ => true, } }, None, ) } GlobalLookupKind::Constant(v, ty) => { let span = self.module.constants.get_span(v); ( self.add_expression(Expression::Constant(v), span)?, false, Some((v, ty)), ) } GlobalLookupKind::Override(v, _ty) => { let span = self.module.overrides.get_span(v); ( self.add_expression(Expression::Override(v), span)?, false, None, ) } }; let var = VariableReference { expr, load, mutable, constant, entry_arg, }; self.symbol_table.add(name.into(), var); Ok(()) } /// Starts the expression emitter /// /// # Panics /// /// - If called twice in a row without calling [`emit_end`][Self::emit_end]. #[inline] pub fn emit_start(&mut self) { self.emitter.start(&self.expressions) } /// Emits all the expressions captured by the emitter to the current body /// /// # Panics /// /// - If called before calling [`emit_start`]. /// - If called twice in a row without calling [`emit_start`]. /// /// [`emit_start`]: Self::emit_start pub fn emit_end(&mut self) { self.body.extend(self.emitter.finish(&self.expressions)) } /// Emits all the expressions captured by the emitter to the current body /// and starts the emitter again /// /// # Panics /// /// - If called before calling [`emit_start`][Self::emit_start]. pub fn emit_restart(&mut self) { self.emit_end(); self.emit_start() } pub fn add_expression(&mut self, expr: Expression, meta: Span) -> Result> { let mut eval = if self.is_const { crate::proc::ConstantEvaluator::for_glsl_module( self.module, self.global_expression_kind_tracker, &mut self.layouter, ) } else { crate::proc::ConstantEvaluator::for_glsl_function( self.module, &mut self.expressions, &mut self.local_expression_kind_tracker, &mut self.layouter, &mut self.emitter, &mut self.body, ) }; eval.try_eval_and_append(expr, meta).map_err(|e| Error { kind: e.into(), meta, }) } /// Add variable to current scope /// /// Returns a variable if a variable with the same name was already defined, /// otherwise returns `None` pub fn add_local_var( &mut self, name: String, expr: Handle, mutable: bool, ) -> Option { let var = VariableReference { expr, load: true, mutable, constant: None, entry_arg: None, }; self.symbol_table.add(name, var) } /// Add function argument to current scope pub fn add_function_arg( &mut self, name_meta: Option<(String, Span)>, ty: Handle, qualifier: ParameterQualifier, ) -> Result<()> { let index = self.arguments.len(); let mut arg = FunctionArgument { name: name_meta.as_ref().map(|&(ref name, _)| name.clone()), ty, binding: None, }; self.parameters.push(ty); let opaque = match self.module.types[ty].inner { TypeInner::Image { .. } | TypeInner::Sampler { .. } => true, _ => false, }; if qualifier.is_lhs() { let span = self.module.types.get_span(arg.ty); arg.ty = self.module.types.insert( Type { name: None, inner: TypeInner::Pointer { base: arg.ty, space: AddressSpace::Function, }, }, span, ) } self.arguments.push(arg); self.parameters_info.push(ParameterInfo { qualifier, depth: false, }); if let Some((name, meta)) = name_meta { let expr = self.add_expression(Expression::FunctionArgument(index as u32), meta)?; let mutable = qualifier != ParameterQualifier::Const && !opaque; let load = qualifier.is_lhs(); let var = if mutable && !load { let handle = self.locals.append( LocalVariable { name: Some(name.clone()), ty, init: None, }, meta, ); let local_expr = self.add_expression(Expression::LocalVariable(handle), meta)?; self.emit_restart(); self.body.push( Statement::Store { pointer: local_expr, value: expr, }, meta, ); VariableReference { expr: local_expr, load: true, mutable, constant: None, entry_arg: None, } } else { VariableReference { expr, load, mutable, constant: None, entry_arg: None, } }; self.symbol_table.add(name, var); } Ok(()) } /// Returns a [`StmtContext`] to be used in parsing and lowering /// /// # Panics /// /// - If more than one [`StmtContext`] are active at the same time or if the /// previous call didn't use it in lowering. #[must_use] pub const fn stmt_ctx(&mut self) -> StmtContext { self.stmt_ctx.take().unwrap() } /// Lowers a [`HirExpr`] which might produce a [`Expression`]. /// /// consumes a [`StmtContext`] returning it to the context so that it can be /// used again later. pub fn lower( &mut self, mut stmt: StmtContext, frontend: &mut Frontend, expr: Handle, pos: ExprPos, ) -> Result<(Option>, Span)> { let res = self.lower_inner(&stmt, frontend, expr, pos); stmt.hir_exprs.clear(); self.stmt_ctx = Some(stmt); res } /// Similar to [`lower`](Self::lower) but returns an error if the expression /// returns void (ie. doesn't produce a [`Expression`]). /// /// consumes a [`StmtContext`] returning it to the context so that it can be /// used again later. pub fn lower_expect( &mut self, mut stmt: StmtContext, frontend: &mut Frontend, expr: Handle, pos: ExprPos, ) -> Result<(Handle, Span)> { let res = self.lower_expect_inner(&stmt, frontend, expr, pos); stmt.hir_exprs.clear(); self.stmt_ctx = Some(stmt); res } /// internal implementation of [`lower_expect`](Self::lower_expect) /// /// this method is only public because it's used in /// [`function_call`](Frontend::function_call), unless you know what /// you're doing use [`lower_expect`](Self::lower_expect) pub fn lower_expect_inner( &mut self, stmt: &StmtContext, frontend: &mut Frontend, expr: Handle, pos: ExprPos, ) -> Result<(Handle, Span)> { let (maybe_expr, meta) = self.lower_inner(stmt, frontend, expr, pos)?; let expr = match maybe_expr { Some(e) => e, None => { return Err(Error { kind: ErrorKind::SemanticError("Expression returns void".into()), meta, }) } }; Ok((expr, meta)) } fn lower_store( &mut self, pointer: Handle, value: Handle, meta: Span, ) -> Result<()> { if let Expression::Swizzle { size, mut vector, pattern, } = self.expressions[pointer] { // Stores to swizzled values are not directly supported, // lower them as series of per-component stores. let size = match size { VectorSize::Bi => 2, VectorSize::Tri => 3, VectorSize::Quad => 4, }; if let Expression::Load { pointer } = self.expressions[vector] { vector = pointer; } #[allow(clippy::needless_range_loop)] for index in 0..size { let dst = self.add_expression( Expression::AccessIndex { base: vector, index: pattern[index].index(), }, meta, )?; let src = self.add_expression( Expression::AccessIndex { base: value, index: index as u32, }, meta, )?; self.emit_restart(); self.body.push( Statement::Store { pointer: dst, value: src, }, meta, ); } } else { self.emit_restart(); self.body.push(Statement::Store { pointer, value }, meta); } Ok(()) } /// Internal implementation of [`lower`](Self::lower) fn lower_inner( &mut self, stmt: &StmtContext, frontend: &mut Frontend, expr: Handle, pos: ExprPos, ) -> Result<(Option>, Span)> { let HirExpr { ref kind, meta } = stmt.hir_exprs[expr]; log::debug!("Lowering {expr:?} (kind {kind:?}, pos {pos:?})"); let handle = match *kind { HirExprKind::Access { base, index } => { let (index, _) = self.lower_expect_inner(stmt, frontend, index, ExprPos::Rhs)?; let maybe_constant_index = match pos { // Don't try to generate `AccessIndex` if in a LHS position, since it // wouldn't produce a pointer. ExprPos::Lhs => None, _ => self .module .to_ctx() .get_const_val_from(index, &self.expressions) .ok(), }; let base = self .lower_expect_inner( stmt, frontend, base, pos.maybe_access_base(maybe_constant_index.is_some()), )? .0; let pointer = maybe_constant_index .map(|index| self.add_expression(Expression::AccessIndex { base, index }, meta)) .unwrap_or_else(|| { self.add_expression(Expression::Access { base, index }, meta) })?; if ExprPos::Rhs == pos { let resolved = self.resolve_type(pointer, meta)?; if resolved.pointer_space().is_some() { return Ok(( Some(self.add_expression(Expression::Load { pointer }, meta)?), meta, )); } } pointer } HirExprKind::Select { base, ref field } => { let base = self.lower_expect_inner(stmt, frontend, base, pos)?.0; frontend.field_selection(self, pos, base, field, meta)? } HirExprKind::Literal(literal) if pos != ExprPos::Lhs => { self.add_expression(Expression::Literal(literal), meta)? } HirExprKind::Binary { left, op, right } if pos != ExprPos::Lhs => { let (mut left, left_meta) = self.lower_expect_inner(stmt, frontend, left, ExprPos::Rhs)?; let (mut right, right_meta) = self.lower_expect_inner(stmt, frontend, right, ExprPos::Rhs)?; match op { BinaryOperator::ShiftLeft | BinaryOperator::ShiftRight => { self.implicit_conversion(&mut right, right_meta, Scalar::U32)? } _ => self .binary_implicit_conversion(&mut left, left_meta, &mut right, right_meta)?, } self.typifier_grow(left, left_meta)?; self.typifier_grow(right, right_meta)?; let left_inner = self.get_type(left); let right_inner = self.get_type(right); match (left_inner, right_inner) { ( &TypeInner::Matrix { columns: left_columns, rows: left_rows, scalar: left_scalar, }, &TypeInner::Matrix { columns: right_columns, rows: right_rows, scalar: right_scalar, }, ) => { let dimensions_ok = if op == BinaryOperator::Multiply { left_columns == right_rows } else { left_columns == right_columns && left_rows == right_rows }; // Check that the two arguments have the same dimensions if !dimensions_ok || left_scalar != right_scalar { frontend.errors.push(Error { kind: ErrorKind::SemanticError( format!( "Cannot apply operation to {left_inner:?} and {right_inner:?}" ) .into(), ), meta, }) } match op { BinaryOperator::Divide => { // Naga IR doesn't support matrix division so we need to // divide the columns individually and reassemble the matrix let mut components = Vec::with_capacity(left_columns as usize); for index in 0..left_columns as u32 { // Get the column vectors let left_vector = self.add_expression( Expression::AccessIndex { base: left, index }, meta, )?; let right_vector = self.add_expression( Expression::AccessIndex { base: right, index }, meta, )?; // Divide the vectors let column = self.add_expression( Expression::Binary { op, left: left_vector, right: right_vector, }, meta, )?; components.push(column) } let ty = self.module.types.insert( Type { name: None, inner: TypeInner::Matrix { columns: left_columns, rows: left_rows, scalar: left_scalar, }, }, Span::default(), ); // Rebuild the matrix from the divided vectors self.add_expression(Expression::Compose { ty, components }, meta)? } BinaryOperator::Equal | BinaryOperator::NotEqual => { // Naga IR doesn't support matrix comparisons so we need to // compare the columns individually and then fold them together // // The folding is done using a logical and for equality and // a logical or for inequality let equals = op == BinaryOperator::Equal; let (op, combine, fun) = match equals { true => ( BinaryOperator::Equal, BinaryOperator::LogicalAnd, RelationalFunction::All, ), false => ( BinaryOperator::NotEqual, BinaryOperator::LogicalOr, RelationalFunction::Any, ), }; let mut root = None; for index in 0..left_columns as u32 { // Get the column vectors let left_vector = self.add_expression( Expression::AccessIndex { base: left, index }, meta, )?; let right_vector = self.add_expression( Expression::AccessIndex { base: right, index }, meta, )?; let argument = self.add_expression( Expression::Binary { op, left: left_vector, right: right_vector, }, meta, )?; // The result of comparing two vectors is a boolean vector // so use a relational function like all to get a single // boolean value let compare = self.add_expression( Expression::Relational { fun, argument }, meta, )?; // Fold the result root = Some(match root { Some(right) => self.add_expression( Expression::Binary { op: combine, left: compare, right, }, meta, )?, None => compare, }); } root.unwrap() } _ => { self.add_expression(Expression::Binary { left, op, right }, meta)? } } } (&TypeInner::Vector { .. }, &TypeInner::Vector { .. }) => match op { BinaryOperator::Equal | BinaryOperator::NotEqual => { let equals = op == BinaryOperator::Equal; let (op, fun) = match equals { true => (BinaryOperator::Equal, RelationalFunction::All), false => (BinaryOperator::NotEqual, RelationalFunction::Any), }; let argument = self.add_expression(Expression::Binary { op, left, right }, meta)?; self.add_expression(Expression::Relational { fun, argument }, meta)? } _ => self.add_expression(Expression::Binary { left, op, right }, meta)?, }, (&TypeInner::Vector { size, .. }, &TypeInner::Scalar { .. }) => match op { BinaryOperator::Add | BinaryOperator::Subtract | BinaryOperator::Divide | BinaryOperator::And | BinaryOperator::ExclusiveOr | BinaryOperator::InclusiveOr | BinaryOperator::ShiftLeft | BinaryOperator::ShiftRight => { let scalar_vector = self .add_expression(Expression::Splat { size, value: right }, meta)?; self.add_expression( Expression::Binary { op, left, right: scalar_vector, }, meta, )? } _ => self.add_expression(Expression::Binary { left, op, right }, meta)?, }, (&TypeInner::Scalar { .. }, &TypeInner::Vector { size, .. }) => match op { BinaryOperator::Add | BinaryOperator::Subtract | BinaryOperator::Divide | BinaryOperator::And | BinaryOperator::ExclusiveOr | BinaryOperator::InclusiveOr => { let scalar_vector = self.add_expression(Expression::Splat { size, value: left }, meta)?; self.add_expression( Expression::Binary { op, left: scalar_vector, right, }, meta, )? } _ => self.add_expression(Expression::Binary { left, op, right }, meta)?, }, ( &TypeInner::Scalar(left_scalar), &TypeInner::Matrix { rows, columns, scalar: right_scalar, }, ) => { // Check that the two arguments have the same scalar type if left_scalar != right_scalar { frontend.errors.push(Error { kind: ErrorKind::SemanticError( format!( "Cannot apply operation to {left_inner:?} and {right_inner:?}" ) .into(), ), meta, }) } match op { BinaryOperator::Divide | BinaryOperator::Add | BinaryOperator::Subtract => { // Naga IR doesn't support all matrix by scalar operations so // we need for some to turn the scalar into a vector by // splatting it and then for each column vector apply the // operation and finally reconstruct the matrix let scalar_vector = self.add_expression( Expression::Splat { size: rows, value: left, }, meta, )?; let mut components = Vec::with_capacity(columns as usize); for index in 0..columns as u32 { // Get the column vector let matrix_column = self.add_expression( Expression::AccessIndex { base: right, index }, meta, )?; // Apply the operation to the splatted vector and // the column vector let column = self.add_expression( Expression::Binary { op, left: scalar_vector, right: matrix_column, }, meta, )?; components.push(column) } let ty = self.module.types.insert( Type { name: None, inner: TypeInner::Matrix { columns, rows, scalar: left_scalar, }, }, Span::default(), ); // Rebuild the matrix from the operation result vectors self.add_expression(Expression::Compose { ty, components }, meta)? } _ => { self.add_expression(Expression::Binary { left, op, right }, meta)? } } } ( &TypeInner::Matrix { rows, columns, scalar: left_scalar, }, &TypeInner::Scalar(right_scalar), ) => { // Check that the two arguments have the same scalar type if left_scalar != right_scalar { frontend.errors.push(Error { kind: ErrorKind::SemanticError( format!( "Cannot apply operation to {left_inner:?} and {right_inner:?}" ) .into(), ), meta, }) } match op { BinaryOperator::Divide | BinaryOperator::Add | BinaryOperator::Subtract => { // Naga IR doesn't support all matrix by scalar operations so // we need for some to turn the scalar into a vector by // splatting it and then for each column vector apply the // operation and finally reconstruct the matrix let scalar_vector = self.add_expression( Expression::Splat { size: rows, value: right, }, meta, )?; let mut components = Vec::with_capacity(columns as usize); for index in 0..columns as u32 { // Get the column vector let matrix_column = self.add_expression( Expression::AccessIndex { base: left, index }, meta, )?; // Apply the operation to the splatted vector and // the column vector let column = self.add_expression( Expression::Binary { op, left: matrix_column, right: scalar_vector, }, meta, )?; components.push(column) } let ty = self.module.types.insert( Type { name: None, inner: TypeInner::Matrix { columns, rows, scalar: left_scalar, }, }, Span::default(), ); // Rebuild the matrix from the operation result vectors self.add_expression(Expression::Compose { ty, components }, meta)? } _ => { self.add_expression(Expression::Binary { left, op, right }, meta)? } } } _ => self.add_expression(Expression::Binary { left, op, right }, meta)?, } } HirExprKind::Unary { op, expr } if pos != ExprPos::Lhs => { let expr = self .lower_expect_inner(stmt, frontend, expr, ExprPos::Rhs)? .0; if let TypeInner::Matrix { scalar, .. } = *self.resolve_type(expr, meta)? { // Naga IR doesn't support matrix negation, so we need to turn it into // multiplication by scalar -1. let minus_one = Literal::minus_one(scalar).ok_or_else(|| Error { kind: ErrorKind::SemanticError( format!("Cannot apply operator {op:?} to type {scalar:?}").into(), ), meta, })?; let lhs = self.add_expression(Expression::Literal(minus_one), meta)?; self.add_expression( Expression::Binary { op: BinaryOperator::Multiply, left: lhs, right: expr, }, meta, )? } else { self.add_expression(Expression::Unary { op, expr }, meta)? } } HirExprKind::Variable(ref var) => match pos { ExprPos::Lhs => { if !var.mutable { frontend.errors.push(Error { kind: ErrorKind::SemanticError( "Variable cannot be used in LHS position".into(), ), meta, }) } var.expr } ExprPos::AccessBase { constant_index } => { // If the index isn't constant all accesses backed by a constant base need // to be done through a proxy local variable, since constants have a non // pointer type which is required for dynamic indexing if !constant_index { if let Some((constant, ty)) = var.constant { let init = self .add_expression(Expression::Constant(constant), Span::default())?; let local = self.locals.append( LocalVariable { name: None, ty, init: Some(init), }, Span::default(), ); self.add_expression(Expression::LocalVariable(local), Span::default())? } else { var.expr } } else { var.expr } } _ if var.load => { self.add_expression(Expression::Load { pointer: var.expr }, meta)? } ExprPos::Rhs => { if let Some((constant, _)) = self.is_const.then_some(var.constant).flatten() { self.add_expression(Expression::Constant(constant), meta)? } else { // Check if this is an Override expression in const context if self.is_const { if let Expression::Override(o) = self.expressions[var.expr] { // Need to add the Override expression to the global arena self.add_expression(Expression::Override(o), meta)? } else { var.expr } } else { var.expr } } } }, HirExprKind::Call(ref call) if pos != ExprPos::Lhs => { let maybe_expr = frontend.function_or_constructor_call( self, stmt, call.kind.clone(), &call.args, meta, )?; return Ok((maybe_expr, meta)); } // `HirExprKind::Conditional` represents the ternary operator in glsl (`:?`) // // The ternary operator is defined to only evaluate one of the two possible // expressions which means that it's behavior is that of an `if` statement, // and it's merely syntactic sugar for it. HirExprKind::Conditional { condition, accept, reject, } if ExprPos::Lhs != pos => { // Given an expression `a ? b : c`, we need to produce a Naga // statement roughly like: // // var temp; // if a { // temp = convert(b); // } else { // temp = convert(c); // } // // where `convert` stands for type conversions to bring `b` and `c` to // the same type, and then use `temp` to represent the value of the whole // conditional expression in subsequent code. // Lower the condition first to the current bodyy let condition = self .lower_expect_inner(stmt, frontend, condition, ExprPos::Rhs)? .0; let (mut accept_body, (mut accept, accept_meta)) = self.new_body_with_ret(|ctx| { // Lower the `true` branch ctx.lower_expect_inner(stmt, frontend, accept, pos) })?; let (mut reject_body, (mut reject, reject_meta)) = self.new_body_with_ret(|ctx| { // Lower the `false` branch ctx.lower_expect_inner(stmt, frontend, reject, pos) })?; // We need to do some custom implicit conversions since the two target expressions // are in different bodies if let (Some((accept_power, accept_scalar)), Some((reject_power, reject_scalar))) = ( // Get the components of both branches and calculate the type power self.expr_scalar_components(accept, accept_meta)? .and_then(|scalar| Some((type_power(scalar)?, scalar))), self.expr_scalar_components(reject, reject_meta)? .and_then(|scalar| Some((type_power(scalar)?, scalar))), ) { match accept_power.cmp(&reject_power) { core::cmp::Ordering::Less => { accept_body = self.with_body(accept_body, |ctx| { ctx.conversion(&mut accept, accept_meta, reject_scalar)?; Ok(()) })?; } core::cmp::Ordering::Equal => {} core::cmp::Ordering::Greater => { reject_body = self.with_body(reject_body, |ctx| { ctx.conversion(&mut reject, reject_meta, accept_scalar)?; Ok(()) })?; } } } // We need to get the type of the resulting expression to create the local, // this must be done after implicit conversions to ensure both branches have // the same type. let ty = self.resolve_type_handle(accept, accept_meta)?; // Add the local that will hold the result of our conditional let local = self.locals.append( LocalVariable { name: None, ty, init: None, }, meta, ); let local_expr = self.add_expression(Expression::LocalVariable(local), meta)?; // Add to each the store to the result variable accept_body.push( Statement::Store { pointer: local_expr, value: accept, }, accept_meta, ); reject_body.push( Statement::Store { pointer: local_expr, value: reject, }, reject_meta, ); // Finally add the `If` to the main body with the `condition` we lowered // earlier and the branches we prepared. self.body.push( Statement::If { condition, accept: accept_body, reject: reject_body, }, meta, ); // Note: `Expression::Load` must be emitted before it's used so make // sure the emitter is active here. self.add_expression( Expression::Load { pointer: local_expr, }, meta, )? } HirExprKind::Assign { tgt, value } if ExprPos::Lhs != pos => { let (pointer, ptr_meta) = self.lower_expect_inner(stmt, frontend, tgt, ExprPos::Lhs)?; let (mut value, value_meta) = self.lower_expect_inner(stmt, frontend, value, ExprPos::Rhs)?; let ty = match *self.resolve_type(pointer, ptr_meta)? { TypeInner::Pointer { base, .. } => &self.module.types[base].inner, ref ty => ty, }; if let Some(scalar) = scalar_components(ty) { self.implicit_conversion(&mut value, value_meta, scalar)?; } self.lower_store(pointer, value, meta)?; value } HirExprKind::PrePostfix { op, postfix, expr } if ExprPos::Lhs != pos => { let (pointer, _) = self.lower_expect_inner(stmt, frontend, expr, ExprPos::Lhs)?; let left = if let Expression::Swizzle { .. } = self.expressions[pointer] { pointer } else { self.add_expression(Expression::Load { pointer }, meta)? }; let res = match *self.resolve_type(left, meta)? { TypeInner::Scalar(scalar) => { let ty = TypeInner::Scalar(scalar); Literal::one(scalar).map(|i| (ty, i, None, None)) } TypeInner::Vector { size, scalar } => { let ty = TypeInner::Vector { size, scalar }; Literal::one(scalar).map(|i| (ty, i, Some(size), None)) } TypeInner::Matrix { columns, rows, scalar, } => { let ty = TypeInner::Matrix { columns, rows, scalar, }; Literal::one(scalar).map(|i| (ty, i, Some(rows), Some(columns))) } _ => None, }; let (ty_inner, literal, rows, columns) = match res { Some(res) => res, None => { frontend.errors.push(Error { kind: ErrorKind::SemanticError( "Increment/decrement only works on scalar/vector/matrix".into(), ), meta, }); return Ok((Some(left), meta)); } }; let mut right = self.add_expression(Expression::Literal(literal), meta)?; // Glsl allows pre/postfixes operations on vectors and matrices, so if the // target is either of them change the right side of the addition to be splatted // to the same size as the target, furthermore if the target is a matrix // use a composed matrix using the splatted value. if let Some(size) = rows { right = self.add_expression(Expression::Splat { size, value: right }, meta)?; if let Some(cols) = columns { let ty = self.module.types.insert( Type { name: None, inner: ty_inner, }, meta, ); right = self.add_expression( Expression::Compose { ty, components: core::iter::repeat_n(right, cols as usize).collect(), }, meta, )?; } } let value = self.add_expression(Expression::Binary { op, left, right }, meta)?; self.lower_store(pointer, value, meta)?; if postfix { left } else { value } } HirExprKind::Method { expr: object, ref name, ref args, } if ExprPos::Lhs != pos => { let args = args .iter() .map(|e| self.lower_expect_inner(stmt, frontend, *e, ExprPos::Rhs)) .collect::>>()?; match name.as_ref() { "length" => { if !args.is_empty() { frontend.errors.push(Error { kind: ErrorKind::SemanticError( ".length() doesn't take any arguments".into(), ), meta, }); } let lowered_array = self.lower_expect_inner(stmt, frontend, object, pos)?.0; let array_type = self.resolve_type(lowered_array, meta)?; match *array_type { TypeInner::Array { size: crate::ArraySize::Constant(size), .. } => { let mut array_length = self.add_expression( Expression::Literal(Literal::U32(size.get())), meta, )?; self.forced_conversion(&mut array_length, meta, Scalar::I32)?; array_length } // let the error be handled in type checking if it's not a dynamic array _ => { let mut array_length = self .add_expression(Expression::ArrayLength(lowered_array), meta)?; self.conversion(&mut array_length, meta, Scalar::I32)?; array_length } } } _ => { return Err(Error { kind: ErrorKind::SemanticError( format!("unknown method '{name}'").into(), ), meta, }); } } } HirExprKind::Sequence { ref exprs } if pos != ExprPos::Lhs => { let mut last_handle = None; for expr in exprs.iter() { let (handle, _) = self.lower_expect_inner(stmt, frontend, *expr, ExprPos::Rhs)?; last_handle = Some(handle); } match last_handle { Some(handle) => handle, None => unreachable!(), } } _ => { return Err(Error { kind: ErrorKind::SemanticError( format!("{:?} cannot be in the left hand side", stmt.hir_exprs[expr]) .into(), ), meta, }) } }; log::trace!("Lowered {expr:?}\n\tKind = {kind:?}\n\tPos = {pos:?}\n\tResult = {handle:?}"); Ok((Some(handle), meta)) } pub fn expr_scalar_components( &mut self, expr: Handle, meta: Span, ) -> Result> { let ty = self.resolve_type(expr, meta)?; Ok(scalar_components(ty)) } pub fn expr_power(&mut self, expr: Handle, meta: Span) -> Result> { Ok(self .expr_scalar_components(expr, meta)? .and_then(type_power)) } pub fn conversion( &mut self, expr: &mut Handle, meta: Span, scalar: Scalar, ) -> Result<()> { *expr = self.add_expression( Expression::As { expr: *expr, kind: scalar.kind, convert: Some(scalar.width), }, meta, )?; Ok(()) } pub fn implicit_conversion( &mut self, expr: &mut Handle, meta: Span, scalar: Scalar, ) -> Result<()> { if let (Some(tgt_power), Some(expr_power)) = (type_power(scalar), self.expr_power(*expr, meta)?) { if tgt_power > expr_power { self.conversion(expr, meta, scalar)?; } } Ok(()) } pub fn forced_conversion( &mut self, expr: &mut Handle, meta: Span, scalar: Scalar, ) -> Result<()> { if let Some(expr_scalar) = self.expr_scalar_components(*expr, meta)? { if expr_scalar != scalar { self.conversion(expr, meta, scalar)?; } } Ok(()) } pub fn binary_implicit_conversion( &mut self, left: &mut Handle, left_meta: Span, right: &mut Handle, right_meta: Span, ) -> Result<()> { let left_components = self.expr_scalar_components(*left, left_meta)?; let right_components = self.expr_scalar_components(*right, right_meta)?; if let (Some((left_power, left_scalar)), Some((right_power, right_scalar))) = ( left_components.and_then(|scalar| Some((type_power(scalar)?, scalar))), right_components.and_then(|scalar| Some((type_power(scalar)?, scalar))), ) { match left_power.cmp(&right_power) { core::cmp::Ordering::Less => { self.conversion(left, left_meta, right_scalar)?; } core::cmp::Ordering::Equal => {} core::cmp::Ordering::Greater => { self.conversion(right, right_meta, left_scalar)?; } } } Ok(()) } pub fn implicit_splat( &mut self, expr: &mut Handle, meta: Span, vector_size: Option, ) -> Result<()> { let expr_type = self.resolve_type(*expr, meta)?; if let (&TypeInner::Scalar { .. }, Some(size)) = (expr_type, vector_size) { *expr = self.add_expression(Expression::Splat { size, value: *expr }, meta)? } Ok(()) } pub fn vector_resize( &mut self, size: VectorSize, vector: Handle, meta: Span, ) -> Result> { self.add_expression( Expression::Swizzle { size, vector, pattern: crate::SwizzleComponent::XYZW, }, meta, ) } } impl Index> for Context<'_> { type Output = Expression; fn index(&self, index: Handle) -> &Self::Output { if self.is_const { &self.module.global_expressions[index] } else { &self.expressions[index] } } } /// Helper struct passed when parsing expressions /// /// This struct should only be obtained through [`stmt_ctx`](Context::stmt_ctx) /// and only one of these may be active at any time per context. #[derive(Debug)] pub struct StmtContext { /// A arena of high level expressions which can be lowered through a /// [`Context`] to Naga's [`Expression`]s pub hir_exprs: Arena, } impl StmtContext { const fn new() -> Self { StmtContext { hir_exprs: Arena::new(), } } } ================================================ FILE: naga/src/front/glsl/error.rs ================================================ use alloc::{ borrow::Cow, string::{String, ToString}, vec, vec::Vec, }; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::files::SimpleFile; use codespan_reporting::term; use pp_rs::token::PreprocessorError; use thiserror::Error; use super::token::TokenValue; #[cfg(feature = "stderr")] use crate::error::ErrorWrite; use crate::{error::replace_control_chars, SourceLocation}; use crate::{proc::ConstantEvaluatorError, Span}; fn join_with_comma(list: &[ExpectedToken]) -> String { let mut string = "".to_string(); for (i, val) in list.iter().enumerate() { string.push_str(&val.to_string()); match i { i if i == list.len() - 1 => {} i if i == list.len() - 2 => string.push_str(" or "), _ => string.push_str(", "), } } string } /// One of the expected tokens returned in [`InvalidToken`](ErrorKind::InvalidToken). #[derive(Clone, Debug, PartialEq)] pub enum ExpectedToken { /// A specific token was expected. Token(TokenValue), /// A type was expected. TypeName, /// An identifier was expected. Identifier, /// An integer literal was expected. IntLiteral, /// A float literal was expected. FloatLiteral, /// A boolean literal was expected. BoolLiteral, /// The end of file was expected. Eof, } impl From for ExpectedToken { fn from(token: TokenValue) -> Self { ExpectedToken::Token(token) } } impl core::fmt::Display for ExpectedToken { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match *self { ExpectedToken::Token(ref token) => write!(f, "{token:?}"), ExpectedToken::TypeName => write!(f, "a type"), ExpectedToken::Identifier => write!(f, "identifier"), ExpectedToken::IntLiteral => write!(f, "integer literal"), ExpectedToken::FloatLiteral => write!(f, "float literal"), ExpectedToken::BoolLiteral => write!(f, "bool literal"), ExpectedToken::Eof => write!(f, "end of file"), } } } /// Information about the cause of an error. #[derive(Clone, Debug, Error)] #[cfg_attr(test, derive(PartialEq))] pub enum ErrorKind { /// Whilst parsing as encountered an unexpected EOF. #[error("Unexpected end of file")] EndOfFile, /// The shader specified an unsupported or invalid profile. #[error("Invalid profile: {0}")] InvalidProfile(String), /// The shader requested an unsupported or invalid version. #[error("Invalid version: {0}")] InvalidVersion(u64), /// Whilst parsing an unexpected token was encountered. /// /// A list of expected tokens is also returned. #[error("Expected {expected_tokens}, found {found_token:?}", found_token = .0, expected_tokens = join_with_comma(.1))] InvalidToken(TokenValue, Vec), /// A specific feature is not yet implemented. /// /// To help prioritize work please open an issue in the github issue tracker /// if none exist already or react to the already existing one. #[error("Not implemented: {0}")] NotImplemented(&'static str), /// A reference to a variable that wasn't declared was used. #[error("Unknown variable: {0}")] UnknownVariable(String), /// A reference to a type that wasn't declared was used. #[error("Unknown type: {0}")] UnknownType(String), /// A reference to a non existent member of a type was made. #[error("Unknown field: {0}")] UnknownField(String), /// An unknown layout qualifier was used. /// /// If the qualifier does exist please open an issue in the github issue tracker /// if none exist already or react to the already existing one to help /// prioritize work. #[error("Unknown layout qualifier: {0}")] UnknownLayoutQualifier(String), /// Unsupported matrix of the form matCx2 /// /// Our IR expects matrices of the form matCx2 to have a stride of 8 however /// matrices in the std140 layout have a stride of at least 16. #[error("unsupported matrix of the form matCx2 (in this case mat{columns}x2) in std140 block layout. See https://github.com/gfx-rs/wgpu/issues/4375")] UnsupportedMatrixWithTwoRowsInStd140 { columns: u8 }, /// Unsupported matrix of the form f16matCxR /// /// Our IR expects matrices of the form f16matCxR to have a stride of 4/8/8 depending on row-count, /// however matrices in the std140 layout have a stride of at least 16. #[error("unsupported matrix of the form f16matCxR (in this case f16mat{columns}x{rows}) in std140 block layout. See https://github.com/gfx-rs/wgpu/issues/4375")] UnsupportedF16MatrixInStd140 { columns: u8, rows: u8 }, /// A variable with the same name already exists in the current scope. #[error("Variable already declared: {0}")] VariableAlreadyDeclared(String), /// A semantic error was detected in the shader. #[error("{0}")] SemanticError(Cow<'static, str>), /// An error was returned by the preprocessor. #[error("{0:?}")] PreprocessorError(PreprocessorError), /// The parser entered an illegal state and exited /// /// This obviously is a bug and as such should be reported in the github issue tracker #[error("Internal error: {0}")] InternalError(&'static str), } impl From for ErrorKind { fn from(err: ConstantEvaluatorError) -> Self { ErrorKind::SemanticError(err.to_string().into()) } } /// Error returned during shader parsing. #[derive(Clone, Debug, Error)] #[error("{kind}")] #[cfg_attr(test, derive(PartialEq))] pub struct Error { /// Holds the information about the error itself. pub kind: ErrorKind, /// Holds information about the range of the source code where the error happened. pub meta: Span, } impl Error { /// Returns a [`SourceLocation`] for the error message. pub fn location(&self, source: &str) -> Option { Some(self.meta.location(source)) } } /// A collection of errors returned during shader parsing. #[derive(Clone, Debug)] #[cfg_attr(test, derive(PartialEq))] pub struct ParseErrors { pub errors: Vec, } impl ParseErrors { #[cfg(feature = "stderr")] pub fn emit_to_writer(&self, writer: &mut impl ErrorWrite, source: &str) { self.emit_to_writer_with_path(writer, source, "glsl"); } #[cfg(feature = "stderr")] pub fn emit_to_writer_with_path(&self, writer: &mut impl ErrorWrite, source: &str, path: &str) { let path = path.to_string(); let files = SimpleFile::new(path, replace_control_chars(source)); let config = term::Config::default(); for err in &self.errors { let diagnostic = Self::make_diagnostic(err); crate::error::emit_to_writer(writer, &config, &files, &diagnostic) .expect("cannot write error"); } } /// Emits a summary of the errors to standard error stream. #[cfg(feature = "stderr")] pub fn emit_to_stderr(&self, source: &str) { self.emit_to_stderr_with_path(source, "glsl") } /// Emits a summary of the errors to standard error stream. #[cfg(feature = "stderr")] pub fn emit_to_stderr_with_path(&self, source: &str, path: &str) { cfg_if::cfg_if! { if #[cfg(feature = "termcolor")] { let writer = term::termcolor::StandardStream::stderr(term::termcolor::ColorChoice::Auto); self.emit_to_writer_with_path(&mut writer.lock(), source, path); } else { let writer = std::io::stderr(); self.emit_to_writer_with_path(&mut writer.lock(), source, path); } } } pub fn emit_to_string(&self, source: &str) -> String { self.emit_to_string_with_path(source, "glsl") } pub fn emit_to_string_with_path(&self, source: &str, path: &str) -> String { let path = path.to_string(); let files = SimpleFile::new(path, replace_control_chars(source)); let config = term::Config::default(); let mut writer = crate::error::DiagnosticBuffer::new(); for err in &self.errors { let diagnostic = Self::make_diagnostic(err); writer .emit_to_self(&config, &files, &diagnostic) .expect("cannot write error"); } writer.into_string() } fn make_diagnostic(err: &Error) -> Diagnostic<()> { let mut diagnostic = Diagnostic::error().with_message(err.kind.to_string()); if let Some(range) = err.meta.to_range() { diagnostic = diagnostic.with_labels(vec![Label::primary((), range)]); } diagnostic } } impl core::fmt::Display for ParseErrors { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { self.errors.iter().try_for_each(|e| write!(f, "{e:?}")) } } impl core::error::Error for ParseErrors {} impl From> for ParseErrors { fn from(errors: Vec) -> Self { Self { errors } } } ================================================ FILE: naga/src/front/glsl/functions.rs ================================================ use alloc::{ format, string::{String, ToString}, vec, vec::Vec, }; use core::iter; use super::{ ast::*, builtins::{inject_builtin, sampled_to_depth}, context::{Context, ExprPos, StmtContext}, error::{Error, ErrorKind}, types::scalar_components, Frontend, Result, }; use crate::{ front::glsl::types::type_power, proc::ensure_block_returns, AddressSpace, Block, EntryPoint, Expression, Function, FunctionArgument, FunctionResult, Handle, Literal, LocalVariable, Scalar, ScalarKind, Span, Statement, StructMember, Type, TypeInner, }; /// Struct detailing a store operation that must happen after a function call struct ProxyWrite { /// The store target target: Handle, /// A pointer to read the value of the store value: Handle, /// An optional conversion to be applied convert: Option, } impl Frontend { pub(crate) fn function_or_constructor_call( &mut self, ctx: &mut Context, stmt: &StmtContext, fc: FunctionCallKind, raw_args: &[Handle], meta: Span, ) -> Result>> { let args: Vec<_> = raw_args .iter() .map(|e| ctx.lower_expect_inner(stmt, self, *e, ExprPos::Rhs)) .collect::>()?; match fc { FunctionCallKind::TypeConstructor(ty) => { if args.len() == 1 { self.constructor_single(ctx, ty, args[0], meta).map(Some) } else { self.constructor_many(ctx, ty, args, meta).map(Some) } } FunctionCallKind::Function(name) => { self.function_call(ctx, stmt, name, args, raw_args, meta) } } } fn constructor_single( &mut self, ctx: &mut Context, ty: Handle, (mut value, expr_meta): (Handle, Span), meta: Span, ) -> Result> { let expr_type = ctx.resolve_type(value, expr_meta)?; let vector_size = match *expr_type { TypeInner::Vector { size, .. } => Some(size), _ => None, }; let expr_is_bool = expr_type.scalar_kind() == Some(ScalarKind::Bool); // Special case: if casting from a bool, we need to use Select and not As. match ctx.module.types[ty].inner.scalar() { Some(result_scalar) if expr_is_bool && result_scalar.kind != ScalarKind::Bool => { let result_scalar = Scalar { width: 4, ..result_scalar }; let l0 = Literal::zero(result_scalar).unwrap(); let l1 = Literal::one(result_scalar).unwrap(); let mut reject = ctx.add_expression(Expression::Literal(l0), expr_meta)?; let mut accept = ctx.add_expression(Expression::Literal(l1), expr_meta)?; ctx.implicit_splat(&mut reject, meta, vector_size)?; ctx.implicit_splat(&mut accept, meta, vector_size)?; let h = ctx.add_expression( Expression::Select { accept, reject, condition: value, }, expr_meta, )?; return Ok(h); } _ => {} } Ok(match ctx.module.types[ty].inner { TypeInner::Vector { size, scalar } if vector_size.is_none() => { ctx.forced_conversion(&mut value, expr_meta, scalar)?; if let TypeInner::Scalar { .. } = *ctx.resolve_type(value, expr_meta)? { ctx.add_expression(Expression::Splat { size, value }, meta)? } else { self.vector_constructor(ctx, ty, size, scalar, &[(value, expr_meta)], meta)? } } TypeInner::Scalar(scalar) => { let mut expr = value; if let TypeInner::Vector { .. } | TypeInner::Matrix { .. } = *ctx.resolve_type(value, expr_meta)? { expr = ctx.add_expression( Expression::AccessIndex { base: expr, index: 0, }, meta, )?; } if let TypeInner::Matrix { .. } = *ctx.resolve_type(value, expr_meta)? { expr = ctx.add_expression( Expression::AccessIndex { base: expr, index: 0, }, meta, )?; } ctx.add_expression( Expression::As { kind: scalar.kind, expr, convert: Some(scalar.width), }, meta, )? } TypeInner::Vector { size, scalar } => { if vector_size != Some(size) { value = ctx.vector_resize(size, value, expr_meta)?; } ctx.add_expression( Expression::As { kind: scalar.kind, expr: value, convert: Some(scalar.width), }, meta, )? } TypeInner::Matrix { columns, rows, scalar, } => self.matrix_one_arg(ctx, ty, columns, rows, scalar, (value, expr_meta), meta)?, TypeInner::Struct { ref members, .. } => { let scalar_components = members .first() .and_then(|member| scalar_components(&ctx.module.types[member.ty].inner)); if let Some(scalar) = scalar_components { ctx.implicit_conversion(&mut value, expr_meta, scalar)?; } ctx.add_expression( Expression::Compose { ty, components: vec![value], }, meta, )? } TypeInner::Array { base, .. } => { let scalar_components = scalar_components(&ctx.module.types[base].inner); if let Some(scalar) = scalar_components { ctx.implicit_conversion(&mut value, expr_meta, scalar)?; } ctx.add_expression( Expression::Compose { ty, components: vec![value], }, meta, )? } _ => { self.errors.push(Error { kind: ErrorKind::SemanticError("Bad type constructor".into()), meta, }); value } }) } #[allow(clippy::too_many_arguments)] fn matrix_one_arg( &mut self, ctx: &mut Context, ty: Handle, columns: crate::VectorSize, rows: crate::VectorSize, element_scalar: Scalar, (mut value, expr_meta): (Handle, Span), meta: Span, ) -> Result> { let mut components = Vec::with_capacity(columns as usize); // TODO: casts // `Expression::As` doesn't support matrix width // casts so we need to do some extra work for casts ctx.forced_conversion(&mut value, expr_meta, element_scalar)?; match *ctx.resolve_type(value, expr_meta)? { TypeInner::Scalar(_) => { // If a matrix is constructed with a single scalar value, then that // value is used to initialize all the values along the diagonal of // the matrix; the rest are given zeros. let vector_ty = ctx.module.types.insert( Type { name: None, inner: TypeInner::Vector { size: rows, scalar: element_scalar, }, }, meta, ); let zero_literal = Literal::zero(element_scalar).unwrap(); let zero = ctx.add_expression(Expression::Literal(zero_literal), meta)?; for i in 0..columns as u32 { components.push( ctx.add_expression( Expression::Compose { ty: vector_ty, components: (0..rows as u32) .map(|r| match r == i { true => value, false => zero, }) .collect(), }, meta, )?, ) } } TypeInner::Matrix { rows: ori_rows, columns: ori_cols, .. } => { // If a matrix is constructed from a matrix, then each component // (column i, row j) in the result that has a corresponding component // (column i, row j) in the argument will be initialized from there. All // other components will be initialized to the identity matrix. let zero_literal = Literal::zero(element_scalar).unwrap(); let one_literal = Literal::one(element_scalar).unwrap(); let zero = ctx.add_expression(Expression::Literal(zero_literal), meta)?; let one = ctx.add_expression(Expression::Literal(one_literal), meta)?; let vector_ty = ctx.module.types.insert( Type { name: None, inner: TypeInner::Vector { size: rows, scalar: element_scalar, }, }, meta, ); for i in 0..columns as u32 { if i < ori_cols as u32 { use core::cmp::Ordering; let vector = ctx.add_expression( Expression::AccessIndex { base: value, index: i, }, meta, )?; components.push(match ori_rows.cmp(&rows) { Ordering::Less => { let components = (0..rows as u32) .map(|r| { if r < ori_rows as u32 { ctx.add_expression( Expression::AccessIndex { base: vector, index: r, }, meta, ) } else if r == i { Ok(one) } else { Ok(zero) } }) .collect::>()?; ctx.add_expression( Expression::Compose { ty: vector_ty, components, }, meta, )? } Ordering::Equal => vector, Ordering::Greater => ctx.vector_resize(rows, vector, meta)?, }) } else { let compose_expr = Expression::Compose { ty: vector_ty, components: (0..rows as u32) .map(|r| match r == i { true => one, false => zero, }) .collect(), }; let vec = ctx.add_expression(compose_expr, meta)?; components.push(vec) } } } _ => { components = iter::repeat_n(value, columns as usize).collect(); } } ctx.add_expression(Expression::Compose { ty, components }, meta) } fn vector_constructor( &mut self, ctx: &mut Context, ty: Handle, size: crate::VectorSize, scalar: Scalar, args: &[(Handle, Span)], meta: Span, ) -> Result> { let mut components = Vec::with_capacity(size as usize); for (mut arg, expr_meta) in args.iter().copied() { ctx.forced_conversion(&mut arg, expr_meta, scalar)?; if components.len() >= size as usize { break; } match *ctx.resolve_type(arg, expr_meta)? { TypeInner::Scalar { .. } => components.push(arg), TypeInner::Matrix { rows, columns, .. } => { components.reserve(rows as usize * columns as usize); for c in 0..(columns as u32) { let base = ctx.add_expression( Expression::AccessIndex { base: arg, index: c, }, expr_meta, )?; for r in 0..(rows as u32) { components.push(ctx.add_expression( Expression::AccessIndex { base, index: r }, expr_meta, )?) } } } TypeInner::Vector { size: ori_size, .. } => { components.reserve(ori_size as usize); for index in 0..(ori_size as u32) { components.push(ctx.add_expression( Expression::AccessIndex { base: arg, index }, expr_meta, )?) } } _ => components.push(arg), } } components.truncate(size as usize); ctx.add_expression(Expression::Compose { ty, components }, meta) } fn constructor_many( &mut self, ctx: &mut Context, ty: Handle, args: Vec<(Handle, Span)>, meta: Span, ) -> Result> { let mut components = Vec::with_capacity(args.len()); let struct_member_data = match ctx.module.types[ty].inner { TypeInner::Matrix { columns, rows, scalar: element_scalar, } => { let mut flattened = Vec::with_capacity(columns as usize * rows as usize); for (mut arg, meta) in args.iter().copied() { ctx.forced_conversion(&mut arg, meta, element_scalar)?; match *ctx.resolve_type(arg, meta)? { TypeInner::Vector { size, .. } => { for i in 0..(size as u32) { flattened.push(ctx.add_expression( Expression::AccessIndex { base: arg, index: i, }, meta, )?) } } _ => flattened.push(arg), } } let ty = ctx.module.types.insert( Type { name: None, inner: TypeInner::Vector { size: rows, scalar: element_scalar, }, }, meta, ); for chunk in flattened.chunks(rows as usize) { components.push(ctx.add_expression( Expression::Compose { ty, components: Vec::from(chunk), }, meta, )?) } None } TypeInner::Vector { size, scalar } => { return self.vector_constructor(ctx, ty, size, scalar, &args, meta) } TypeInner::Array { base, .. } => { for (mut arg, meta) in args.iter().copied() { let scalar_components = scalar_components(&ctx.module.types[base].inner); if let Some(scalar) = scalar_components { ctx.implicit_conversion(&mut arg, meta, scalar)?; } components.push(arg) } None } TypeInner::Struct { ref members, .. } => Some( members .iter() .map(|member| scalar_components(&ctx.module.types[member.ty].inner)) .collect::>(), ), _ => { return Err(Error { kind: ErrorKind::SemanticError("Constructor: Too many arguments".into()), meta, }) } }; if let Some(struct_member_data) = struct_member_data { for ((mut arg, meta), scalar_components) in args.iter().copied().zip(struct_member_data.iter().copied()) { if let Some(scalar) = scalar_components { ctx.implicit_conversion(&mut arg, meta, scalar)?; } components.push(arg) } } ctx.add_expression(Expression::Compose { ty, components }, meta) } fn function_call( &mut self, ctx: &mut Context, stmt: &StmtContext, name: String, args: Vec<(Handle, Span)>, raw_args: &[Handle], meta: Span, ) -> Result>> { // Grow the typifier to be able to index it later without needing // to hold the context mutably for &(expr, span) in args.iter() { ctx.typifier_grow(expr, span)?; } // Check if the passed arguments require any special variations let mut variations = builtin_required_variations(args.iter().map(|&(expr, _)| ctx.get_type(expr))); // Initiate the declaration if it wasn't previously initialized and inject builtins let declaration = self.lookup_function.entry(name.clone()).or_insert_with(|| { variations |= BuiltinVariations::STANDARD; Default::default() }); inject_builtin(declaration, ctx.module, &name, variations); // Borrow again but without mutability, at this point a declaration is guaranteed let declaration = self.lookup_function.get(&name).unwrap(); // Possibly contains the overload to be used in the call let mut maybe_overload = None; // The conversions needed for the best analyzed overload, this is initialized all to // `NONE` to make sure that conversions always pass the first time without ambiguity let mut old_conversions = vec![Conversion::None; args.len()]; // Tracks whether the comparison between overloads lead to an ambiguity let mut ambiguous = false; // Iterate over all the available overloads to select either an exact match or a // overload which has suitable implicit conversions 'outer: for (overload_idx, overload) in declaration.overloads.iter().enumerate() { // If the overload and the function call don't have the same number of arguments // continue to the next overload if args.len() != overload.parameters.len() { continue; } log::trace!("Testing overload {overload_idx}"); // Stores whether the current overload matches exactly the function call let mut exact = true; // State of the selection // If None we still don't know what is the best overload // If Some(true) the new overload is better // If Some(false) the old overload is better let mut superior = None; // Store the conversions for the current overload so that later they can replace the // conversions used for querying the best overload let mut new_conversions = vec![Conversion::None; args.len()]; // Loop through the overload parameters and check if the current overload is better // compared to the previous best overload. for (i, overload_parameter) in overload.parameters.iter().enumerate() { let call_argument = &args[i]; let parameter_info = &overload.parameters_info[i]; // If the image is used in the overload as a depth texture convert it // before comparing, otherwise exact matches wouldn't be reported if parameter_info.depth { sampled_to_depth(ctx, call_argument.0, call_argument.1, &mut self.errors); ctx.invalidate_expression(call_argument.0, call_argument.1)? } ctx.typifier_grow(call_argument.0, call_argument.1)?; let overload_param_ty = &ctx.module.types[*overload_parameter].inner; let call_arg_ty = ctx.get_type(call_argument.0); log::trace!( "Testing parameter {i}\n\tOverload = {overload_param_ty:?}\n\tCall = {call_arg_ty:?}" ); // Storage images cannot be directly compared since while the access is part of the // type in naga's IR, in glsl they are a qualifier and don't enter in the match as // long as the access needed is satisfied. if let ( &TypeInner::Image { class: crate::ImageClass::Storage { format: overload_format, access: overload_access, }, dim: overload_dim, arrayed: overload_arrayed, }, &TypeInner::Image { class: crate::ImageClass::Storage { format: call_format, access: call_access, }, dim: call_dim, arrayed: call_arrayed, }, ) = (overload_param_ty, call_arg_ty) { // Images size must match otherwise the overload isn't what we want let good_size = call_dim == overload_dim && call_arrayed == overload_arrayed; // Glsl requires the formats to strictly match unless you are builtin // function overload and have not been replaced, in which case we only // check that the format scalar kind matches let good_format = overload_format == call_format || (overload.internal && Scalar::from(overload_format) == Scalar::from(call_format)); if !(good_size && good_format) { continue 'outer; } // While storage access mismatch is an error it isn't one that causes // the overload matching to fail so we defer the error and consider // that the images match exactly if !call_access.contains(overload_access) { self.errors.push(Error { kind: ErrorKind::SemanticError( format!( "'{name}': image needs {overload_access:?} access but only {call_access:?} was provided" ) .into(), ), meta, }); } // The images satisfy the conditions to be considered as an exact match new_conversions[i] = Conversion::Exact; continue; } else if overload_param_ty == call_arg_ty { // If the types match there's no need to check for conversions so continue new_conversions[i] = Conversion::Exact; continue; } // Glsl defines that inout follows both the conversions for input parameters and // output parameters, this means that the type must have a conversion from both the // call argument to the function parameter and the function parameter to the call // argument, the only way this is possible is for the conversion to be an identity // (i.e. call argument = function parameter) if let ParameterQualifier::InOut = parameter_info.qualifier { continue 'outer; } // The function call argument and the function definition // parameter are not equal at this point, so we need to try // implicit conversions. // // Now there are two cases, the argument is defined as a normal // parameter (`in` or `const`), in this case an implicit // conversion is made from the calling argument to the // definition argument. If the parameter is `out` the // opposite needs to be done, so the implicit conversion is made // from the definition argument to the calling argument. let maybe_conversion = if parameter_info.qualifier.is_lhs() { conversion(call_arg_ty, overload_param_ty) } else { conversion(overload_param_ty, call_arg_ty) }; let conversion = match maybe_conversion { Some(info) => info, None => continue 'outer, }; // At this point a conversion will be needed so the overload no longer // exactly matches the call arguments exact = false; // Compare the conversions needed for this overload parameter to that of the // last overload analyzed respective parameter, the value is: // - `true` when the new overload argument has a better conversion // - `false` when the old overload argument has a better conversion let best_arg = match (conversion, old_conversions[i]) { // An exact match is always better, we don't need to check this for the // current overload since it was checked earlier (_, Conversion::Exact) => false, // No overload was yet analyzed so this one is the best yet (_, Conversion::None) => true, // A conversion from a float to a double is the best possible conversion (Conversion::FloatToDouble, _) => true, (_, Conversion::FloatToDouble) => false, // A conversion from a float to an integer is preferred than one // from double to an integer (Conversion::IntToFloat, Conversion::IntToDouble) => true, (Conversion::IntToDouble, Conversion::IntToFloat) => false, // This case handles things like no conversion and exact which were already // treated and other cases which no conversion is better than the other _ => continue, }; // Check if the best parameter corresponds to the current selected overload // to pass to the next comparison, if this isn't true mark it as ambiguous match best_arg { true => match superior { Some(false) => ambiguous = true, _ => { superior = Some(true); new_conversions[i] = conversion } }, false => match superior { Some(true) => ambiguous = true, _ => superior = Some(false), }, } } // The overload matches exactly the function call so there's no ambiguity (since // repeated overload aren't allowed) and the current overload is selected, no // further querying is needed. if exact { maybe_overload = Some(overload); ambiguous = false; break; } match superior { // New overload is better keep it Some(true) => { maybe_overload = Some(overload); // Replace the conversions old_conversions = new_conversions; } // Old overload is better do nothing Some(false) => {} // No overload was better than the other this can be caused // when all conversions are ambiguous in which the overloads themselves are // ambiguous. None => { ambiguous = true; // Assign the new overload, this helps ensures that in this case of // ambiguity the parsing won't end immediately and allow for further // collection of errors. maybe_overload = Some(overload); } } } if ambiguous { self.errors.push(Error { kind: ErrorKind::SemanticError( format!("Ambiguous best function for '{name}'").into(), ), meta, }) } let overload = maybe_overload.ok_or_else(|| Error { kind: ErrorKind::SemanticError(format!("Unknown function '{name}'").into()), meta, })?; let parameters_info = overload.parameters_info.clone(); let parameters = overload.parameters.clone(); let is_void = overload.void; let kind = overload.kind; let mut arguments = Vec::with_capacity(args.len()); let mut proxy_writes = Vec::new(); // Iterate through the function call arguments applying transformations as needed for (((parameter_info, call_argument), expr), parameter) in parameters_info .iter() .zip(&args) .zip(raw_args) .zip(¶meters) { if parameter_info.qualifier.is_lhs() { // Reprocess argument in LHS position let (handle, meta) = ctx.lower_expect_inner(stmt, self, *expr, ExprPos::Lhs)?; self.process_lhs_argument( ctx, meta, *parameter, parameter_info, handle, call_argument, &mut proxy_writes, &mut arguments, )?; continue; } let (mut handle, meta) = *call_argument; let scalar_comps = scalar_components(&ctx.module.types[*parameter].inner); // Apply implicit conversions as needed if let Some(scalar) = scalar_comps { ctx.implicit_conversion(&mut handle, meta, scalar)?; } arguments.push(handle) } match kind { FunctionKind::Call(function) => { ctx.emit_end(); let result = if !is_void { Some(ctx.add_expression(Expression::CallResult(function), meta)?) } else { None }; ctx.body.push( Statement::Call { function, arguments, result, }, meta, ); ctx.emit_start(); // Write back all the variables that were scheduled to their original place for proxy_write in proxy_writes { let mut value = ctx.add_expression( Expression::Load { pointer: proxy_write.value, }, meta, )?; if let Some(scalar) = proxy_write.convert { ctx.conversion(&mut value, meta, scalar)?; } ctx.emit_restart(); ctx.body.push( Statement::Store { pointer: proxy_write.target, value, }, meta, ); } Ok(result) } FunctionKind::Macro(builtin) => builtin.call(self, ctx, arguments.as_mut_slice(), meta), } } /// Processes a function call argument that appears in place of an output /// parameter. #[allow(clippy::too_many_arguments)] fn process_lhs_argument( &mut self, ctx: &mut Context, meta: Span, parameter_ty: Handle, parameter_info: &ParameterInfo, original: Handle, call_argument: &(Handle, Span), proxy_writes: &mut Vec, arguments: &mut Vec>, ) -> Result<()> { let original_ty = ctx.resolve_type(original, meta)?; let original_pointer_space = original_ty.pointer_space(); // The type of a possible spill variable needed for a proxy write let mut maybe_ty = match *original_ty { // If the argument is to be passed as a pointer but the type of the // expression returns a vector it must mean that it was for example // swizzled and it must be spilled into a local before calling TypeInner::Vector { size, scalar } => Some(ctx.module.types.insert( Type { name: None, inner: TypeInner::Vector { size, scalar }, }, Span::default(), )), // If the argument is a pointer whose address space isn't `Function`, an // indirection through a local variable is needed to align the address // spaces of the call argument and the overload parameter. TypeInner::Pointer { base, space } if space != AddressSpace::Function => Some(base), TypeInner::ValuePointer { size, scalar, space, } if space != AddressSpace::Function => { let inner = match size { Some(size) => TypeInner::Vector { size, scalar }, None => TypeInner::Scalar(scalar), }; Some( ctx.module .types .insert(Type { name: None, inner }, Span::default()), ) } _ => None, }; // Since the original expression might be a pointer and we want a value // for the proxy writes, we might need to load the pointer. let value = if original_pointer_space.is_some() { ctx.add_expression(Expression::Load { pointer: original }, Span::default())? } else { original }; ctx.typifier_grow(call_argument.0, call_argument.1)?; let overload_param_ty = &ctx.module.types[parameter_ty].inner; let call_arg_ty = ctx.get_type(call_argument.0); let needs_conversion = call_arg_ty != overload_param_ty; let arg_scalar_comps = scalar_components(call_arg_ty); // Since output parameters also allow implicit conversions from the // parameter to the argument, we need to spill the conversion to a // variable and create a proxy write for the original variable. if needs_conversion { maybe_ty = Some(parameter_ty); } if let Some(ty) = maybe_ty { // Create the spill variable let spill_var = ctx.locals.append( LocalVariable { name: None, ty, init: None, }, Span::default(), ); let spill_expr = ctx.add_expression(Expression::LocalVariable(spill_var), Span::default())?; // If the argument is also copied in we must store the value of the // original variable to the spill variable. if let ParameterQualifier::InOut = parameter_info.qualifier { ctx.body.push( Statement::Store { pointer: spill_expr, value, }, Span::default(), ); } // Add the spill variable as an argument to the function call arguments.push(spill_expr); let convert = if needs_conversion { arg_scalar_comps } else { None }; // Register the temporary local to be written back to it's original // place after the function call if let Expression::Swizzle { size, mut vector, pattern, } = ctx.expressions[original] { if let Expression::Load { pointer } = ctx.expressions[vector] { vector = pointer; } for (i, component) in pattern.iter().take(size as usize).enumerate() { let original = ctx.add_expression( Expression::AccessIndex { base: vector, index: *component as u32, }, Span::default(), )?; let spill_component = ctx.add_expression( Expression::AccessIndex { base: spill_expr, index: i as u32, }, Span::default(), )?; proxy_writes.push(ProxyWrite { target: original, value: spill_component, convert, }); } } else { proxy_writes.push(ProxyWrite { target: original, value: spill_expr, convert, }); } } else { arguments.push(original); } Ok(()) } pub(crate) fn add_function( &mut self, mut ctx: Context, name: String, result: Option, meta: Span, ) { ensure_block_returns(&mut ctx.body); let void = result.is_none(); // Check if the passed arguments require any special variations let mut variations = builtin_required_variations( ctx.parameters .iter() .map(|&arg| &ctx.module.types[arg].inner), ); // Initiate the declaration if it wasn't previously initialized and inject builtins let declaration = self.lookup_function.entry(name.clone()).or_insert_with(|| { variations |= BuiltinVariations::STANDARD; Default::default() }); inject_builtin(declaration, ctx.module, &name, variations); let Context { expressions, locals, arguments, parameters, parameters_info, body, module, .. } = ctx; let function = Function { name: Some(name), arguments, result, local_variables: locals, expressions, named_expressions: crate::NamedExpressions::default(), body, diagnostic_filter_leaf: None, }; 'outer: for decl in declaration.overloads.iter_mut() { if parameters.len() != decl.parameters.len() { continue; } for (new_parameter, old_parameter) in parameters.iter().zip(decl.parameters.iter()) { let new_inner = &module.types[*new_parameter].inner; let old_inner = &module.types[*old_parameter].inner; if new_inner != old_inner { continue 'outer; } } if decl.defined { return self.errors.push(Error { kind: ErrorKind::SemanticError("Function already defined".into()), meta, }); } decl.defined = true; decl.parameters_info = parameters_info; match decl.kind { FunctionKind::Call(handle) => *module.functions.get_mut(handle) = function, FunctionKind::Macro(_) => { let handle = module.functions.append(function, meta); decl.kind = FunctionKind::Call(handle) } } return; } let handle = module.functions.append(function, meta); declaration.overloads.push(Overload { parameters, parameters_info, kind: FunctionKind::Call(handle), defined: true, internal: false, void, }); } pub(crate) fn add_prototype( &mut self, ctx: Context, name: String, result: Option, meta: Span, ) { let void = result.is_none(); // Check if the passed arguments require any special variations let mut variations = builtin_required_variations( ctx.parameters .iter() .map(|&arg| &ctx.module.types[arg].inner), ); // Initiate the declaration if it wasn't previously initialized and inject builtins let declaration = self.lookup_function.entry(name.clone()).or_insert_with(|| { variations |= BuiltinVariations::STANDARD; Default::default() }); inject_builtin(declaration, ctx.module, &name, variations); let Context { arguments, parameters, parameters_info, module, .. } = ctx; let function = Function { name: Some(name), arguments, result, ..Default::default() }; 'outer: for decl in declaration.overloads.iter() { if parameters.len() != decl.parameters.len() { continue; } for (new_parameter, old_parameter) in parameters.iter().zip(decl.parameters.iter()) { let new_inner = &module.types[*new_parameter].inner; let old_inner = &module.types[*old_parameter].inner; if new_inner != old_inner { continue 'outer; } } return self.errors.push(Error { kind: ErrorKind::SemanticError("Prototype already defined".into()), meta, }); } let handle = module.functions.append(function, meta); declaration.overloads.push(Overload { parameters, parameters_info, kind: FunctionKind::Call(handle), defined: false, internal: false, void, }); } /// Create a Naga [`EntryPoint`] that calls the GLSL `main` function. /// /// We compile the GLSL `main` function as an ordinary Naga [`Function`]. /// This function synthesizes a Naga [`EntryPoint`] to call that. /// /// Each GLSL input and output variable (including builtins) becomes a Naga /// [`GlobalVariable`]s in the [`Private`] address space, which `main` can /// access in the usual way. /// /// The `EntryPoint` we synthesize here has an argument for each GLSL input /// variable, and returns a struct with a member for each GLSL output /// variable. The entry point contains code to: /// /// - copy its arguments into the Naga globals representing the GLSL input /// variables, /// /// - call the Naga `Function` representing the GLSL `main` function, and then /// /// - build its return value from whatever values the GLSL `main` left in /// the Naga globals representing GLSL `output` variables. /// /// Upon entry, [`ctx.body`] should contain code, accumulated by prior calls /// to [`ParsingContext::parse_external_declaration`][pxd], to initialize /// private global variables as needed. This code gets spliced into the /// entry point before the call to `main`. /// /// [`GlobalVariable`]: crate::GlobalVariable /// [`Private`]: crate::AddressSpace::Private /// [`ctx.body`]: Context::body /// [pxd]: super::ParsingContext::parse_external_declaration pub(crate) fn add_entry_point( &mut self, function: Handle, mut ctx: Context, ) -> Result<()> { let mut arguments = Vec::new(); let body = Block::with_capacity( // global init body ctx.body.len() + // prologue and epilogue self.entry_args.len() * 2 // Call, Emit for composing struct and return + 3, ); let global_init_body = core::mem::replace(&mut ctx.body, body); for arg in self.entry_args.iter() { if arg.storage != StorageQualifier::Input { continue; } let pointer = ctx .expressions .append(Expression::GlobalVariable(arg.handle), Default::default()); ctx.local_expression_kind_tracker .insert(pointer, crate::proc::ExpressionKind::Runtime); let ty = ctx.module.global_variables[arg.handle].ty; ctx.arg_type_walker( arg.name.clone(), arg.binding.clone(), pointer, ty, &mut |ctx, name, pointer, ty, binding| { let idx = arguments.len() as u32; arguments.push(FunctionArgument { name, ty, binding: Some(binding), }); let value = ctx .expressions .append(Expression::FunctionArgument(idx), Default::default()); ctx.local_expression_kind_tracker .insert(value, crate::proc::ExpressionKind::Runtime); ctx.body .push(Statement::Store { pointer, value }, Default::default()); }, )? } ctx.body.extend_block(global_init_body); ctx.body.push( Statement::Call { function, arguments: Vec::new(), result: None, }, Default::default(), ); let mut span = 0; let mut members = Vec::new(); let mut components = Vec::new(); for arg in self.entry_args.iter() { if arg.storage != StorageQualifier::Output { continue; } let pointer = ctx .expressions .append(Expression::GlobalVariable(arg.handle), Default::default()); ctx.local_expression_kind_tracker .insert(pointer, crate::proc::ExpressionKind::Runtime); let ty = ctx.module.global_variables[arg.handle].ty; ctx.arg_type_walker( arg.name.clone(), arg.binding.clone(), pointer, ty, &mut |ctx, name, pointer, ty, binding| { members.push(StructMember { name, ty, binding: Some(binding), offset: span, }); span += ctx.module.types[ty].inner.size(ctx.module.to_ctx()); let len = ctx.expressions.len(); let load = ctx .expressions .append(Expression::Load { pointer }, Default::default()); ctx.local_expression_kind_tracker .insert(load, crate::proc::ExpressionKind::Runtime); ctx.body.push( Statement::Emit(ctx.expressions.range_from(len)), Default::default(), ); components.push(load) }, )? } let (ty, value) = if !components.is_empty() { let ty = ctx.module.types.insert( Type { name: None, inner: TypeInner::Struct { members, span }, }, Default::default(), ); let len = ctx.expressions.len(); let res = ctx .expressions .append(Expression::Compose { ty, components }, Default::default()); ctx.local_expression_kind_tracker .insert(res, crate::proc::ExpressionKind::Runtime); ctx.body.push( Statement::Emit(ctx.expressions.range_from(len)), Default::default(), ); (Some(ty), Some(res)) } else { (None, None) }; ctx.body .push(Statement::Return { value }, Default::default()); let Context { body, expressions, .. } = ctx; ctx.module.entry_points.push(EntryPoint { name: "main".to_string(), stage: self.meta.stage, early_depth_test: Some(crate::EarlyDepthTest::Force) .filter(|_| self.meta.early_fragment_tests), workgroup_size: self.meta.workgroup_size, workgroup_size_overrides: None, function: Function { arguments, expressions, body, result: ty.map(|ty| FunctionResult { ty, binding: None }), ..Default::default() }, mesh_info: None, task_payload: None, incoming_ray_payload: None, }); Ok(()) } } impl Context<'_> { /// Helper function for building the input/output interface of the entry point /// /// Calls `f` with the data of the entry point argument, flattening composite types /// recursively /// /// The passed arguments to the callback are: /// - The ctx /// - The name /// - The pointer expression to the global storage /// - The handle to the type of the entry point argument /// - The binding of the entry point argument fn arg_type_walker( &mut self, name: Option, binding: crate::Binding, pointer: Handle, ty: Handle, f: &mut impl FnMut( &mut Context, Option, Handle, Handle, crate::Binding, ), ) -> Result<()> { match self.module.types[ty].inner { // TODO: Better error reporting // right now we just don't walk the array if the size isn't known at // compile time and let validation catch it TypeInner::Array { base, size: crate::ArraySize::Constant(size), .. } => { let mut location = match binding { crate::Binding::Location { location, .. } => location, crate::Binding::BuiltIn(_) => return Ok(()), }; let interpolation = self.module.types[base] .inner .scalar_kind() .map(|kind| match kind { ScalarKind::Float => crate::Interpolation::Perspective, _ => crate::Interpolation::Flat, }); for index in 0..size.get() { let member_pointer = self.add_expression( Expression::AccessIndex { base: pointer, index, }, Span::default(), )?; let binding = crate::Binding::Location { location, interpolation, sampling: None, blend_src: None, per_primitive: false, }; location += 1; self.arg_type_walker(name.clone(), binding, member_pointer, base, f)? } } TypeInner::Struct { ref members, .. } => { let mut location = match binding { crate::Binding::Location { location, .. } => location, crate::Binding::BuiltIn(_) => return Ok(()), }; for (i, member) in members.clone().into_iter().enumerate() { let member_pointer = self.add_expression( Expression::AccessIndex { base: pointer, index: i as u32, }, Span::default(), )?; let binding = match member.binding { Some(binding) => binding, None => { let interpolation = self.module.types[member.ty] .inner .scalar_kind() .map(|kind| match kind { ScalarKind::Float => crate::Interpolation::Perspective, _ => crate::Interpolation::Flat, }); let binding = crate::Binding::Location { location, interpolation, sampling: None, blend_src: None, per_primitive: false, }; location += 1; binding } }; self.arg_type_walker(member.name, binding, member_pointer, member.ty, f)? } } _ => f(self, name, pointer, ty, binding), } Ok(()) } } /// Helper enum containing the type of conversion need for a call #[derive(PartialEq, Eq, Clone, Copy, Debug)] enum Conversion { /// No conversion needed Exact, /// Float to double conversion needed FloatToDouble, /// Int or uint to float conversion needed IntToFloat, /// Int or uint to double conversion needed IntToDouble, /// Other type of conversion needed Other, /// No conversion was yet registered None, } /// Helper function, returns the type of conversion from `source` to `target`, if a /// conversion is not possible returns None. fn conversion(target: &TypeInner, source: &TypeInner) -> Option { use ScalarKind::*; // Gather the `ScalarKind` and scalar width from both the target and the source let (target_scalar, source_scalar) = match (target, source) { // Conversions between scalars are allowed (&TypeInner::Scalar(tgt_scalar), &TypeInner::Scalar(src_scalar)) => { (tgt_scalar, src_scalar) } // Conversions between vectors of the same size are allowed ( &TypeInner::Vector { size: tgt_size, scalar: tgt_scalar, }, &TypeInner::Vector { size: src_size, scalar: src_scalar, }, ) if tgt_size == src_size => (tgt_scalar, src_scalar), // Conversions between matrices of the same size are allowed ( &TypeInner::Matrix { rows: tgt_rows, columns: tgt_cols, scalar: tgt_scalar, }, &TypeInner::Matrix { rows: src_rows, columns: src_cols, scalar: src_scalar, }, ) if tgt_cols == src_cols && tgt_rows == src_rows => (tgt_scalar, src_scalar), _ => return None, }; // Check if source can be converted into target, if this is the case then the type // power of target must be higher than that of source let target_power = type_power(target_scalar); let source_power = type_power(source_scalar); if target_power < source_power { return None; } Some(match (target_scalar, source_scalar) { // A conversion from a float to a double is special (Scalar::F64, Scalar::F32) => Conversion::FloatToDouble, // A conversion from an integer to a float is special ( Scalar::F32, Scalar { kind: Sint | Uint, width: _, }, ) => Conversion::IntToFloat, // A conversion from an integer to a double is special ( Scalar::F64, Scalar { kind: Sint | Uint, width: _, }, ) => Conversion::IntToDouble, _ => Conversion::Other, }) } /// Helper method returning all the non standard builtin variations needed /// to process the function call with the passed arguments fn builtin_required_variations<'a>(args: impl Iterator) -> BuiltinVariations { let mut variations = BuiltinVariations::empty(); for ty in args { match *ty { TypeInner::ValuePointer { scalar, .. } | TypeInner::Scalar(scalar) | TypeInner::Vector { scalar, .. } | TypeInner::Matrix { scalar, .. } => { if scalar == Scalar::F64 { variations |= BuiltinVariations::DOUBLE } } TypeInner::Image { dim, arrayed, class, } => { if dim == crate::ImageDimension::Cube && arrayed { variations |= BuiltinVariations::CUBE_TEXTURES_ARRAY } if dim == crate::ImageDimension::D2 && arrayed && class.is_multisampled() { variations |= BuiltinVariations::D2_MULTI_TEXTURES_ARRAY } } _ => {} } } variations } ================================================ FILE: naga/src/front/glsl/lex.rs ================================================ use alloc::string::String; use pp_rs::{ pp::Preprocessor, token::{PreprocessorError, Punct, TokenValue as PPTokenValue}, }; use super::{ ast::Precision, token::{Directive, DirectiveKind, Token, TokenValue}, types::parse_type, }; use crate::{FastHashMap, Span, StorageAccess}; #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] pub struct LexerResult { pub kind: LexerResultKind, pub meta: Span, } #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] pub enum LexerResultKind { Token(Token), Directive(Directive), Error(PreprocessorError), } pub struct Lexer<'a> { pp: Preprocessor<'a>, } impl<'a> Lexer<'a> { pub fn new(input: &'a str, defines: &'a FastHashMap) -> Self { let mut pp = Preprocessor::new(input); for (define, value) in defines { pp.add_define(define, value).unwrap(); //TODO: handle error } Lexer { pp } } } impl Iterator for Lexer<'_> { type Item = LexerResult; fn next(&mut self) -> Option { let pp_token = match self.pp.next()? { Ok(t) => t, Err((err, loc)) => { return Some(LexerResult { kind: LexerResultKind::Error(err), meta: loc.into(), }); } }; let meta = pp_token.location.into(); let value = match pp_token.value { PPTokenValue::Extension(extension) => { return Some(LexerResult { kind: LexerResultKind::Directive(Directive { kind: DirectiveKind::Extension, tokens: extension.tokens, }), meta, }) } PPTokenValue::Float(float) => TokenValue::FloatConstant(float), PPTokenValue::Ident(ident) => { match ident.as_str() { // Qualifiers "layout" => TokenValue::Layout, "in" => TokenValue::In, "out" => TokenValue::Out, "uniform" => TokenValue::Uniform, "buffer" => TokenValue::Buffer, "shared" => TokenValue::Shared, "invariant" => TokenValue::Invariant, "flat" => TokenValue::Interpolation(crate::Interpolation::Flat), "noperspective" => TokenValue::Interpolation(crate::Interpolation::Linear), "smooth" => TokenValue::Interpolation(crate::Interpolation::Perspective), "centroid" => TokenValue::Sampling(crate::Sampling::Centroid), "sample" => TokenValue::Sampling(crate::Sampling::Sample), "const" => TokenValue::Const, "inout" => TokenValue::InOut, "precision" => TokenValue::Precision, "highp" => TokenValue::PrecisionQualifier(Precision::High), "mediump" => TokenValue::PrecisionQualifier(Precision::Medium), "lowp" => TokenValue::PrecisionQualifier(Precision::Low), "restrict" => TokenValue::Restrict, "readonly" => TokenValue::MemoryQualifier(StorageAccess::LOAD), "writeonly" => TokenValue::MemoryQualifier(StorageAccess::STORE), // values "true" => TokenValue::BoolConstant(true), "false" => TokenValue::BoolConstant(false), // jump statements "continue" => TokenValue::Continue, "break" => TokenValue::Break, "return" => TokenValue::Return, "discard" => TokenValue::Discard, // selection statements "if" => TokenValue::If, "else" => TokenValue::Else, "switch" => TokenValue::Switch, "case" => TokenValue::Case, "default" => TokenValue::Default, // iteration statements "while" => TokenValue::While, "do" => TokenValue::Do, "for" => TokenValue::For, // types "void" => TokenValue::Void, "struct" => TokenValue::Struct, word => match parse_type(word) { Some(t) => TokenValue::TypeName(t), None => TokenValue::Identifier(String::from(word)), }, } } PPTokenValue::Integer(integer) => TokenValue::IntConstant(integer), PPTokenValue::Punct(punct) => match punct { // Compound assignments Punct::AddAssign => TokenValue::AddAssign, Punct::SubAssign => TokenValue::SubAssign, Punct::MulAssign => TokenValue::MulAssign, Punct::DivAssign => TokenValue::DivAssign, Punct::ModAssign => TokenValue::ModAssign, Punct::LeftShiftAssign => TokenValue::LeftShiftAssign, Punct::RightShiftAssign => TokenValue::RightShiftAssign, Punct::AndAssign => TokenValue::AndAssign, Punct::XorAssign => TokenValue::XorAssign, Punct::OrAssign => TokenValue::OrAssign, // Two character punctuation Punct::Increment => TokenValue::Increment, Punct::Decrement => TokenValue::Decrement, Punct::LogicalAnd => TokenValue::LogicalAnd, Punct::LogicalOr => TokenValue::LogicalOr, Punct::LogicalXor => TokenValue::LogicalXor, Punct::LessEqual => TokenValue::LessEqual, Punct::GreaterEqual => TokenValue::GreaterEqual, Punct::EqualEqual => TokenValue::Equal, Punct::NotEqual => TokenValue::NotEqual, Punct::LeftShift => TokenValue::LeftShift, Punct::RightShift => TokenValue::RightShift, // Parenthesis or similar Punct::LeftBrace => TokenValue::LeftBrace, Punct::RightBrace => TokenValue::RightBrace, Punct::LeftParen => TokenValue::LeftParen, Punct::RightParen => TokenValue::RightParen, Punct::LeftBracket => TokenValue::LeftBracket, Punct::RightBracket => TokenValue::RightBracket, // Other one character punctuation Punct::LeftAngle => TokenValue::LeftAngle, Punct::RightAngle => TokenValue::RightAngle, Punct::Semicolon => TokenValue::Semicolon, Punct::Comma => TokenValue::Comma, Punct::Colon => TokenValue::Colon, Punct::Dot => TokenValue::Dot, Punct::Equal => TokenValue::Assign, Punct::Bang => TokenValue::Bang, Punct::Minus => TokenValue::Dash, Punct::Tilde => TokenValue::Tilde, Punct::Plus => TokenValue::Plus, Punct::Star => TokenValue::Star, Punct::Slash => TokenValue::Slash, Punct::Percent => TokenValue::Percent, Punct::Pipe => TokenValue::VerticalBar, Punct::Caret => TokenValue::Caret, Punct::Ampersand => TokenValue::Ampersand, Punct::Question => TokenValue::Question, }, PPTokenValue::Pragma(pragma) => { return Some(LexerResult { kind: LexerResultKind::Directive(Directive { kind: DirectiveKind::Pragma, tokens: pragma.tokens, }), meta, }) } PPTokenValue::Version(version) => { return Some(LexerResult { kind: LexerResultKind::Directive(Directive { kind: DirectiveKind::Version { is_first_directive: version.is_first_directive, }, tokens: version.tokens, }), meta, }) } }; Some(LexerResult { kind: LexerResultKind::Token(Token { value, meta }), meta, }) } } #[cfg(test)] mod tests { use alloc::vec; use pp_rs::token::{Integer, Location, Token as PPToken, TokenValue as PPTokenValue}; use super::{ super::token::{Directive, DirectiveKind, Token, TokenValue}, Lexer, LexerResult, LexerResultKind, }; use crate::Span; #[test] fn lex_tokens() { let defines = crate::FastHashMap::default(); // line comments let mut lex = Lexer::new("#version 450\nvoid main () {}", &defines); let mut location = Location::default(); location.start = 9; location.end = 12; assert_eq!( lex.next().unwrap(), LexerResult { kind: LexerResultKind::Directive(Directive { kind: DirectiveKind::Version { is_first_directive: true }, tokens: vec![PPToken { value: PPTokenValue::Integer(Integer { signed: true, value: 450, width: 32 }), location }] }), meta: Span::new(1, 8) } ); assert_eq!( lex.next().unwrap(), LexerResult { kind: LexerResultKind::Token(Token { value: TokenValue::Void, meta: Span::new(13, 17) }), meta: Span::new(13, 17) } ); assert_eq!( lex.next().unwrap(), LexerResult { kind: LexerResultKind::Token(Token { value: TokenValue::Identifier("main".into()), meta: Span::new(18, 22) }), meta: Span::new(18, 22) } ); assert_eq!( lex.next().unwrap(), LexerResult { kind: LexerResultKind::Token(Token { value: TokenValue::LeftParen, meta: Span::new(23, 24) }), meta: Span::new(23, 24) } ); assert_eq!( lex.next().unwrap(), LexerResult { kind: LexerResultKind::Token(Token { value: TokenValue::RightParen, meta: Span::new(24, 25) }), meta: Span::new(24, 25) } ); assert_eq!( lex.next().unwrap(), LexerResult { kind: LexerResultKind::Token(Token { value: TokenValue::LeftBrace, meta: Span::new(26, 27) }), meta: Span::new(26, 27) } ); assert_eq!( lex.next().unwrap(), LexerResult { kind: LexerResultKind::Token(Token { value: TokenValue::RightBrace, meta: Span::new(27, 28) }), meta: Span::new(27, 28) } ); assert_eq!(lex.next(), None); } } ================================================ FILE: naga/src/front/glsl/mod.rs ================================================ /*! Frontend for [GLSL][glsl] (OpenGL Shading Language). To begin, take a look at the documentation for the [`Frontend`]. # Supported versions ## Vulkan - 440 (partial) - 450 - 460 [glsl]: https://www.khronos.org/registry/OpenGL/index_gl.php */ pub use ast::{Precision, Profile}; pub use error::{Error, ErrorKind, ExpectedToken, ParseErrors}; pub use token::TokenValue; use alloc::{string::String, vec::Vec}; use crate::{proc::Layouter, FastHashMap, FastHashSet, Handle, Module, ShaderStage, Span, Type}; use ast::{EntryArg, FunctionDeclaration, GlobalLookup}; use parser::ParsingContext; mod ast; mod builtins; mod context; mod error; mod functions; mod lex; mod offset; mod parser; #[cfg(test)] mod parser_tests; mod token; mod types; mod variables; type Result = core::result::Result; /// Per-shader options passed to [`parse`](Frontend::parse). /// /// The [`From`] trait is implemented for [`ShaderStage`] to provide a quick way /// to create an `Options` instance. /// /// ```rust /// # use naga::ShaderStage; /// # use naga::front::glsl::Options; /// Options::from(ShaderStage::Vertex); /// ``` #[derive(Debug)] pub struct Options { /// The shader stage in the pipeline. pub stage: ShaderStage, /// Preprocessor definitions to be used, akin to having /// ```glsl /// #define key value /// ``` /// for each key value pair in the map. pub defines: FastHashMap, } impl From for Options { fn from(stage: ShaderStage) -> Self { Options { stage, defines: FastHashMap::default(), } } } /// Additional information about the GLSL shader. /// /// Stores additional information about the GLSL shader which might not be /// stored in the shader [`Module`]. #[derive(Debug)] pub struct ShaderMetadata { /// The GLSL version specified in the shader through the use of the /// `#version` preprocessor directive. pub version: u16, /// The GLSL profile specified in the shader through the use of the /// `#version` preprocessor directive. pub profile: Profile, /// The shader stage in the pipeline, passed to the [`parse`](Frontend::parse) /// method via the [`Options`] struct. pub stage: ShaderStage, /// The workgroup size for compute shaders, defaults to `[1; 3]` for /// compute shaders and `[0; 3]` for non compute shaders. pub workgroup_size: [u32; 3], /// Whether or not early fragment tests where requested by the shader. /// Defaults to `false`. pub early_fragment_tests: bool, /// The shader can request extensions via the /// `#extension` preprocessor directive, in the directive a behavior /// parameter is used to control whether the extension should be disabled, /// warn on usage, enabled if possible or required. /// /// This field only stores extensions which were required or requested to /// be enabled if possible and they are supported. pub extensions: FastHashSet, } impl ShaderMetadata { fn reset(&mut self, stage: ShaderStage) { self.version = 0; self.profile = Profile::Core; self.stage = stage; self.workgroup_size = [u32::from(stage.compute_like()); 3]; self.early_fragment_tests = false; self.extensions.clear(); } } impl Default for ShaderMetadata { fn default() -> Self { ShaderMetadata { version: 0, profile: Profile::Core, stage: ShaderStage::Vertex, workgroup_size: [0; 3], early_fragment_tests: false, extensions: FastHashSet::default(), } } } /// The `Frontend` is the central structure of the GLSL frontend. /// /// To instantiate a new `Frontend` the [`Default`] trait is used, so a /// call to the associated function [`Frontend::default`](Frontend::default) will /// return a new `Frontend` instance. /// /// To parse a shader simply call the [`parse`](Frontend::parse) method with a /// [`Options`] struct and a [`&str`](str) holding the glsl code. /// /// The `Frontend` also provides the [`metadata`](Frontend::metadata) to get some /// further information about the previously parsed shader, like version and /// extensions used (see the documentation for /// [`ShaderMetadata`] to see all the returned information) /// /// # Example usage /// ```rust /// use naga::ShaderStage; /// use naga::front::glsl::{Frontend, Options}; /// /// let glsl = r#" /// #version 450 core /// /// void main() {} /// "#; /// /// let mut frontend = Frontend::default(); /// let options = Options::from(ShaderStage::Vertex); /// frontend.parse(&options, glsl); /// ``` /// /// # Reusability /// /// If there's a need to parse more than one shader reusing the same `Frontend` /// instance may be beneficial since internal allocations will be reused. /// /// Calling the [`parse`](Frontend::parse) method multiple times will reset the /// `Frontend` so no extra care is needed when reusing. #[derive(Debug, Default)] pub struct Frontend { meta: ShaderMetadata, lookup_function: FastHashMap, lookup_type: FastHashMap>, global_variables: Vec<(String, GlobalLookup)>, entry_args: Vec, layouter: Layouter, errors: Vec, } impl Frontend { fn reset(&mut self, stage: ShaderStage) { self.meta.reset(stage); self.lookup_function.clear(); self.lookup_type.clear(); self.global_variables.clear(); self.entry_args.clear(); self.layouter.clear(); } /// Parses a shader either outputting a shader [`Module`] or a list of /// [`Error`]s. /// /// Multiple calls using the same `Frontend` and different shaders are supported. pub fn parse( &mut self, options: &Options, source: &str, ) -> core::result::Result { self.reset(options.stage); let lexer = lex::Lexer::new(source, &options.defines); let mut ctx = ParsingContext::new(lexer); match ctx.parse(self) { Ok(module) => { if self.errors.is_empty() { Ok(module) } else { Err(core::mem::take(&mut self.errors).into()) } } Err(e) => { self.errors.push(e); Err(core::mem::take(&mut self.errors).into()) } } } /// Returns additional information about the parsed shader which might not /// be stored in the [`Module`], see the documentation for /// [`ShaderMetadata`] for more information about the returned data. /// /// # Notes /// /// Following an unsuccessful parsing the state of the returned information /// is undefined, it might contain only partial information about the /// current shader, the previous shader or both. pub const fn metadata(&self) -> &ShaderMetadata { &self.meta } } ================================================ FILE: naga/src/front/glsl/offset.rs ================================================ /*! Module responsible for calculating the offset and span for types. There exists two types of layouts std140 and std430 (there's technically two more layouts, shared and packed. Shared is not supported by spirv. Packed is implementation dependent and for now it's just implemented as an alias to std140). The OpenGl spec (the layout rules are defined by the OpenGl spec in section 7.6.2.2 as opposed to the GLSL spec) uses the term basic machine units which are equivalent to bytes. */ use alloc::vec::Vec; use super::{ ast::StructLayout, error::{Error, ErrorKind}, Span, }; use crate::{proc::Alignment, Handle, Scalar, Type, TypeInner, UniqueArena}; /// Struct with information needed for defining a struct member. /// /// Returned by [`calculate_offset`]. #[derive(Debug)] pub struct TypeAlignSpan { /// The handle to the type, this might be the same handle passed to /// [`calculate_offset`] or a new such a new array type with a different /// stride set. pub ty: Handle, /// The alignment required by the type. pub align: Alignment, /// The size of the type. pub span: u32, } /// Returns the type, alignment and span of a struct member according to a [`StructLayout`]. /// /// The functions returns a [`TypeAlignSpan`] which has a `ty` member this /// should be used as the struct member type because for example arrays may have /// to change the stride and as such need to have a different type. pub fn calculate_offset( mut ty: Handle, meta: Span, layout: StructLayout, types: &mut UniqueArena, errors: &mut Vec, ) -> TypeAlignSpan { // When using the std430 storage layout, shader storage blocks will be laid out in buffer storage // identically to uniform and shader storage blocks using the std140 layout, except // that the base alignment and stride of arrays of scalars and vectors in rule 4 and of // structures in rule 9 are not rounded up a multiple of the base alignment of a vec4. let (align, span) = match types[ty].inner { // 1. If the member is a scalar consuming N basic machine units, // the base alignment is N. TypeInner::Scalar(Scalar { width, .. }) => (Alignment::from_width(width), width as u32), // 2. If the member is a two- or four-component vector with components // consuming N basic machine units, the base alignment is 2N or 4N, respectively. // 3. If the member is a three-component vector with components consuming N // basic machine units, the base alignment is 4N. TypeInner::Vector { size, scalar: Scalar { width, .. }, } => ( Alignment::from(size) * Alignment::from_width(width), size as u32 * width as u32, ), // 4. If the member is an array of scalars or vectors, the base alignment and array // stride are set to match the base alignment of a single array element, according // to rules (1), (2), and (3), and rounded up to the base alignment of a vec4. // TODO: Matrices array TypeInner::Array { base, size, .. } => { let info = calculate_offset(base, meta, layout, types, errors); let name = types[ty].name.clone(); // See comment at the beginning of the function let (align, stride) = if StructLayout::Std430 == layout { (info.align, info.align.round_up(info.span)) } else { let align = info.align.max(Alignment::MIN_UNIFORM); (align, align.round_up(info.span)) }; let span = match size { crate::ArraySize::Constant(size) => size.get() * stride, crate::ArraySize::Pending(_) => unreachable!(), crate::ArraySize::Dynamic => stride, }; let ty_span = types.get_span(ty); ty = types.insert( Type { name, inner: TypeInner::Array { base: info.ty, size, stride, }, }, ty_span, ); (align, span) } // 5. If the member is a column-major matrix with C columns and R rows, the // matrix is stored identically to an array of C column vectors with R // components each, according to rule (4) // TODO: Row major matrices TypeInner::Matrix { columns, rows, scalar, } => { let mut align = Alignment::from(rows) * Alignment::from_width(scalar.width); // See comment at the beginning of the function if StructLayout::Std430 != layout { align = align.max(Alignment::MIN_UNIFORM); } // See comment on the error kind if StructLayout::Std140 == layout { // Do the f16 test first, as it's more specific if scalar == Scalar::F16 { errors.push(Error { kind: ErrorKind::UnsupportedF16MatrixInStd140 { columns: columns as u8, rows: rows as u8, }, meta, }); } if rows == crate::VectorSize::Bi { errors.push(Error { kind: ErrorKind::UnsupportedMatrixWithTwoRowsInStd140 { columns: columns as u8, }, meta, }); } } (align, align * columns as u32) } TypeInner::Struct { ref members, .. } => { let mut span = 0; let mut align = Alignment::ONE; let mut members = members.clone(); let name = types[ty].name.clone(); for member in members.iter_mut() { let info = calculate_offset(member.ty, meta, layout, types, errors); let member_alignment = info.align; span = member_alignment.round_up(span); align = member_alignment.max(align); member.ty = info.ty; member.offset = span; span += info.span; } span = align.round_up(span); let ty_span = types.get_span(ty); ty = types.insert( Type { name, inner: TypeInner::Struct { members, span }, }, ty_span, ); (align, span) } _ => { errors.push(Error { kind: ErrorKind::SemanticError("Invalid struct member type".into()), meta, }); (Alignment::ONE, 0) } }; TypeAlignSpan { ty, align, span } } ================================================ FILE: naga/src/front/glsl/parser/declarations.rs ================================================ use alloc::{string::String, vec, vec::Vec}; use super::{DeclarationContext, ParsingContext, Result}; use crate::{ front::glsl::{ ast::{ GlobalLookup, GlobalLookupKind, Precision, QualifierKey, QualifierValue, StorageQualifier, StructLayout, TypeQualifiers, }, context::{Context, ExprPos}, error::ExpectedToken, offset, token::{Token, TokenValue}, types::scalar_components, variables::{GlobalOrConstant, VarDeclaration}, Error, ErrorKind, Frontend, Span, }, proc::Alignment, AddressSpace, Expression, FunctionResult, Handle, Scalar, ScalarKind, Statement, StructMember, Type, TypeInner, }; /// Helper method used to retrieve the child type of `ty` at /// index `i`. /// /// # Note /// /// Does not check if the index is valid and returns the same type /// when indexing out-of-bounds a struct or indexing a non indexable /// type. fn element_or_member_type( ty: Handle, i: usize, types: &mut crate::UniqueArena, ) -> Handle { match types[ty].inner { // The child type of a vector is a scalar of the same kind and width TypeInner::Vector { scalar, .. } => types.insert( Type { name: None, inner: TypeInner::Scalar(scalar), }, Default::default(), ), // The child type of a matrix is a vector of floats with the same // width and the size of the matrix rows. TypeInner::Matrix { rows, scalar, .. } => types.insert( Type { name: None, inner: TypeInner::Vector { size: rows, scalar }, }, Default::default(), ), // The child type of an array is the base type of the array TypeInner::Array { base, .. } => base, // The child type of a struct at index `i` is the type of it's // member at that same index. // // In case the index is out of bounds the same type is returned TypeInner::Struct { ref members, .. } => { members.get(i).map(|member| member.ty).unwrap_or(ty) } // The type isn't indexable, the same type is returned _ => ty, } } impl ParsingContext<'_> { pub fn parse_external_declaration( &mut self, frontend: &mut Frontend, global_ctx: &mut Context, ) -> Result<()> { if self .parse_declaration(frontend, global_ctx, true, false)? .is_none() { let token = self.bump(frontend)?; match token.value { TokenValue::Semicolon if frontend.meta.version == 460 => Ok(()), _ => { let expected = match frontend.meta.version { 460 => vec![TokenValue::Semicolon.into(), ExpectedToken::Eof], _ => vec![ExpectedToken::Eof], }; Err(Error { kind: ErrorKind::InvalidToken(token.value, expected), meta: token.meta, }) } } } else { Ok(()) } } pub fn parse_initializer( &mut self, frontend: &mut Frontend, ty: Handle, ctx: &mut Context, ) -> Result<(Handle, Span)> { // initializer: // assignment_expression // LEFT_BRACE initializer_list RIGHT_BRACE // LEFT_BRACE initializer_list COMMA RIGHT_BRACE // // initializer_list: // initializer // initializer_list COMMA initializer if let Some(Token { mut meta, .. }) = self.bump_if(frontend, TokenValue::LeftBrace) { // initializer_list let mut components = Vec::new(); loop { // The type expected to be parsed inside the initializer list let new_ty = element_or_member_type(ty, components.len(), &mut ctx.module.types); components.push(self.parse_initializer(frontend, new_ty, ctx)?.0); let token = self.bump(frontend)?; match token.value { TokenValue::Comma => { if let Some(Token { meta: end_meta, .. }) = self.bump_if(frontend, TokenValue::RightBrace) { meta.subsume(end_meta); break; } } TokenValue::RightBrace => { meta.subsume(token.meta); break; } _ => { return Err(Error { kind: ErrorKind::InvalidToken( token.value, vec![TokenValue::Comma.into(), TokenValue::RightBrace.into()], ), meta: token.meta, }) } } } Ok(( ctx.add_expression(Expression::Compose { ty, components }, meta)?, meta, )) } else { let mut stmt = ctx.stmt_ctx(); let expr = self.parse_assignment(frontend, ctx, &mut stmt)?; let (mut init, init_meta) = ctx.lower_expect(stmt, frontend, expr, ExprPos::Rhs)?; let scalar_components = scalar_components(&ctx.module.types[ty].inner); if let Some(scalar) = scalar_components { ctx.implicit_conversion(&mut init, init_meta, scalar)?; } Ok((init, init_meta)) } } // Note: caller preparsed the type and qualifiers // Note: caller skips this if the fallthrough token is not expected to be consumed here so this // produced Error::InvalidToken if it isn't consumed pub fn parse_init_declarator_list( &mut self, frontend: &mut Frontend, mut ty: Handle, ctx: &mut DeclarationContext, ) -> Result<()> { // init_declarator_list: // single_declaration // init_declarator_list COMMA IDENTIFIER // init_declarator_list COMMA IDENTIFIER array_specifier // init_declarator_list COMMA IDENTIFIER array_specifier EQUAL initializer // init_declarator_list COMMA IDENTIFIER EQUAL initializer // // single_declaration: // fully_specified_type // fully_specified_type IDENTIFIER // fully_specified_type IDENTIFIER array_specifier // fully_specified_type IDENTIFIER array_specifier EQUAL initializer // fully_specified_type IDENTIFIER EQUAL initializer // Consume any leading comma, e.g. this is valid: `float, a=1;` if self .peek(frontend) .is_some_and(|t| t.value == TokenValue::Comma) { self.next(frontend); } loop { let token = self.bump(frontend)?; let name = match token.value { TokenValue::Semicolon => break, TokenValue::Identifier(name) => name, _ => { return Err(Error { kind: ErrorKind::InvalidToken( token.value, vec![ExpectedToken::Identifier, TokenValue::Semicolon.into()], ), meta: token.meta, }) } }; let mut meta = token.meta; // array_specifier // array_specifier EQUAL initializer // EQUAL initializer // parse an array specifier if it exists // NOTE: unlike other parse methods this one doesn't expect an array specifier and // returns Ok(None) rather than an error if there is not one self.parse_array_specifier(frontend, ctx.ctx, &mut meta, &mut ty)?; let is_global_const = ctx.qualifiers.storage.0 == StorageQualifier::Const && ctx.external; let init = self .bump_if(frontend, TokenValue::Assign) .map::, _>(|_| { let prev_const = ctx.ctx.is_const; ctx.ctx.is_const = is_global_const; let (mut expr, init_meta) = self.parse_initializer(frontend, ty, ctx.ctx)?; let scalar_components = scalar_components(&ctx.ctx.module.types[ty].inner); if let Some(scalar) = scalar_components { ctx.ctx.implicit_conversion(&mut expr, init_meta, scalar)?; } ctx.ctx.is_const = prev_const; meta.subsume(init_meta); Ok(expr) }) .transpose()?; let decl_initializer; let late_initializer; if is_global_const { decl_initializer = init; late_initializer = None; } else if ctx.external { decl_initializer = init.and_then(|expr| ctx.ctx.lift_up_const_expression(expr).ok()); late_initializer = None; } else if let Some(init) = init { if ctx.is_inside_loop || !ctx.ctx.local_expression_kind_tracker.is_const(init) { decl_initializer = None; late_initializer = Some(init); } else { decl_initializer = Some(init); late_initializer = None; } } else { decl_initializer = None; late_initializer = None; }; let pointer = ctx.add_var(frontend, ty, name, decl_initializer, meta)?; if let Some(value) = late_initializer { ctx.ctx.emit_restart(); ctx.ctx.body.push(Statement::Store { pointer, value }, meta); } let token = self.bump(frontend)?; match token.value { TokenValue::Semicolon => break, TokenValue::Comma => {} _ => { return Err(Error { kind: ErrorKind::InvalidToken( token.value, vec![TokenValue::Comma.into(), TokenValue::Semicolon.into()], ), meta: token.meta, }) } } } Ok(()) } /// `external` whether or not we are in a global or local context pub fn parse_declaration( &mut self, frontend: &mut Frontend, ctx: &mut Context, external: bool, is_inside_loop: bool, ) -> Result> { //declaration: // function_prototype SEMICOLON // // init_declarator_list SEMICOLON // PRECISION precision_qualifier type_specifier SEMICOLON // // type_qualifier IDENTIFIER LEFT_BRACE struct_declaration_list RIGHT_BRACE SEMICOLON // type_qualifier IDENTIFIER LEFT_BRACE struct_declaration_list RIGHT_BRACE IDENTIFIER SEMICOLON // type_qualifier IDENTIFIER LEFT_BRACE struct_declaration_list RIGHT_BRACE IDENTIFIER array_specifier SEMICOLON // type_qualifier SEMICOLON type_qualifier IDENTIFIER SEMICOLON // type_qualifier IDENTIFIER identifier_list SEMICOLON if self.peek_type_qualifier(frontend) || self.peek_type_name(frontend) { let mut qualifiers = self.parse_type_qualifiers(frontend, ctx)?; if self.peek_type_name(frontend) { // This branch handles variables and function prototypes and if // external is true also function definitions let (ty, mut meta) = self.parse_type(frontend, ctx)?; let token = self.bump(frontend)?; let token_fallthrough = match token.value { TokenValue::Identifier(name) => match self.expect_peek(frontend)?.value { TokenValue::LeftParen => { // This branch handles function definition and prototypes self.bump(frontend)?; let result = ty.map(|ty| FunctionResult { ty, binding: None }); let mut context = Context::new( frontend, ctx.module, false, ctx.global_expression_kind_tracker, )?; self.parse_function_args(frontend, &mut context)?; let end_meta = self.expect(frontend, TokenValue::RightParen)?.meta; meta.subsume(end_meta); let token = self.bump(frontend)?; return match token.value { TokenValue::Semicolon => { // This branch handles function prototypes frontend.add_prototype(context, name, result, meta); Ok(Some(meta)) } TokenValue::LeftBrace if external => { // This branch handles function definitions // as you can see by the guard this branch // only happens if external is also true // parse the body self.parse_compound_statement( token.meta, frontend, &mut context, &mut None, false, )?; frontend.add_function(context, name, result, meta); Ok(Some(meta)) } _ if external => Err(Error { kind: ErrorKind::InvalidToken( token.value, vec![ TokenValue::LeftBrace.into(), TokenValue::Semicolon.into(), ], ), meta: token.meta, }), _ => Err(Error { kind: ErrorKind::InvalidToken( token.value, vec![TokenValue::Semicolon.into()], ), meta: token.meta, }), }; } // Pass the token to the init_declarator_list parser _ => Token { value: TokenValue::Identifier(name), meta: token.meta, }, }, // Pass the token to the init_declarator_list parser _ => token, }; // If program execution has reached here then this will be a // init_declarator_list // token_fallthrough will have a token that was already bumped if let Some(ty) = ty { let mut ctx = DeclarationContext { qualifiers, external, is_inside_loop, ctx, }; self.backtrack(token_fallthrough)?; self.parse_init_declarator_list(frontend, ty, &mut ctx)?; } else { frontend.errors.push(Error { kind: ErrorKind::SemanticError("Declaration cannot have void type".into()), meta, }) } Ok(Some(meta)) } else { // This branch handles struct definitions and modifiers like // ```glsl // layout(early_fragment_tests); // ``` let token = self.bump(frontend)?; match token.value { TokenValue::Identifier(ty_name) => { if self.bump_if(frontend, TokenValue::LeftBrace).is_some() { self.parse_block_declaration( frontend, ctx, &mut qualifiers, ty_name, token.meta, ) .map(Some) } else { if qualifiers.invariant.take().is_some() { frontend.make_variable_invariant(ctx, &ty_name, token.meta)?; qualifiers.unused_errors(&mut frontend.errors); self.expect(frontend, TokenValue::Semicolon)?; return Ok(Some(qualifiers.span)); } //TODO: declaration // type_qualifier IDENTIFIER SEMICOLON // type_qualifier IDENTIFIER identifier_list SEMICOLON Err(Error { kind: ErrorKind::NotImplemented("variable qualifier"), meta: token.meta, }) } } TokenValue::Semicolon => { if let Some(value) = qualifiers.uint_layout_qualifier("local_size_x", &mut frontend.errors) { frontend.meta.workgroup_size[0] = value; } if let Some(value) = qualifiers.uint_layout_qualifier("local_size_y", &mut frontend.errors) { frontend.meta.workgroup_size[1] = value; } if let Some(value) = qualifiers.uint_layout_qualifier("local_size_z", &mut frontend.errors) { frontend.meta.workgroup_size[2] = value; } frontend.meta.early_fragment_tests |= qualifiers .none_layout_qualifier("early_fragment_tests", &mut frontend.errors); qualifiers.unused_errors(&mut frontend.errors); Ok(Some(qualifiers.span)) } _ => Err(Error { kind: ErrorKind::InvalidToken( token.value, vec![ExpectedToken::Identifier, TokenValue::Semicolon.into()], ), meta: token.meta, }), } } } else { match self.peek(frontend).map(|t| &t.value) { Some(&TokenValue::Precision) => { // PRECISION precision_qualifier type_specifier SEMICOLON self.bump(frontend)?; let token = self.bump(frontend)?; let _ = match token.value { TokenValue::PrecisionQualifier(p) => p, _ => { return Err(Error { kind: ErrorKind::InvalidToken( token.value, vec![ TokenValue::PrecisionQualifier(Precision::High).into(), TokenValue::PrecisionQualifier(Precision::Medium).into(), TokenValue::PrecisionQualifier(Precision::Low).into(), ], ), meta: token.meta, }) } }; let (ty, meta) = self.parse_type_non_void(frontend, ctx)?; match ctx.module.types[ty].inner { TypeInner::Scalar(Scalar { kind: ScalarKind::Float | ScalarKind::Sint, .. }) => {} _ => frontend.errors.push(Error { kind: ErrorKind::SemanticError( "Precision statement can only work on floats and ints".into(), ), meta, }), } self.expect(frontend, TokenValue::Semicolon)?; Ok(Some(meta)) } _ => Ok(None), } } } pub fn parse_block_declaration( &mut self, frontend: &mut Frontend, ctx: &mut Context, qualifiers: &mut TypeQualifiers, ty_name: String, mut meta: Span, ) -> Result { let layout = match qualifiers.layout_qualifiers.remove(&QualifierKey::Layout) { Some((QualifierValue::Layout(l), _)) => l, None => { if let StorageQualifier::AddressSpace(AddressSpace::Storage { .. }) = qualifiers.storage.0 { StructLayout::Std430 } else { StructLayout::Std140 } } _ => unreachable!(), }; let mut members = Vec::new(); let span = self.parse_struct_declaration_list(frontend, ctx, &mut members, layout)?; self.expect(frontend, TokenValue::RightBrace)?; let mut ty = ctx.module.types.insert( Type { name: Some(ty_name), inner: TypeInner::Struct { members: members.clone(), span, }, }, Default::default(), ); let token = self.bump(frontend)?; let name = match token.value { TokenValue::Semicolon => None, TokenValue::Identifier(name) => { self.parse_array_specifier(frontend, ctx, &mut meta, &mut ty)?; self.expect(frontend, TokenValue::Semicolon)?; Some(name) } _ => { return Err(Error { kind: ErrorKind::InvalidToken( token.value, vec![ExpectedToken::Identifier, TokenValue::Semicolon.into()], ), meta: token.meta, }) } }; let global = frontend.add_global_var( ctx, VarDeclaration { qualifiers, ty, name, init: None, meta, }, )?; for (i, k, ty) in members.into_iter().enumerate().filter_map(|(i, m)| { let ty = m.ty; m.name.map(|s| (i as u32, s, ty)) }) { let lookup = GlobalLookup { kind: match global { GlobalOrConstant::Global(handle) => GlobalLookupKind::BlockSelect(handle, i), GlobalOrConstant::Constant(handle) => GlobalLookupKind::Constant(handle, ty), GlobalOrConstant::Override(handle) => GlobalLookupKind::Override(handle, ty), }, entry_arg: None, mutable: true, }; ctx.add_global(&k, lookup)?; frontend.global_variables.push((k, lookup)); } Ok(meta) } // TODO: Accept layout arguments pub fn parse_struct_declaration_list( &mut self, frontend: &mut Frontend, ctx: &mut Context, members: &mut Vec, layout: StructLayout, ) -> Result { let mut span = 0; let mut align = Alignment::ONE; loop { // TODO: type_qualifier let (base_ty, mut meta) = self.parse_type_non_void(frontend, ctx)?; loop { let (name, name_meta) = self.expect_ident(frontend)?; let mut ty = base_ty; self.parse_array_specifier(frontend, ctx, &mut meta, &mut ty)?; meta.subsume(name_meta); let info = offset::calculate_offset( ty, meta, layout, &mut ctx.module.types, &mut frontend.errors, ); let member_alignment = info.align; span = member_alignment.round_up(span); align = member_alignment.max(align); members.push(StructMember { name: Some(name), ty: info.ty, binding: None, offset: span, }); span += info.span; if self.bump_if(frontend, TokenValue::Comma).is_none() { break; } } self.expect(frontend, TokenValue::Semicolon)?; if let TokenValue::RightBrace = self.expect_peek(frontend)?.value { break; } } span = align.round_up(span); Ok(span) } } ================================================ FILE: naga/src/front/glsl/parser/expressions.rs ================================================ use alloc::{vec, vec::Vec}; use core::num::NonZeroU32; use crate::{ front::glsl::{ ast::{FunctionCall, FunctionCallKind, HirExpr, HirExprKind}, context::{Context, StmtContext}, error::{ErrorKind, ExpectedToken}, parser::ParsingContext, token::{Token, TokenValue}, Error, Frontend, Result, Span, }, ArraySize, BinaryOperator, Handle, Literal, Type, TypeInner, UnaryOperator, }; impl ParsingContext<'_> { pub fn parse_primary( &mut self, frontend: &mut Frontend, ctx: &mut Context, stmt: &mut StmtContext, ) -> Result> { let mut token = self.bump(frontend)?; let literal = match token.value { TokenValue::IntConstant(int) => { if int.width != 32 { frontend.errors.push(Error { kind: ErrorKind::SemanticError("Unsupported non-32bit integer".into()), meta: token.meta, }); } if int.signed { Literal::I32(int.value as i32) } else { Literal::U32(int.value as u32) } } TokenValue::FloatConstant(float) => { if float.width != 32 { frontend.errors.push(Error { kind: ErrorKind::SemanticError( concat!( "Unsupported floating-point value ", "(expected single-precision floating-point number)" ) .into(), ), meta: token.meta, }); } Literal::F32(float.value) } TokenValue::BoolConstant(value) => Literal::Bool(value), TokenValue::LeftParen => { let expr = self.parse_expression(frontend, ctx, stmt)?; let meta = self.expect(frontend, TokenValue::RightParen)?.meta; token.meta.subsume(meta); return Ok(expr); } _ => { return Err(Error { kind: ErrorKind::InvalidToken( token.value, vec![ TokenValue::LeftParen.into(), ExpectedToken::IntLiteral, ExpectedToken::FloatLiteral, ExpectedToken::BoolLiteral, ], ), meta: token.meta, }); } }; Ok(stmt.hir_exprs.append( HirExpr { kind: HirExprKind::Literal(literal), meta: token.meta, }, Default::default(), )) } pub fn parse_function_call_args( &mut self, frontend: &mut Frontend, ctx: &mut Context, stmt: &mut StmtContext, meta: &mut Span, ) -> Result>> { let mut args = Vec::new(); if let Some(token) = self.bump_if(frontend, TokenValue::RightParen) { meta.subsume(token.meta); } else { loop { args.push(self.parse_assignment(frontend, ctx, stmt)?); let token = self.bump(frontend)?; match token.value { TokenValue::Comma => {} TokenValue::RightParen => { meta.subsume(token.meta); break; } _ => { return Err(Error { kind: ErrorKind::InvalidToken( token.value, vec![TokenValue::Comma.into(), TokenValue::RightParen.into()], ), meta: token.meta, }); } } } } Ok(args) } pub fn parse_postfix( &mut self, frontend: &mut Frontend, ctx: &mut Context, stmt: &mut StmtContext, ) -> Result> { let mut base = if self.peek_type_name(frontend) { let (mut handle, mut meta) = self.parse_type_non_void(frontend, ctx)?; self.expect(frontend, TokenValue::LeftParen)?; let args = self.parse_function_call_args(frontend, ctx, stmt, &mut meta)?; if let TypeInner::Array { size: ArraySize::Dynamic, stride, base, } = ctx.module.types[handle].inner { let span = ctx.module.types.get_span(handle); let size = u32::try_from(args.len()) .ok() .and_then(NonZeroU32::new) .ok_or(Error { kind: ErrorKind::SemanticError( "There must be at least one argument".into(), ), meta, })?; handle = ctx.module.types.insert( Type { name: None, inner: TypeInner::Array { stride, base, size: ArraySize::Constant(size), }, }, span, ) } stmt.hir_exprs.append( HirExpr { kind: HirExprKind::Call(FunctionCall { kind: FunctionCallKind::TypeConstructor(handle), args, }), meta, }, Default::default(), ) } else if let TokenValue::Identifier(_) = self.expect_peek(frontend)?.value { let (name, mut meta) = self.expect_ident(frontend)?; let expr = if self.bump_if(frontend, TokenValue::LeftParen).is_some() { let args = self.parse_function_call_args(frontend, ctx, stmt, &mut meta)?; let kind = match frontend.lookup_type.get(&name) { Some(ty) => FunctionCallKind::TypeConstructor(*ty), None => FunctionCallKind::Function(name), }; HirExpr { kind: HirExprKind::Call(FunctionCall { kind, args }), meta, } } else { let var = match frontend.lookup_variable(ctx, &name, meta)? { Some(var) => var, None => { return Err(Error { kind: ErrorKind::UnknownVariable(name), meta, }) } }; HirExpr { kind: HirExprKind::Variable(var), meta, } }; stmt.hir_exprs.append(expr, Default::default()) } else { self.parse_primary(frontend, ctx, stmt)? }; while let TokenValue::LeftBracket | TokenValue::Dot | TokenValue::Increment | TokenValue::Decrement = self.expect_peek(frontend)?.value { let Token { value, mut meta } = self.bump(frontend)?; match value { TokenValue::LeftBracket => { let index = self.parse_expression(frontend, ctx, stmt)?; let end_meta = self.expect(frontend, TokenValue::RightBracket)?.meta; meta.subsume(end_meta); base = stmt.hir_exprs.append( HirExpr { kind: HirExprKind::Access { base, index }, meta, }, Default::default(), ) } TokenValue::Dot => { let (field, end_meta) = self.expect_ident(frontend)?; if self.bump_if(frontend, TokenValue::LeftParen).is_some() { let args = self.parse_function_call_args(frontend, ctx, stmt, &mut meta)?; base = stmt.hir_exprs.append( HirExpr { kind: HirExprKind::Method { expr: base, name: field, args, }, meta, }, Default::default(), ); continue; } meta.subsume(end_meta); base = stmt.hir_exprs.append( HirExpr { kind: HirExprKind::Select { base, field }, meta, }, Default::default(), ) } TokenValue::Increment | TokenValue::Decrement => { base = stmt.hir_exprs.append( HirExpr { kind: HirExprKind::PrePostfix { op: match value { TokenValue::Increment => BinaryOperator::Add, _ => BinaryOperator::Subtract, }, postfix: true, expr: base, }, meta, }, Default::default(), ) } _ => unreachable!(), } } Ok(base) } pub fn parse_unary( &mut self, frontend: &mut Frontend, ctx: &mut Context, stmt: &mut StmtContext, ) -> Result> { Ok(match self.expect_peek(frontend)?.value { TokenValue::Plus | TokenValue::Dash | TokenValue::Bang | TokenValue::Tilde => { let Token { value, mut meta } = self.bump(frontend)?; let expr = self.parse_unary(frontend, ctx, stmt)?; let end_meta = stmt.hir_exprs[expr].meta; let kind = match value { TokenValue::Dash => HirExprKind::Unary { op: UnaryOperator::Negate, expr, }, TokenValue::Bang => HirExprKind::Unary { op: UnaryOperator::LogicalNot, expr, }, TokenValue::Tilde => HirExprKind::Unary { op: UnaryOperator::BitwiseNot, expr, }, _ => return Ok(expr), }; meta.subsume(end_meta); stmt.hir_exprs .append(HirExpr { kind, meta }, Default::default()) } TokenValue::Increment | TokenValue::Decrement => { let Token { value, meta } = self.bump(frontend)?; let expr = self.parse_unary(frontend, ctx, stmt)?; stmt.hir_exprs.append( HirExpr { kind: HirExprKind::PrePostfix { op: match value { TokenValue::Increment => BinaryOperator::Add, _ => BinaryOperator::Subtract, }, postfix: false, expr, }, meta, }, Default::default(), ) } _ => self.parse_postfix(frontend, ctx, stmt)?, }) } pub fn parse_binary( &mut self, frontend: &mut Frontend, ctx: &mut Context, stmt: &mut StmtContext, passthrough: Option>, min_bp: u8, ) -> Result> { let mut left = passthrough .ok_or(ErrorKind::EndOfFile /* Dummy error */) .or_else(|_| self.parse_unary(frontend, ctx, stmt))?; let mut meta = stmt.hir_exprs[left].meta; while let Some((l_bp, r_bp)) = binding_power(&self.expect_peek(frontend)?.value) { if l_bp < min_bp { break; } let Token { value, .. } = self.bump(frontend)?; let right = self.parse_binary(frontend, ctx, stmt, None, r_bp)?; let end_meta = stmt.hir_exprs[right].meta; meta.subsume(end_meta); left = stmt.hir_exprs.append( HirExpr { kind: HirExprKind::Binary { left, op: match value { TokenValue::LogicalOr => BinaryOperator::LogicalOr, TokenValue::LogicalXor => BinaryOperator::NotEqual, TokenValue::LogicalAnd => BinaryOperator::LogicalAnd, TokenValue::VerticalBar => BinaryOperator::InclusiveOr, TokenValue::Caret => BinaryOperator::ExclusiveOr, TokenValue::Ampersand => BinaryOperator::And, TokenValue::Equal => BinaryOperator::Equal, TokenValue::NotEqual => BinaryOperator::NotEqual, TokenValue::GreaterEqual => BinaryOperator::GreaterEqual, TokenValue::LessEqual => BinaryOperator::LessEqual, TokenValue::LeftAngle => BinaryOperator::Less, TokenValue::RightAngle => BinaryOperator::Greater, TokenValue::LeftShift => BinaryOperator::ShiftLeft, TokenValue::RightShift => BinaryOperator::ShiftRight, TokenValue::Plus => BinaryOperator::Add, TokenValue::Dash => BinaryOperator::Subtract, TokenValue::Star => BinaryOperator::Multiply, TokenValue::Slash => BinaryOperator::Divide, TokenValue::Percent => BinaryOperator::Modulo, _ => unreachable!(), }, right, }, meta, }, Default::default(), ) } Ok(left) } pub fn parse_conditional( &mut self, frontend: &mut Frontend, ctx: &mut Context, stmt: &mut StmtContext, passthrough: Option>, ) -> Result> { let mut condition = self.parse_binary(frontend, ctx, stmt, passthrough, 0)?; let mut meta = stmt.hir_exprs[condition].meta; if self.bump_if(frontend, TokenValue::Question).is_some() { let accept = self.parse_expression(frontend, ctx, stmt)?; self.expect(frontend, TokenValue::Colon)?; let reject = self.parse_assignment(frontend, ctx, stmt)?; let end_meta = stmt.hir_exprs[reject].meta; meta.subsume(end_meta); condition = stmt.hir_exprs.append( HirExpr { kind: HirExprKind::Conditional { condition, accept, reject, }, meta, }, Default::default(), ) } Ok(condition) } pub fn parse_assignment( &mut self, frontend: &mut Frontend, ctx: &mut Context, stmt: &mut StmtContext, ) -> Result> { let tgt = self.parse_unary(frontend, ctx, stmt)?; let mut meta = stmt.hir_exprs[tgt].meta; Ok(match self.expect_peek(frontend)?.value { TokenValue::Assign => { self.bump(frontend)?; let value = self.parse_assignment(frontend, ctx, stmt)?; let end_meta = stmt.hir_exprs[value].meta; meta.subsume(end_meta); stmt.hir_exprs.append( HirExpr { kind: HirExprKind::Assign { tgt, value }, meta, }, Default::default(), ) } TokenValue::OrAssign | TokenValue::AndAssign | TokenValue::AddAssign | TokenValue::DivAssign | TokenValue::ModAssign | TokenValue::SubAssign | TokenValue::MulAssign | TokenValue::LeftShiftAssign | TokenValue::RightShiftAssign | TokenValue::XorAssign => { let token = self.bump(frontend)?; let right = self.parse_assignment(frontend, ctx, stmt)?; let end_meta = stmt.hir_exprs[right].meta; meta.subsume(end_meta); let value = stmt.hir_exprs.append( HirExpr { meta, kind: HirExprKind::Binary { left: tgt, op: match token.value { TokenValue::OrAssign => BinaryOperator::InclusiveOr, TokenValue::AndAssign => BinaryOperator::And, TokenValue::AddAssign => BinaryOperator::Add, TokenValue::DivAssign => BinaryOperator::Divide, TokenValue::ModAssign => BinaryOperator::Modulo, TokenValue::SubAssign => BinaryOperator::Subtract, TokenValue::MulAssign => BinaryOperator::Multiply, TokenValue::LeftShiftAssign => BinaryOperator::ShiftLeft, TokenValue::RightShiftAssign => BinaryOperator::ShiftRight, TokenValue::XorAssign => BinaryOperator::ExclusiveOr, _ => unreachable!(), }, right, }, }, Default::default(), ); stmt.hir_exprs.append( HirExpr { kind: HirExprKind::Assign { tgt, value }, meta, }, Default::default(), ) } _ => self.parse_conditional(frontend, ctx, stmt, Some(tgt))?, }) } pub fn parse_expression( &mut self, frontend: &mut Frontend, ctx: &mut Context, stmt: &mut StmtContext, ) -> Result> { let mut exprs = Vec::new(); let mut expr = self.parse_assignment(frontend, ctx, stmt)?; exprs.push(expr); while let TokenValue::Comma = self.expect_peek(frontend)?.value { self.bump(frontend)?; expr = self.parse_assignment(frontend, ctx, stmt)?; exprs.push(expr); } if exprs.len() == 1 { Ok(expr) } else { let mut meta = stmt.hir_exprs[exprs[0]].meta; for &e in &exprs[1..] { meta.subsume(stmt.hir_exprs[e].meta); } expr = stmt.hir_exprs.append( HirExpr { kind: HirExprKind::Sequence { exprs }, meta, }, Default::default(), ); Ok(expr) } } } const fn binding_power(value: &TokenValue) -> Option<(u8, u8)> { Some(match *value { TokenValue::LogicalOr => (1, 2), TokenValue::LogicalXor => (3, 4), TokenValue::LogicalAnd => (5, 6), TokenValue::VerticalBar => (7, 8), TokenValue::Caret => (9, 10), TokenValue::Ampersand => (11, 12), TokenValue::Equal | TokenValue::NotEqual => (13, 14), TokenValue::GreaterEqual | TokenValue::LessEqual | TokenValue::LeftAngle | TokenValue::RightAngle => (15, 16), TokenValue::LeftShift | TokenValue::RightShift => (17, 18), TokenValue::Plus | TokenValue::Dash => (19, 20), TokenValue::Star | TokenValue::Slash | TokenValue::Percent => (21, 22), _ => return None, }) } ================================================ FILE: naga/src/front/glsl/parser/functions.rs ================================================ use alloc::{vec, vec::Vec}; use crate::front::glsl::context::ExprPos; use crate::front::glsl::Span; use crate::Literal; use crate::{ front::glsl::{ ast::ParameterQualifier, context::Context, parser::ParsingContext, token::{Token, TokenValue}, variables::VarDeclaration, Error, ErrorKind, Frontend, Result, }, Block, Expression, Statement, SwitchCase, UnaryOperator, }; impl ParsingContext<'_> { pub fn peek_parameter_qualifier(&mut self, frontend: &mut Frontend) -> bool { self.peek(frontend).is_some_and(|t| match t.value { TokenValue::In | TokenValue::Out | TokenValue::InOut | TokenValue::Const => true, _ => false, }) } /// Returns the parsed `ParameterQualifier` or `ParameterQualifier::In` pub fn parse_parameter_qualifier(&mut self, frontend: &mut Frontend) -> ParameterQualifier { if self.peek_parameter_qualifier(frontend) { match self.bump(frontend).unwrap().value { TokenValue::In => ParameterQualifier::In, TokenValue::Out => ParameterQualifier::Out, TokenValue::InOut => ParameterQualifier::InOut, TokenValue::Const => ParameterQualifier::Const, _ => unreachable!(), } } else { ParameterQualifier::In } } pub fn parse_statement( &mut self, frontend: &mut Frontend, ctx: &mut Context, terminator: &mut Option, is_inside_loop: bool, ) -> Result> { // Type qualifiers always identify a declaration statement if self.peek_type_qualifier(frontend) { return self.parse_declaration(frontend, ctx, false, is_inside_loop); } // Type names can identify either declaration statements or type constructors // depending on whether the token following the type name is a `(` (LeftParen) if self.peek_type_name(frontend) { // Start by consuming the type name so that we can peek the token after it let token = self.bump(frontend)?; // Peek the next token and check if it's a `(` (LeftParen) if so the statement // is a constructor, otherwise it's a declaration. We need to do the check // beforehand and not in the if since we will backtrack before the if let declaration = TokenValue::LeftParen != self.expect_peek(frontend)?.value; self.backtrack(token)?; if declaration { return self.parse_declaration(frontend, ctx, false, is_inside_loop); } } let new_break = || { let mut block = Block::new(); block.push(Statement::Break, Span::default()); block }; let &Token { ref value, mut meta, } = self.expect_peek(frontend)?; let meta_rest = match *value { TokenValue::Continue => { let meta = self.bump(frontend)?.meta; ctx.body.push(Statement::Continue, meta); terminator.get_or_insert(ctx.body.len()); self.expect(frontend, TokenValue::Semicolon)?.meta } TokenValue::Break => { let meta = self.bump(frontend)?.meta; ctx.body.push(Statement::Break, meta); terminator.get_or_insert(ctx.body.len()); self.expect(frontend, TokenValue::Semicolon)?.meta } TokenValue::Return => { self.bump(frontend)?; let (value, meta) = match self.expect_peek(frontend)?.value { TokenValue::Semicolon => (None, self.bump(frontend)?.meta), _ => { // TODO: Implicit conversions let mut stmt = ctx.stmt_ctx(); let expr = self.parse_expression(frontend, ctx, &mut stmt)?; self.expect(frontend, TokenValue::Semicolon)?; let (handle, meta) = ctx.lower_expect(stmt, frontend, expr, ExprPos::Rhs)?; (Some(handle), meta) } }; ctx.emit_restart(); ctx.body.push(Statement::Return { value }, meta); terminator.get_or_insert(ctx.body.len()); meta } TokenValue::Discard => { let meta = self.bump(frontend)?.meta; ctx.body.push(Statement::Kill, meta); terminator.get_or_insert(ctx.body.len()); self.expect(frontend, TokenValue::Semicolon)?.meta } TokenValue::If => { let mut meta = self.bump(frontend)?.meta; self.expect(frontend, TokenValue::LeftParen)?; let condition = { let mut stmt = ctx.stmt_ctx(); let expr = self.parse_expression(frontend, ctx, &mut stmt)?; let (handle, more_meta) = ctx.lower_expect(stmt, frontend, expr, ExprPos::Rhs)?; meta.subsume(more_meta); handle }; self.expect(frontend, TokenValue::RightParen)?; let accept = ctx.new_body(|ctx| { if let Some(more_meta) = self.parse_statement(frontend, ctx, &mut None, is_inside_loop)? { meta.subsume(more_meta); } Ok(()) })?; let reject = ctx.new_body(|ctx| { if self.bump_if(frontend, TokenValue::Else).is_some() { if let Some(more_meta) = self.parse_statement(frontend, ctx, &mut None, is_inside_loop)? { meta.subsume(more_meta); } } Ok(()) })?; ctx.body.push( Statement::If { condition, accept, reject, }, meta, ); meta } TokenValue::Switch => { let mut meta = self.bump(frontend)?.meta; let end_meta; self.expect(frontend, TokenValue::LeftParen)?; let (selector, uint) = { let mut stmt = ctx.stmt_ctx(); let expr = self.parse_expression(frontend, ctx, &mut stmt)?; let (root, meta) = ctx.lower_expect(stmt, frontend, expr, ExprPos::Rhs)?; let uint = ctx.resolve_type(root, meta)?.scalar_kind() == Some(crate::ScalarKind::Uint); (root, uint) }; self.expect(frontend, TokenValue::RightParen)?; ctx.emit_restart(); let mut cases = Vec::new(); // Track if any default case is present in the switch statement. let mut default_present = false; self.expect(frontend, TokenValue::LeftBrace)?; loop { let value = match self.expect_peek(frontend)?.value { TokenValue::Case => { self.bump(frontend)?; let (const_expr, meta) = self.parse_constant_expression( frontend, ctx.module, ctx.global_expression_kind_tracker, )?; match ctx.module.global_expressions[const_expr] { Expression::Literal(Literal::I32(value)) => match uint { // This unchecked cast isn't good, but since // we only reach this code when the selector // is unsigned but the case label is signed, // verification will reject the module // anyway (which also matches GLSL's rules). true => crate::SwitchValue::U32(value as u32), false => crate::SwitchValue::I32(value), }, Expression::Literal(Literal::U32(value)) => { crate::SwitchValue::U32(value) } _ => { frontend.errors.push(Error { kind: ErrorKind::SemanticError( "Case values can only be integers".into(), ), meta, }); crate::SwitchValue::I32(0) } } } TokenValue::Default => { self.bump(frontend)?; default_present = true; crate::SwitchValue::Default } TokenValue::RightBrace => { end_meta = self.bump(frontend)?.meta; break; } _ => { let Token { value, meta } = self.bump(frontend)?; return Err(Error { kind: ErrorKind::InvalidToken( value, vec![ TokenValue::Case.into(), TokenValue::Default.into(), TokenValue::RightBrace.into(), ], ), meta, }); } }; self.expect(frontend, TokenValue::Colon)?; let mut fall_through = true; let body = ctx.new_body(|ctx| { let mut case_terminator = None; loop { match self.expect_peek(frontend)?.value { TokenValue::Case | TokenValue::Default | TokenValue::RightBrace => { break } _ => { self.parse_statement( frontend, ctx, &mut case_terminator, is_inside_loop, )?; } } } if let Some(mut idx) = case_terminator { if let Statement::Break = ctx.body[idx - 1] { fall_through = false; idx -= 1; } ctx.body.cull(idx..) } Ok(()) })?; cases.push(SwitchCase { value, body, fall_through, }) } meta.subsume(end_meta); // NOTE: do not unwrap here since a switch statement isn't required // to have any cases. if let Some(case) = cases.last_mut() { // GLSL requires that the last case not be empty, so we check // that here and produce an error otherwise (fall_through must // also be checked because `break`s count as statements but // they aren't added to the body) if case.body.is_empty() && case.fall_through { frontend.errors.push(Error { kind: ErrorKind::SemanticError( "last case/default label must be followed by statements".into(), ), meta, }) } // GLSL allows the last case to not have any `break` statement, // this would mark it as fall through but naga's IR requires that // the last case must not be fall through, so we mark need to mark // the last case as not fall through always. case.fall_through = false; } // Add an empty default case in case non was present, this is needed because // naga's IR requires that all switch statements must have a default case but // GLSL doesn't require that, so we might need to add an empty default case. if !default_present { cases.push(SwitchCase { value: crate::SwitchValue::Default, body: Block::new(), fall_through: false, }) } ctx.body.push(Statement::Switch { selector, cases }, meta); meta } TokenValue::While => { let mut meta = self.bump(frontend)?.meta; let loop_body = ctx.new_body(|ctx| { let mut stmt = ctx.stmt_ctx(); self.expect(frontend, TokenValue::LeftParen)?; let root = self.parse_expression(frontend, ctx, &mut stmt)?; meta.subsume(self.expect(frontend, TokenValue::RightParen)?.meta); let (expr, expr_meta) = ctx.lower_expect(stmt, frontend, root, ExprPos::Rhs)?; let condition = ctx.add_expression( Expression::Unary { op: UnaryOperator::LogicalNot, expr, }, expr_meta, )?; ctx.emit_restart(); ctx.body.push( Statement::If { condition, accept: new_break(), reject: Block::new(), }, Span::default(), ); meta.subsume(expr_meta); if let Some(body_meta) = self.parse_statement(frontend, ctx, &mut None, true)? { meta.subsume(body_meta); } Ok(()) })?; ctx.body.push( Statement::Loop { body: loop_body, continuing: Block::new(), break_if: None, }, meta, ); meta } TokenValue::Do => { let mut meta = self.bump(frontend)?.meta; let loop_body = ctx.new_body(|ctx| { let mut terminator = None; self.parse_statement(frontend, ctx, &mut terminator, true)?; let mut stmt = ctx.stmt_ctx(); self.expect(frontend, TokenValue::While)?; self.expect(frontend, TokenValue::LeftParen)?; let root = self.parse_expression(frontend, ctx, &mut stmt)?; let end_meta = self.expect(frontend, TokenValue::RightParen)?.meta; meta.subsume(end_meta); let (expr, expr_meta) = ctx.lower_expect(stmt, frontend, root, ExprPos::Rhs)?; let condition = ctx.add_expression( Expression::Unary { op: UnaryOperator::LogicalNot, expr, }, expr_meta, )?; ctx.emit_restart(); ctx.body.push( Statement::If { condition, accept: new_break(), reject: Block::new(), }, Span::default(), ); if let Some(idx) = terminator { ctx.body.cull(idx..) } Ok(()) })?; ctx.body.push( Statement::Loop { body: loop_body, continuing: Block::new(), break_if: None, }, meta, ); meta } TokenValue::For => { let mut meta = self.bump(frontend)?.meta; ctx.symbol_table.push_scope(); self.expect(frontend, TokenValue::LeftParen)?; if self.bump_if(frontend, TokenValue::Semicolon).is_none() { if self.peek_type_name(frontend) || self.peek_type_qualifier(frontend) { self.parse_declaration(frontend, ctx, false, is_inside_loop)?; } else { let mut stmt = ctx.stmt_ctx(); let expr = self.parse_expression(frontend, ctx, &mut stmt)?; ctx.lower(stmt, frontend, expr, ExprPos::Rhs)?; self.expect(frontend, TokenValue::Semicolon)?; } } let loop_body = ctx.new_body(|ctx| { if self.bump_if(frontend, TokenValue::Semicolon).is_none() { let (expr, expr_meta) = if self.peek_type_name(frontend) || self.peek_type_qualifier(frontend) { let mut qualifiers = self.parse_type_qualifiers(frontend, ctx)?; let (ty, mut meta) = self.parse_type_non_void(frontend, ctx)?; let name = self.expect_ident(frontend)?.0; self.expect(frontend, TokenValue::Assign)?; let (value, end_meta) = self.parse_initializer(frontend, ty, ctx)?; meta.subsume(end_meta); let decl = VarDeclaration { qualifiers: &mut qualifiers, ty, name: Some(name), init: None, meta, }; let pointer = frontend.add_local_var(ctx, decl)?; ctx.emit_restart(); ctx.body.push(Statement::Store { pointer, value }, meta); (value, end_meta) } else { let mut stmt = ctx.stmt_ctx(); let root = self.parse_expression(frontend, ctx, &mut stmt)?; ctx.lower_expect(stmt, frontend, root, ExprPos::Rhs)? }; let condition = ctx.add_expression( Expression::Unary { op: UnaryOperator::LogicalNot, expr, }, expr_meta, )?; ctx.emit_restart(); ctx.body.push( Statement::If { condition, accept: new_break(), reject: Block::new(), }, Span::default(), ); self.expect(frontend, TokenValue::Semicolon)?; } Ok(()) })?; let continuing = ctx.new_body(|ctx| { match self.expect_peek(frontend)?.value { TokenValue::RightParen => {} _ => { let mut stmt = ctx.stmt_ctx(); let rest = self.parse_expression(frontend, ctx, &mut stmt)?; ctx.lower(stmt, frontend, rest, ExprPos::Rhs)?; } } Ok(()) })?; meta.subsume(self.expect(frontend, TokenValue::RightParen)?.meta); let loop_body = ctx.with_body(loop_body, |ctx| { if let Some(stmt_meta) = self.parse_statement(frontend, ctx, &mut None, true)? { meta.subsume(stmt_meta); } Ok(()) })?; ctx.body.push( Statement::Loop { body: loop_body, continuing, break_if: None, }, meta, ); ctx.symbol_table.pop_scope(); meta } TokenValue::LeftBrace => { let mut meta = self.bump(frontend)?.meta; let mut block_terminator = None; let block = ctx.new_body(|ctx| { let block_meta = self.parse_compound_statement( meta, frontend, ctx, &mut block_terminator, is_inside_loop, )?; meta.subsume(block_meta); Ok(()) })?; ctx.body.push(Statement::Block(block), meta); if block_terminator.is_some() { terminator.get_or_insert(ctx.body.len()); } meta } TokenValue::Semicolon => self.bump(frontend)?.meta, _ => { // Attempt to force expression parsing for remainder of the // tokens. Unknown or invalid tokens will be caught there and // turned into an error. let mut stmt = ctx.stmt_ctx(); let expr = self.parse_expression(frontend, ctx, &mut stmt)?; ctx.lower(stmt, frontend, expr, ExprPos::Rhs)?; self.expect(frontend, TokenValue::Semicolon)?.meta } }; meta.subsume(meta_rest); Ok(Some(meta)) } pub fn parse_compound_statement( &mut self, mut meta: Span, frontend: &mut Frontend, ctx: &mut Context, terminator: &mut Option, is_inside_loop: bool, ) -> Result { ctx.symbol_table.push_scope(); loop { if let Some(Token { meta: brace_meta, .. }) = self.bump_if(frontend, TokenValue::RightBrace) { meta.subsume(brace_meta); break; } let stmt = self.parse_statement(frontend, ctx, terminator, is_inside_loop)?; if let Some(stmt_meta) = stmt { meta.subsume(stmt_meta); } } if let Some(idx) = *terminator { ctx.body.cull(idx..) } ctx.symbol_table.pop_scope(); Ok(meta) } pub fn parse_function_args( &mut self, frontend: &mut Frontend, ctx: &mut Context, ) -> Result<()> { if self.bump_if(frontend, TokenValue::Void).is_some() { return Ok(()); } loop { if self.peek_type_name(frontend) || self.peek_parameter_qualifier(frontend) { let qualifier = self.parse_parameter_qualifier(frontend); let mut ty = self.parse_type_non_void(frontend, ctx)?.0; match self.expect_peek(frontend)?.value { TokenValue::Comma => { self.bump(frontend)?; ctx.add_function_arg(None, ty, qualifier)?; continue; } TokenValue::Identifier(_) => { let mut name = self.expect_ident(frontend)?; self.parse_array_specifier(frontend, ctx, &mut name.1, &mut ty)?; ctx.add_function_arg(Some(name), ty, qualifier)?; if self.bump_if(frontend, TokenValue::Comma).is_some() { continue; } break; } _ => break, } } break; } Ok(()) } } ================================================ FILE: naga/src/front/glsl/parser/types.rs ================================================ use alloc::{vec, vec::Vec}; use core::num::NonZeroU32; use crate::{ front::glsl::{ ast::{QualifierKey, QualifierValue, StorageQualifier, StructLayout, TypeQualifiers}, context::Context, error::ExpectedToken, parser::ParsingContext, token::{Token, TokenValue}, Error, ErrorKind, Frontend, Result, }, AddressSpace, ArraySize, Handle, Span, Type, TypeInner, }; impl ParsingContext<'_> { /// Parses an optional array_specifier returning whether or not it's present /// and modifying the type handle if it exists pub fn parse_array_specifier( &mut self, frontend: &mut Frontend, ctx: &mut Context, span: &mut Span, ty: &mut Handle, ) -> Result<()> { while self.parse_array_specifier_single(frontend, ctx, span, ty)? {} Ok(()) } /// Implementation of [`Self::parse_array_specifier`] for a single array_specifier fn parse_array_specifier_single( &mut self, frontend: &mut Frontend, ctx: &mut Context, span: &mut Span, ty: &mut Handle, ) -> Result { if self.bump_if(frontend, TokenValue::LeftBracket).is_some() { let size = if let Some(Token { meta, .. }) = self.bump_if(frontend, TokenValue::RightBracket) { span.subsume(meta); ArraySize::Dynamic } else { let (value, constant_span) = self.parse_uint_constant(frontend, ctx)?; let size = NonZeroU32::new(value).ok_or(Error { kind: ErrorKind::SemanticError("Array size must be greater than zero".into()), meta: constant_span, })?; let end_span = self.expect(frontend, TokenValue::RightBracket)?.meta; span.subsume(end_span); ArraySize::Constant(size) }; frontend.layouter.update(ctx.module.to_ctx()).unwrap(); let stride = frontend.layouter[*ty].to_stride(); *ty = ctx.module.types.insert( Type { name: None, inner: TypeInner::Array { base: *ty, size, stride, }, }, *span, ); Ok(true) } else { Ok(false) } } pub fn parse_type( &mut self, frontend: &mut Frontend, ctx: &mut Context, ) -> Result<(Option>, Span)> { let token = self.bump(frontend)?; let mut handle = match token.value { TokenValue::Void => return Ok((None, token.meta)), TokenValue::TypeName(ty) => ctx.module.types.insert(ty, token.meta), TokenValue::Struct => { let mut meta = token.meta; let ty_name = self.expect_ident(frontend)?.0; self.expect(frontend, TokenValue::LeftBrace)?; let mut members = Vec::new(); let span = self.parse_struct_declaration_list( frontend, ctx, &mut members, StructLayout::Std140, )?; let end_meta = self.expect(frontend, TokenValue::RightBrace)?.meta; meta.subsume(end_meta); let ty = ctx.module.types.insert( Type { name: Some(ty_name.clone()), inner: TypeInner::Struct { members, span }, }, meta, ); frontend.lookup_type.insert(ty_name, ty); ty } TokenValue::Identifier(ident) => match frontend.lookup_type.get(&ident) { Some(ty) => *ty, None => { return Err(Error { kind: ErrorKind::UnknownType(ident), meta: token.meta, }) } }, _ => { return Err(Error { kind: ErrorKind::InvalidToken( token.value, vec![ TokenValue::Void.into(), TokenValue::Struct.into(), ExpectedToken::TypeName, ], ), meta: token.meta, }); } }; let mut span = token.meta; self.parse_array_specifier(frontend, ctx, &mut span, &mut handle)?; Ok((Some(handle), span)) } pub fn parse_type_non_void( &mut self, frontend: &mut Frontend, ctx: &mut Context, ) -> Result<(Handle, Span)> { let (maybe_ty, meta) = self.parse_type(frontend, ctx)?; let ty = maybe_ty.ok_or_else(|| Error { kind: ErrorKind::SemanticError("Type can't be void".into()), meta, })?; Ok((ty, meta)) } pub fn peek_type_qualifier(&mut self, frontend: &mut Frontend) -> bool { self.peek(frontend).is_some_and(|t| match t.value { TokenValue::Invariant | TokenValue::Interpolation(_) | TokenValue::Sampling(_) | TokenValue::PrecisionQualifier(_) | TokenValue::Const | TokenValue::In | TokenValue::Out | TokenValue::Uniform | TokenValue::Shared | TokenValue::Buffer | TokenValue::Restrict | TokenValue::MemoryQualifier(_) | TokenValue::Layout => true, _ => false, }) } pub fn parse_type_qualifiers<'a>( &mut self, frontend: &mut Frontend, ctx: &mut Context, ) -> Result> { let mut qualifiers = TypeQualifiers::default(); while self.peek_type_qualifier(frontend) { let token = self.bump(frontend)?; // Handle layout qualifiers outside the match since this can push multiple values if token.value == TokenValue::Layout { self.parse_layout_qualifier_id_list(frontend, ctx, &mut qualifiers)?; continue; } qualifiers.span.subsume(token.meta); match token.value { TokenValue::Invariant => { if qualifiers.invariant.is_some() { frontend.errors.push(Error { kind: ErrorKind::SemanticError( "Cannot use more than one invariant qualifier per declaration" .into(), ), meta: token.meta, }) } qualifiers.invariant = Some(token.meta); } TokenValue::Interpolation(i) => { if qualifiers.interpolation.is_some() { frontend.errors.push(Error { kind: ErrorKind::SemanticError( "Cannot use more than one interpolation qualifier per declaration" .into(), ), meta: token.meta, }) } qualifiers.interpolation = Some((i, token.meta)); } TokenValue::Const | TokenValue::In | TokenValue::Out | TokenValue::Uniform | TokenValue::Shared | TokenValue::Buffer => { let storage = match token.value { TokenValue::Const => StorageQualifier::Const, TokenValue::In => StorageQualifier::Input, TokenValue::Out => StorageQualifier::Output, TokenValue::Uniform => { StorageQualifier::AddressSpace(AddressSpace::Uniform) } TokenValue::Shared => { StorageQualifier::AddressSpace(AddressSpace::WorkGroup) } TokenValue::Buffer => { StorageQualifier::AddressSpace(AddressSpace::Storage { access: crate::StorageAccess::LOAD | crate::StorageAccess::STORE, }) } _ => unreachable!(), }; if StorageQualifier::AddressSpace(AddressSpace::Function) != qualifiers.storage.0 { frontend.errors.push(Error { kind: ErrorKind::SemanticError( "Cannot use more than one storage qualifier per declaration".into(), ), meta: token.meta, }); } qualifiers.storage = (storage, token.meta); } TokenValue::Sampling(s) => { if qualifiers.sampling.is_some() { frontend.errors.push(Error { kind: ErrorKind::SemanticError( "Cannot use more than one sampling qualifier per declaration" .into(), ), meta: token.meta, }) } qualifiers.sampling = Some((s, token.meta)); } TokenValue::PrecisionQualifier(p) => { if qualifiers.precision.is_some() { frontend.errors.push(Error { kind: ErrorKind::SemanticError( "Cannot use more than one precision qualifier per declaration" .into(), ), meta: token.meta, }) } qualifiers.precision = Some((p, token.meta)); } TokenValue::MemoryQualifier(access) => { let load_store = crate::StorageAccess::LOAD | crate::StorageAccess::STORE; let storage_access = qualifiers .storage_access .get_or_insert((load_store, Span::default())); if !storage_access.0.contains(!access & load_store) { frontend.errors.push(Error { kind: ErrorKind::SemanticError( "The same memory qualifier can only be used once".into(), ), meta: token.meta, }) } storage_access.0 &= access; storage_access.1.subsume(token.meta); } TokenValue::Restrict => continue, _ => unreachable!(), }; } Ok(qualifiers) } pub fn parse_layout_qualifier_id_list( &mut self, frontend: &mut Frontend, ctx: &mut Context, qualifiers: &mut TypeQualifiers, ) -> Result<()> { self.expect(frontend, TokenValue::LeftParen)?; loop { self.parse_layout_qualifier_id(frontend, ctx, &mut qualifiers.layout_qualifiers)?; if self.bump_if(frontend, TokenValue::Comma).is_some() { continue; } break; } let token = self.expect(frontend, TokenValue::RightParen)?; qualifiers.span.subsume(token.meta); Ok(()) } pub fn parse_layout_qualifier_id( &mut self, frontend: &mut Frontend, ctx: &mut Context, qualifiers: &mut crate::FastHashMap, ) -> Result<()> { // layout_qualifier_id: // IDENTIFIER // IDENTIFIER EQUAL constant_expression // SHARED let mut token = self.bump(frontend)?; match token.value { TokenValue::Identifier(name) => { let (key, value) = match name.as_str() { "std140" => ( QualifierKey::Layout, QualifierValue::Layout(StructLayout::Std140), ), "std430" => ( QualifierKey::Layout, QualifierValue::Layout(StructLayout::Std430), ), "index" => { self.expect(frontend, TokenValue::Assign)?; let (value, end_meta) = self.parse_uint_constant(frontend, ctx)?; token.meta.subsume(end_meta); (QualifierKey::Index, QualifierValue::Uint(value)) } word => { if let Some(format) = map_image_format(word) { (QualifierKey::Format, QualifierValue::Format(format)) } else { let key = QualifierKey::String(name.into()); let value = if self.bump_if(frontend, TokenValue::Assign).is_some() { let (value, end_meta) = match self.parse_uint_constant(frontend, ctx) { Ok(v) => v, Err(e) => { frontend.errors.push(e); (0, Span::default()) } }; token.meta.subsume(end_meta); QualifierValue::Uint(value) } else { QualifierValue::None }; (key, value) } } }; qualifiers.insert(key, (value, token.meta)); } _ => frontend.errors.push(Error { kind: ErrorKind::InvalidToken(token.value, vec![ExpectedToken::Identifier]), meta: token.meta, }), } Ok(()) } pub fn peek_type_name(&mut self, frontend: &mut Frontend) -> bool { self.peek(frontend).is_some_and(|t| match t.value { TokenValue::TypeName(_) | TokenValue::Void => true, TokenValue::Struct => true, TokenValue::Identifier(ref ident) => frontend.lookup_type.contains_key(ident), _ => false, }) } } fn map_image_format(word: &str) -> Option { use crate::StorageFormat as Sf; let format = match word { // float-image-format-qualifier: "rgba32f" => Sf::Rgba32Float, "rgba16f" => Sf::Rgba16Float, "rg32f" => Sf::Rg32Float, "rg16f" => Sf::Rg16Float, "r11f_g11f_b10f" => Sf::Rg11b10Ufloat, "r32f" => Sf::R32Float, "r16f" => Sf::R16Float, "rgba16" => Sf::Rgba16Unorm, "rgb10_a2ui" => Sf::Rgb10a2Uint, "rgb10_a2" => Sf::Rgb10a2Unorm, "rgba8" => Sf::Rgba8Unorm, "rg16" => Sf::Rg16Unorm, "rg8" => Sf::Rg8Unorm, "r16" => Sf::R16Unorm, "r8" => Sf::R8Unorm, "rgba16_snorm" => Sf::Rgba16Snorm, "rgba8_snorm" => Sf::Rgba8Snorm, "rg16_snorm" => Sf::Rg16Snorm, "rg8_snorm" => Sf::Rg8Snorm, "r16_snorm" => Sf::R16Snorm, "r8_snorm" => Sf::R8Snorm, // int-image-format-qualifier: "rgba32i" => Sf::Rgba32Sint, "rgba16i" => Sf::Rgba16Sint, "rgba8i" => Sf::Rgba8Sint, "rg32i" => Sf::Rg32Sint, "rg16i" => Sf::Rg16Sint, "rg8i" => Sf::Rg8Sint, "r32i" => Sf::R32Sint, "r16i" => Sf::R16Sint, "r8i" => Sf::R8Sint, // uint-image-format-qualifier: "rgba32ui" => Sf::Rgba32Uint, "rgba16ui" => Sf::Rgba16Uint, "rgba8ui" => Sf::Rgba8Uint, "r64ui" => Sf::R64Uint, "rg32ui" => Sf::Rg32Uint, "rg16ui" => Sf::Rg16Uint, "rg8ui" => Sf::Rg8Uint, "r32ui" => Sf::R32Uint, "r16ui" => Sf::R16Uint, "r8ui" => Sf::R8Uint, // TODO: These next ones seem incorrect to me // "rgb10_a2ui" => Sf::Rgb10a2Unorm, _ => return None, }; Some(format) } ================================================ FILE: naga/src/front/glsl/parser.rs ================================================ use alloc::{string::String, vec}; use core::iter::Peekable; use pp_rs::token::{PreprocessorError, Token as PPToken, TokenValue as PPTokenValue}; use super::{ ast::{FunctionKind, Profile, TypeQualifiers}, context::{Context, ExprPos}, error::ExpectedToken, error::{Error, ErrorKind}, lex::{Lexer, LexerResultKind}, token::{Directive, DirectiveKind}, token::{Token, TokenValue}, variables::{GlobalOrConstant, VarDeclaration}, Frontend, Result, }; use crate::{arena::Handle, proc::ConstValueError, Expression, Module, Span, Type}; mod declarations; mod expressions; mod functions; mod types; pub struct ParsingContext<'source> { lexer: Peekable>, /// Used to store tokens already consumed by the parser but that need to be backtracked backtracked_token: Option, last_meta: Span, } impl<'source> ParsingContext<'source> { pub fn new(lexer: Lexer<'source>) -> Self { ParsingContext { lexer: lexer.peekable(), backtracked_token: None, last_meta: Span::default(), } } /// Helper method for backtracking from a consumed token /// /// This method should always be used instead of assigning to `backtracked_token` since /// it validates that backtracking hasn't occurred more than one time in a row /// /// # Panics /// - If the parser already backtracked without bumping in between pub fn backtrack(&mut self, token: Token) -> Result<()> { // This should never happen if let Some(ref prev_token) = self.backtracked_token { return Err(Error { kind: ErrorKind::InternalError("The parser tried to backtrack twice in a row"), meta: prev_token.meta, }); } self.backtracked_token = Some(token); Ok(()) } pub fn expect_ident(&mut self, frontend: &mut Frontend) -> Result<(String, Span)> { let token = self.bump(frontend)?; match token.value { TokenValue::Identifier(name) => Ok((name, token.meta)), _ => Err(Error { kind: ErrorKind::InvalidToken(token.value, vec![ExpectedToken::Identifier]), meta: token.meta, }), } } pub fn expect(&mut self, frontend: &mut Frontend, value: TokenValue) -> Result { let token = self.bump(frontend)?; if token.value != value { Err(Error { kind: ErrorKind::InvalidToken(token.value, vec![value.into()]), meta: token.meta, }) } else { Ok(token) } } pub fn next(&mut self, frontend: &mut Frontend) -> Option { loop { if let Some(token) = self.backtracked_token.take() { self.last_meta = token.meta; break Some(token); } let res = self.lexer.next()?; match res.kind { LexerResultKind::Token(token) => { self.last_meta = token.meta; break Some(token); } LexerResultKind::Directive(directive) => { frontend.handle_directive(directive, res.meta) } LexerResultKind::Error(error) => frontend.errors.push(Error { kind: ErrorKind::PreprocessorError(error), meta: res.meta, }), } } } pub fn bump(&mut self, frontend: &mut Frontend) -> Result { self.next(frontend).ok_or(Error { kind: ErrorKind::EndOfFile, meta: self.last_meta, }) } /// Returns None on the end of the file rather than an error like other methods pub fn bump_if(&mut self, frontend: &mut Frontend, value: TokenValue) -> Option { if self.peek(frontend).filter(|t| t.value == value).is_some() { self.bump(frontend).ok() } else { None } } pub fn peek(&mut self, frontend: &mut Frontend) -> Option<&Token> { loop { if let Some(ref token) = self.backtracked_token { break Some(token); } match self.lexer.peek()?.kind { LexerResultKind::Token(_) => { let res = self.lexer.peek()?; match res.kind { LexerResultKind::Token(ref token) => break Some(token), _ => unreachable!(), } } LexerResultKind::Error(_) | LexerResultKind::Directive(_) => { let res = self.lexer.next()?; match res.kind { LexerResultKind::Directive(directive) => { frontend.handle_directive(directive, res.meta) } LexerResultKind::Error(error) => frontend.errors.push(Error { kind: ErrorKind::PreprocessorError(error), meta: res.meta, }), LexerResultKind::Token(_) => unreachable!(), } } } } } pub fn expect_peek(&mut self, frontend: &mut Frontend) -> Result<&Token> { let meta = self.last_meta; self.peek(frontend).ok_or(Error { kind: ErrorKind::EndOfFile, meta, }) } pub fn parse(&mut self, frontend: &mut Frontend) -> Result { let mut module = Module::default(); let mut global_expression_kind_tracker = crate::proc::ExpressionKindTracker::new(); // Body and expression arena for global initialization let mut ctx = Context::new( frontend, &mut module, false, &mut global_expression_kind_tracker, )?; while self.peek(frontend).is_some() { self.parse_external_declaration(frontend, &mut ctx)?; } // Add an `EntryPoint` to `parser.module` for `main`, if a // suitable overload exists. Error out if we can't find one. if let Some(declaration) = frontend.lookup_function.get("main") { for decl in declaration.overloads.iter() { if let FunctionKind::Call(handle) = decl.kind { if decl.defined && decl.parameters.is_empty() { frontend.add_entry_point(handle, ctx)?; return Ok(module); } } } } Err(Error { kind: ErrorKind::SemanticError("Missing entry point".into()), meta: Span::default(), }) } fn parse_uint_constant( &mut self, frontend: &mut Frontend, ctx: &mut Context, ) -> Result<(u32, Span)> { let (const_expr, meta) = self.parse_constant_expression( frontend, ctx.module, ctx.global_expression_kind_tracker, )?; let res = ctx.module.to_ctx().get_const_val(const_expr); let int = match res { Ok(value) => Ok(value), Err(ConstValueError::Negative) => Err(Error { kind: ErrorKind::SemanticError("int constant overflows".into()), meta, }), Err(ConstValueError::NonConst | ConstValueError::InvalidType) => Err(Error { kind: ErrorKind::SemanticError("Expected a uint constant".into()), meta, }), }?; Ok((int, meta)) } fn parse_constant_expression( &mut self, frontend: &mut Frontend, module: &mut Module, global_expression_kind_tracker: &mut crate::proc::ExpressionKindTracker, ) -> Result<(Handle, Span)> { let mut ctx = Context::new(frontend, module, true, global_expression_kind_tracker)?; let mut stmt_ctx = ctx.stmt_ctx(); let expr = self.parse_conditional(frontend, &mut ctx, &mut stmt_ctx, None)?; let (root, meta) = ctx.lower_expect(stmt_ctx, frontend, expr, ExprPos::Rhs)?; Ok((root, meta)) } } impl Frontend { fn handle_directive(&mut self, directive: Directive, meta: Span) { let mut tokens = directive.tokens.into_iter(); match directive.kind { DirectiveKind::Version { is_first_directive } => { if !is_first_directive { self.errors.push(Error { kind: ErrorKind::SemanticError( "#version must occur first in shader".into(), ), meta, }) } match tokens.next() { Some(PPToken { value: PPTokenValue::Integer(int), location, }) => match int.value { 440 | 450 | 460 => self.meta.version = int.value as u16, _ => self.errors.push(Error { kind: ErrorKind::InvalidVersion(int.value), meta: location.into(), }), }, Some(PPToken { value, location }) => self.errors.push(Error { kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( value, )), meta: location.into(), }), None => self.errors.push(Error { kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedNewLine), meta, }), }; match tokens.next() { Some(PPToken { value: PPTokenValue::Ident(name), location, }) => match name.as_str() { "core" => self.meta.profile = Profile::Core, _ => self.errors.push(Error { kind: ErrorKind::InvalidProfile(name), meta: location.into(), }), }, Some(PPToken { value, location }) => self.errors.push(Error { kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( value, )), meta: location.into(), }), None => {} }; if let Some(PPToken { value, location }) = tokens.next() { self.errors.push(Error { kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( value, )), meta: location.into(), }) } } DirectiveKind::Extension => { // TODO: Proper extension handling // - Checking for extension support in the compiler // - Handle behaviors such as warn // - Handle the all extension let name = match tokens.next() { Some(PPToken { value: PPTokenValue::Ident(name), .. }) => Some(name), Some(PPToken { value, location }) => { self.errors.push(Error { kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( value, )), meta: location.into(), }); None } None => { self.errors.push(Error { kind: ErrorKind::PreprocessorError( PreprocessorError::UnexpectedNewLine, ), meta, }); None } }; match tokens.next() { Some(PPToken { value: PPTokenValue::Punct(pp_rs::token::Punct::Colon), .. }) => {} Some(PPToken { value, location }) => self.errors.push(Error { kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( value, )), meta: location.into(), }), None => self.errors.push(Error { kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedNewLine), meta, }), }; match tokens.next() { Some(PPToken { value: PPTokenValue::Ident(behavior), location, }) => match behavior.as_str() { "require" | "enable" | "warn" | "disable" => { if let Some(name) = name { self.meta.extensions.insert(name); } } _ => self.errors.push(Error { kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( PPTokenValue::Ident(behavior), )), meta: location.into(), }), }, Some(PPToken { value, location }) => self.errors.push(Error { kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( value, )), meta: location.into(), }), None => self.errors.push(Error { kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedNewLine), meta, }), } if let Some(PPToken { value, location }) = tokens.next() { self.errors.push(Error { kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( value, )), meta: location.into(), }) } } DirectiveKind::Pragma => { // TODO: handle some common pragmas? } } } } pub struct DeclarationContext<'ctx, 'qualifiers, 'a> { qualifiers: TypeQualifiers<'qualifiers>, /// Indicates a global declaration external: bool, is_inside_loop: bool, ctx: &'ctx mut Context<'a>, } impl DeclarationContext<'_, '_, '_> { fn add_var( &mut self, frontend: &mut Frontend, ty: Handle, name: String, init: Option>, meta: Span, ) -> Result> { let decl = VarDeclaration { qualifiers: &mut self.qualifiers, ty, name: Some(name), init, meta, }; match self.external { true => { let global = frontend.add_global_var(self.ctx, decl)?; let expr = match global { GlobalOrConstant::Global(handle) => Expression::GlobalVariable(handle), GlobalOrConstant::Constant(handle) => Expression::Constant(handle), GlobalOrConstant::Override(handle) => Expression::Override(handle), }; Ok(self.ctx.add_expression(expr, meta)?) } false => frontend.add_local_var(self.ctx, decl), } } } ================================================ FILE: naga/src/front/glsl/parser_tests.rs ================================================ use alloc::{borrow::ToOwned, vec}; use pp_rs::token::PreprocessorError; use super::{ ast::Profile, error::ExpectedToken, error::{Error, ErrorKind, ParseErrors}, token::TokenValue, Frontend, Options, Span, }; use crate::ShaderStage; #[cfg(test)] use std::println; #[test] fn version() { let mut frontend = Frontend::default(); // invalid versions assert_eq!( frontend .parse( &Options::from(ShaderStage::Vertex), "#version 99000\n void main(){}", ) .err() .unwrap(), ParseErrors { errors: vec![Error { kind: ErrorKind::InvalidVersion(99000), meta: Span::new(9, 14) }], }, ); assert_eq!( frontend .parse( &Options::from(ShaderStage::Vertex), "#version 449\n void main(){}", ) .err() .unwrap(), ParseErrors { errors: vec![Error { kind: ErrorKind::InvalidVersion(449), meta: Span::new(9, 12) }] }, ); assert_eq!( frontend .parse( &Options::from(ShaderStage::Vertex), "#version 450 smart\n void main(){}", ) .err() .unwrap(), ParseErrors { errors: vec![Error { kind: ErrorKind::InvalidProfile("smart".into()), meta: Span::new(13, 18), }] }, ); assert_eq!( frontend .parse( &Options::from(ShaderStage::Vertex), "#version 450\nvoid main(){} #version 450", ) .err() .unwrap(), ParseErrors { errors: vec![ Error { kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedHash,), meta: Span::new(27, 28), }, Error { kind: ErrorKind::InvalidToken( TokenValue::Identifier("version".into()), vec![ExpectedToken::Eof] ), meta: Span::new(28, 35) } ] }, ); // valid versions frontend .parse( &Options::from(ShaderStage::Vertex), " # version 450\nvoid main() {}", ) .unwrap(); assert_eq!( (frontend.metadata().version, frontend.metadata().profile), (450, Profile::Core) ); frontend .parse( &Options::from(ShaderStage::Vertex), "#version 450\nvoid main() {}", ) .unwrap(); assert_eq!( (frontend.metadata().version, frontend.metadata().profile), (450, Profile::Core) ); frontend .parse( &Options::from(ShaderStage::Vertex), "#version 450 core\nvoid main(void) {}", ) .unwrap(); assert_eq!( (frontend.metadata().version, frontend.metadata().profile), (450, Profile::Core) ); } #[test] fn control_flow() { let mut frontend = Frontend::default(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void main() { if (true) { return 1; } else { return 2; } } "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void main() { if (true) { return 1; } } "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void main() { int x; int y = 3; switch (5) { case 2: x = 2; case 5: x = 5; y = 2; break; default: x = 0; } } "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void main() { int x = 0; while(x < 5) { x = x + 1; } do { x = x - 1; } while(x >= 4) } "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void main() { int x = 0; for(int i = 0; i < 10;) { x = x + 2; } for(;;); return x; } "#, ) .unwrap(); } #[test] fn declarations() { let mut frontend = Frontend::default(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" #version 450 layout(location = 0) in vec2 v_uv; layout(location = 0) out vec4 o_color; layout(set = 1, binding = 1) uniform texture2D tex; layout(set = 1, binding = 2) uniform sampler tex_sampler; layout(early_fragment_tests) in; void main() {} "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" #version 450 layout(std140, set = 2, binding = 0) uniform u_locals { vec3 model_offs; float load_time; ivec4 atlas_offs; }; void main() {} "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" #version 450 layout(push_constant) uniform u_locals { vec3 model_offs; float load_time; ivec4 atlas_offs; }; void main() {} "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" #version 450 layout(std430, set = 2, binding = 0) uniform u_locals { vec3 model_offs; float load_time; ivec4 atlas_offs; }; void main() {} "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" #version 450 layout(std140, set = 2, binding = 0) uniform u_locals { vec3 model_offs; float load_time; } block_var; void main() { load_time * model_offs; block_var.load_time * block_var.model_offs; } "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" #version 450 float vector = vec4(1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0); void main() {} "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" #version 450 precision highp float; void main() {} "#, ) .unwrap(); } #[test] fn textures() { let mut frontend = Frontend::default(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" #version 450 layout(location = 0) in vec2 v_uv; layout(location = 0) out vec4 o_color; layout(set = 1, binding = 1) uniform texture2D tex; layout(set = 1, binding = 2) uniform sampler tex_sampler; void main() { o_color = texture(sampler2D(tex, tex_sampler), v_uv); o_color.a = texture(sampler2D(tex, tex_sampler), v_uv, 2.0).a; } "#, ) .unwrap(); } #[test] fn functions() { let mut frontend = Frontend::default(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void test1(float); void test1(float) {} void main() {} "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void test2(float a) {} void test3(float a, float b) {} void test4(float, float) {} void main() {} "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 float test(float a) { return a; } void main() {} "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 float test(vec4 p) { return p.x; } void main() {} "#, ) .unwrap(); // Function overloading frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 float test(vec2 p); float test(vec3 p); float test(vec4 p); float test(vec2 p) { return p.x; } float test(vec3 p) { return p.x; } float test(vec4 p) { return p.x; } void main() {} "#, ) .unwrap(); assert_eq!( frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 int test(vec4 p) { return p.x; } float test(vec4 p) { return p.x; } void main() {} "#, ) .err() .unwrap(), ParseErrors { errors: vec![Error { kind: ErrorKind::SemanticError("Function already defined".into()), meta: Span::new(134, 152), }] }, ); println!(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 float callee(uint q) { return float(q); } float caller() { callee(1u); } void main() {} "#, ) .unwrap(); // Nested function call frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 layout(set = 0, binding = 1) uniform texture2D t_noise; layout(set = 0, binding = 2) uniform sampler s_noise; void main() { textureLod(sampler2D(t_noise, s_noise), vec2(1.0), 0); } "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void fun(vec2 in_parameter, out float out_parameter) { ivec2 _ = ivec2(in_parameter); } void main() { float a; fun(vec2(1.0), a); } "#, ) .unwrap(); } #[test] fn constants() { use crate::{Constant, Expression, Type, TypeInner}; let mut frontend = Frontend::default(); let module = frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 const float a = 1.0; float global = a; const float b = a; void main() {} "#, ) .unwrap(); let mut types = module.types.iter(); let mut constants = module.constants.iter(); let mut global_expressions = module.global_expressions.iter(); let (ty_handle, ty) = types.next().unwrap(); assert_eq!( ty, &Type { name: None, inner: TypeInner::Scalar(crate::Scalar::F32) } ); let (init_handle, init) = global_expressions.next().unwrap(); assert_eq!(init, &Expression::Literal(crate::Literal::F32(1.0))); assert_eq!( constants.next().unwrap().1, &Constant { name: Some("a".to_owned()), ty: ty_handle, init: init_handle } ); assert_eq!( constants.next().unwrap().1, &Constant { name: Some("b".to_owned()), ty: ty_handle, init: init_handle } ); assert!(constants.next().is_none()); } #[test] fn function_overloading() { let mut frontend = Frontend::default(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 float saturate(float v) { return clamp(v, 0.0, 1.0); } vec2 saturate(vec2 v) { return clamp(v, vec2(0.0), vec2(1.0)); } vec3 saturate(vec3 v) { return clamp(v, vec3(0.0), vec3(1.0)); } vec4 saturate(vec4 v) { return clamp(v, vec4(0.0), vec4(1.0)); } void main() { float v1 = saturate(1.5); vec2 v2 = saturate(vec2(0.5, 1.5)); vec3 v3 = saturate(vec3(0.5, 1.5, 2.5)); vec3 v4 = saturate(vec4(0.5, 1.5, 2.5, 3.5)); } "#, ) .unwrap(); } #[test] fn implicit_conversions() { let mut frontend = Frontend::default(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void main() { mat4 a = mat4(1); float b = 1u; float c = 1 + 2.0; } "#, ) .unwrap(); assert_eq!( frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void test(int a) {} void test(uint a) {} void main() { test(1.0); } "#, ) .err() .unwrap(), ParseErrors { errors: vec![Error { kind: ErrorKind::SemanticError("Unknown function \'test\'".into()), meta: Span::new(156, 165), }] }, ); assert_eq!( frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void test(float a) {} void test(uint a) {} void main() { test(1); } "#, ) .err() .unwrap(), ParseErrors { errors: vec![Error { kind: ErrorKind::SemanticError("Ambiguous best function for \'test\'".into()), meta: Span::new(158, 165), }] } ); } #[test] fn structs() { let mut frontend = Frontend::default(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 Test { vec4 pos; } xx; void main() {} "#, ) .unwrap_err(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 struct Test { vec4 pos; }; void main() {} "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 const int NUM_VECS = 42; struct Test { vec4 vecs[NUM_VECS]; }; void main() {} "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 struct Hello { vec4 test; } test() { return Hello( vec4(1.0) ); } void main() {} "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 struct Test {}; void main() {} "#, ) .unwrap_err(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 inout struct Test { vec4 x; }; void main() {} "#, ) .unwrap_err(); } #[test] fn swizzles() { let mut frontend = Frontend::default(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void main() { vec4 v = vec4(1); v.xyz = vec3(2); v.x = 5.0; v.xyz.zxy.yx.xy = vec2(5.0, 1.0); } "#, ) .unwrap(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void main() { vec4 v = vec4(1); v.xx = vec2(5.0); } "#, ) .unwrap_err(); frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void main() { vec3 v = vec3(1); v.w = 2.0; } "#, ) .unwrap_err(); } #[test] fn expressions() { let mut frontend = Frontend::default(); // Vector indexing frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 float test(int index) { vec4 v = vec4(1.0, 2.0, 3.0, 4.0); return v[index] + 1.0; } void main() {} "#, ) .unwrap(); // Prefix increment/decrement frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void main() { uint index = 0; --index; ++index; } "#, ) .unwrap(); // Dynamic indexing of array frontend .parse( &Options::from(ShaderStage::Vertex), r#" # version 450 void main() { const vec4 positions[1] = { vec4(0) }; gl_Position = positions[gl_VertexIndex]; } "#, ) .unwrap(); } ================================================ FILE: naga/src/front/glsl/token.rs ================================================ pub use pp_rs::token::{Float, Integer, Location, Token as PPToken}; use alloc::{string::String, vec::Vec}; use super::ast::Precision; use crate::{Interpolation, Sampling, Span, Type}; impl From for Span { fn from(loc: Location) -> Self { Span::new(loc.start, loc.end) } } #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] pub struct Token { pub value: TokenValue, pub meta: Span, } /// A token passed from the lexing used in the parsing. /// /// This type is exported since it's returned in the /// [`InvalidToken`](super::ErrorKind::InvalidToken) error. #[derive(Clone, Debug, PartialEq)] pub enum TokenValue { Identifier(String), FloatConstant(Float), IntConstant(Integer), BoolConstant(bool), Layout, In, Out, InOut, Uniform, Buffer, Const, Shared, Restrict, /// A `glsl` memory qualifier such as `writeonly` /// /// The associated [`crate::StorageAccess`] is the access being allowed /// (for example `writeonly` has an associated value of [`crate::StorageAccess::STORE`]) MemoryQualifier(crate::StorageAccess), Invariant, Interpolation(Interpolation), Sampling(Sampling), Precision, PrecisionQualifier(Precision), Continue, Break, Return, Discard, If, Else, Switch, Case, Default, While, Do, For, Void, Struct, TypeName(Type), Assign, AddAssign, SubAssign, MulAssign, DivAssign, ModAssign, LeftShiftAssign, RightShiftAssign, AndAssign, XorAssign, OrAssign, Increment, Decrement, LogicalOr, LogicalAnd, LogicalXor, LessEqual, GreaterEqual, Equal, NotEqual, LeftShift, RightShift, LeftBrace, RightBrace, LeftParen, RightParen, LeftBracket, RightBracket, LeftAngle, RightAngle, Comma, Semicolon, Colon, Dot, Bang, Dash, Tilde, Plus, Star, Slash, Percent, VerticalBar, Caret, Ampersand, Question, } #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] pub struct Directive { pub kind: DirectiveKind, pub tokens: Vec, } #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] pub enum DirectiveKind { Version { is_first_directive: bool }, Extension, Pragma, } ================================================ FILE: naga/src/front/glsl/types.rs ================================================ use alloc::format; use super::{context::Context, Error, ErrorKind, Result, Span}; use crate::{ proc::ResolveContext, Expression, Handle, ImageClass, ImageDimension, Scalar, ScalarKind, Type, TypeInner, VectorSize, }; pub fn parse_type(type_name: &str) -> Option { match type_name { "bool" => Some(Type { name: None, inner: TypeInner::Scalar(Scalar::BOOL), }), "float16_t" => Some(Type { name: None, inner: TypeInner::Scalar(Scalar::F16), }), "float" => Some(Type { name: None, inner: TypeInner::Scalar(Scalar::F32), }), "double" => Some(Type { name: None, inner: TypeInner::Scalar(Scalar::F64), }), "int" => Some(Type { name: None, inner: TypeInner::Scalar(Scalar::I32), }), "uint" => Some(Type { name: None, inner: TypeInner::Scalar(Scalar::U32), }), "sampler" | "samplerShadow" => Some(Type { name: None, inner: TypeInner::Sampler { comparison: type_name == "samplerShadow", }, }), word => { fn kind_width_parse(ty: &str) -> Option { Some(match ty { "" => Scalar::F32, "b" => Scalar::BOOL, "i" => Scalar::I32, "u" => Scalar::U32, "d" => Scalar::F64, "f16" => Scalar::F16, _ => return None, }) } fn size_parse(n: &str) -> Option { Some(match n { "2" => VectorSize::Bi, "3" => VectorSize::Tri, "4" => VectorSize::Quad, _ => return None, }) } let vec_parse = |word: &str| { let mut iter = word.split("vec"); let kind = iter.next()?; let size = iter.next()?; let scalar = kind_width_parse(kind)?; let size = size_parse(size)?; Some(Type { name: None, inner: TypeInner::Vector { size, scalar }, }) }; let mat_parse = |word: &str| { let mut iter = word.split("mat"); let kind = iter.next()?; let size = iter.next()?; let scalar = kind_width_parse(kind)?; let (columns, rows) = if let Some(size) = size_parse(size) { (size, size) } else { let mut iter = size.split('x'); match (iter.next()?, iter.next()?, iter.next()) { (col, row, None) => (size_parse(col)?, size_parse(row)?), _ => return None, } }; Some(Type { name: None, inner: TypeInner::Matrix { columns, rows, scalar, }, }) }; let texture_parse = |word: &str| { let mut iter = word.split("texture"); let texture_kind = |ty| { Some(match ty { "" => ScalarKind::Float, "i" => ScalarKind::Sint, "u" => ScalarKind::Uint, _ => return None, }) }; let kind = iter.next()?; let size = iter.next()?; let kind = texture_kind(kind)?; let sampled = |multi| ImageClass::Sampled { kind, multi }; let (dim, arrayed, class) = match size { "1D" => (ImageDimension::D1, false, sampled(false)), "1DArray" => (ImageDimension::D1, true, sampled(false)), "2D" => (ImageDimension::D2, false, sampled(false)), "2DArray" => (ImageDimension::D2, true, sampled(false)), "2DMS" => (ImageDimension::D2, false, sampled(true)), "2DMSArray" => (ImageDimension::D2, true, sampled(true)), "3D" => (ImageDimension::D3, false, sampled(false)), "Cube" => (ImageDimension::Cube, false, sampled(false)), "CubeArray" => (ImageDimension::Cube, true, sampled(false)), _ => return None, }; Some(Type { name: None, inner: TypeInner::Image { dim, arrayed, class, }, }) }; let image_parse = |word: &str| { let mut iter = word.split("image"); let texture_kind = |ty| { Some(match ty { "" => ScalarKind::Float, "i" => ScalarKind::Sint, "u" => ScalarKind::Uint, _ => return None, }) }; let kind = iter.next()?; let size = iter.next()?; // TODO: Check that the texture format and the kind match let _ = texture_kind(kind)?; let class = ImageClass::Storage { format: crate::StorageFormat::R8Uint, access: crate::StorageAccess::LOAD | crate::StorageAccess::STORE, }; // TODO: glsl support multisampled storage images, naga doesn't let (dim, arrayed) = match size { "1D" => (ImageDimension::D1, false), "1DArray" => (ImageDimension::D1, true), "2D" => (ImageDimension::D2, false), "2DArray" => (ImageDimension::D2, true), "3D" => (ImageDimension::D3, false), // Naga doesn't support cube images and it's usefulness // is questionable, so they won't be supported for now // "Cube" => (ImageDimension::Cube, false), // "CubeArray" => (ImageDimension::Cube, true), _ => return None, }; Some(Type { name: None, inner: TypeInner::Image { dim, arrayed, class, }, }) }; vec_parse(word) .or_else(|| mat_parse(word)) .or_else(|| texture_parse(word)) .or_else(|| image_parse(word)) } } } pub const fn scalar_components(ty: &TypeInner) -> Option { match *ty { TypeInner::Scalar(scalar) | TypeInner::Vector { scalar, .. } | TypeInner::ValuePointer { scalar, .. } | TypeInner::Matrix { scalar, .. } => Some(scalar), _ => None, } } pub const fn type_power(scalar: Scalar) -> Option { Some(match scalar.kind { ScalarKind::Sint => 0, ScalarKind::Uint => 1, ScalarKind::Float if scalar.width == 4 => 2, ScalarKind::Float => 3, ScalarKind::Bool | ScalarKind::AbstractInt | ScalarKind::AbstractFloat => return None, }) } impl Context<'_> { /// Resolves the types of the expressions until `expr` (inclusive) /// /// This needs to be done before the [`typifier`] can be queried for /// the types of the expressions in the range between the last grow and `expr`. /// /// # Note /// /// The `resolve_type*` methods (like [`resolve_type`]) automatically /// grow the [`typifier`] so calling this method is not necessary when using /// them. /// /// [`typifier`]: Context::typifier /// [`resolve_type`]: Self::resolve_type pub(crate) fn typifier_grow(&mut self, expr: Handle, meta: Span) -> Result<()> { let resolve_ctx = ResolveContext::with_locals(self.module, &self.locals, &self.arguments); let typifier = if self.is_const { &mut self.const_typifier } else { &mut self.typifier }; let expressions = if self.is_const { &self.module.global_expressions } else { &self.expressions }; typifier .grow(expr, expressions, &resolve_ctx) .map_err(|error| Error { kind: ErrorKind::SemanticError(format!("Can't resolve type: {error:?}").into()), meta, }) } pub(crate) fn get_type(&self, expr: Handle) -> &TypeInner { let typifier = if self.is_const { &self.const_typifier } else { &self.typifier }; typifier.get(expr, &self.module.types) } /// Gets the type for the result of the `expr` expression /// /// Automatically grows the [`typifier`] to `expr` so calling /// [`typifier_grow`] is not necessary /// /// [`typifier`]: Context::typifier /// [`typifier_grow`]: Self::typifier_grow pub(crate) fn resolve_type( &mut self, expr: Handle, meta: Span, ) -> Result<&TypeInner> { self.typifier_grow(expr, meta)?; Ok(self.get_type(expr)) } /// Gets the type handle for the result of the `expr` expression /// /// Automatically grows the [`typifier`] to `expr` so calling /// [`typifier_grow`] is not necessary /// /// # Note /// /// Consider using [`resolve_type`] whenever possible /// since it doesn't require adding each type to the [`types`] arena /// and it doesn't need to mutably borrow the [`Parser`][Self] /// /// [`types`]: crate::Module::types /// [`typifier`]: Context::typifier /// [`typifier_grow`]: Self::typifier_grow /// [`resolve_type`]: Self::resolve_type pub(crate) fn resolve_type_handle( &mut self, expr: Handle, meta: Span, ) -> Result> { self.typifier_grow(expr, meta)?; let typifier = if self.is_const { &mut self.const_typifier } else { &mut self.typifier }; Ok(typifier.register_type(expr, &mut self.module.types)) } /// Invalidates the cached type resolution for `expr` forcing a recomputation pub(crate) fn invalidate_expression( &mut self, expr: Handle, meta: Span, ) -> Result<()> { let resolve_ctx = ResolveContext::with_locals(self.module, &self.locals, &self.arguments); let typifier = if self.is_const { &mut self.const_typifier } else { &mut self.typifier }; typifier .invalidate(expr, &self.expressions, &resolve_ctx) .map_err(|error| Error { kind: ErrorKind::SemanticError(format!("Can't resolve type: {error:?}").into()), meta, }) } pub(crate) fn lift_up_const_expression( &mut self, expr: Handle, ) -> Result> { let meta = self.expressions.get_span(expr); let h = match self.expressions[expr] { ref expr @ (Expression::Literal(_) | Expression::Constant(_) | Expression::ZeroValue(_)) => { self.module.global_expressions.append(expr.clone(), meta) } Expression::Compose { ty, ref components } => { let mut components = components.clone(); for component in &mut components { *component = self.lift_up_const_expression(*component)?; } self.module .global_expressions .append(Expression::Compose { ty, components }, meta) } Expression::Splat { size, value } => { let value = self.lift_up_const_expression(value)?; self.module .global_expressions .append(Expression::Splat { size, value }, meta) } _ => { return Err(Error { kind: ErrorKind::SemanticError("Expression is not const-expression".into()), meta, }) } }; self.global_expression_kind_tracker .insert(h, crate::proc::ExpressionKind::Const); Ok(h) } } ================================================ FILE: naga/src/front/glsl/variables.rs ================================================ use alloc::{format, string::String, vec::Vec}; use super::{ ast::*, context::{Context, ExprPos}, error::{Error, ErrorKind}, Frontend, Result, Span, }; use crate::{ AddressSpace, Binding, BuiltIn, Constant, Expression, GlobalVariable, Handle, Interpolation, LocalVariable, Override, ResourceBinding, Scalar, ScalarKind, ShaderStage, SwizzleComponent, Type, TypeInner, VectorSize, }; pub struct VarDeclaration<'a, 'key> { pub qualifiers: &'a mut TypeQualifiers<'key>, pub ty: Handle, pub name: Option, pub init: Option>, pub meta: Span, } /// Information about a builtin used in [`add_builtin`](Frontend::add_builtin). struct BuiltInData { /// The type of the builtin. inner: TypeInner, /// The associated builtin class. builtin: BuiltIn, /// Whether the builtin can be written to or not. mutable: bool, /// The storage used for the builtin. storage: StorageQualifier, } pub enum GlobalOrConstant { Global(Handle), Constant(Handle), Override(Handle), } impl Frontend { /// Adds a builtin and returns a variable reference to it fn add_builtin( &mut self, ctx: &mut Context, name: &str, data: BuiltInData, meta: Span, ) -> Result> { let ty = ctx.module.types.insert( Type { name: None, inner: data.inner, }, meta, ); let handle = ctx.module.global_variables.append( GlobalVariable { name: Some(name.into()), space: AddressSpace::Private, binding: None, ty, init: None, memory_decorations: crate::MemoryDecorations::empty(), }, meta, ); let idx = self.entry_args.len(); self.entry_args.push(EntryArg { name: Some(name.into()), binding: Binding::BuiltIn(data.builtin), handle, storage: data.storage, }); self.global_variables.push(( name.into(), GlobalLookup { kind: GlobalLookupKind::Variable(handle), entry_arg: Some(idx), mutable: data.mutable, }, )); let expr = ctx.add_expression(Expression::GlobalVariable(handle), meta)?; let var = VariableReference { expr, load: true, mutable: data.mutable, constant: None, entry_arg: Some(idx), }; ctx.symbol_table.add_root(name.into(), var.clone()); Ok(Some(var)) } pub(crate) fn lookup_variable( &mut self, ctx: &mut Context, name: &str, meta: Span, ) -> Result> { if let Some(var) = ctx.symbol_table.lookup(name).cloned() { return Ok(Some(var)); } let data = match name { "gl_Position" => BuiltInData { inner: TypeInner::Vector { size: VectorSize::Quad, scalar: Scalar::F32, }, builtin: BuiltIn::Position { invariant: false }, mutable: true, storage: StorageQualifier::Output, }, "gl_FragCoord" => BuiltInData { inner: TypeInner::Vector { size: VectorSize::Quad, scalar: Scalar::F32, }, builtin: BuiltIn::Position { invariant: false }, mutable: false, storage: StorageQualifier::Input, }, "gl_PointCoord" => BuiltInData { inner: TypeInner::Vector { size: VectorSize::Bi, scalar: Scalar::F32, }, builtin: BuiltIn::PointCoord, mutable: false, storage: StorageQualifier::Input, }, "gl_GlobalInvocationID" | "gl_NumWorkGroups" | "gl_WorkGroupSize" | "gl_WorkGroupID" | "gl_LocalInvocationID" => BuiltInData { inner: TypeInner::Vector { size: VectorSize::Tri, scalar: Scalar::U32, }, builtin: match name { "gl_GlobalInvocationID" => BuiltIn::GlobalInvocationId, "gl_NumWorkGroups" => BuiltIn::NumWorkGroups, "gl_WorkGroupSize" => BuiltIn::WorkGroupSize, "gl_WorkGroupID" => BuiltIn::WorkGroupId, "gl_LocalInvocationID" => BuiltIn::LocalInvocationId, _ => unreachable!(), }, mutable: false, storage: StorageQualifier::Input, }, "gl_FrontFacing" => BuiltInData { inner: TypeInner::Scalar(Scalar::BOOL), builtin: BuiltIn::FrontFacing, mutable: false, storage: StorageQualifier::Input, }, "gl_PointSize" | "gl_FragDepth" => BuiltInData { inner: TypeInner::Scalar(Scalar::F32), builtin: match name { "gl_PointSize" => BuiltIn::PointSize, "gl_FragDepth" => BuiltIn::FragDepth, _ => unreachable!(), }, mutable: true, storage: StorageQualifier::Output, }, "gl_ClipDistance" | "gl_CullDistance" => { let base = ctx.module.types.insert( Type { name: None, inner: TypeInner::Scalar(Scalar::F32), }, meta, ); BuiltInData { inner: TypeInner::Array { base, size: crate::ArraySize::Dynamic, stride: 4, }, builtin: match name { "gl_ClipDistance" => BuiltIn::ClipDistances, "gl_CullDistance" => BuiltIn::CullDistance, _ => unreachable!(), }, mutable: self.meta.stage == ShaderStage::Vertex, storage: StorageQualifier::Output, } } _ => { let builtin = match name { "gl_BaseVertex" => BuiltIn::BaseVertex, "gl_BaseInstance" => BuiltIn::BaseInstance, "gl_PrimitiveID" => BuiltIn::PrimitiveIndex, "gl_BaryCoordEXT" => BuiltIn::Barycentric { perspective: true }, "gl_BaryCoordNoPerspEXT" => BuiltIn::Barycentric { perspective: false }, "gl_InstanceIndex" => BuiltIn::InstanceIndex, "gl_VertexIndex" => BuiltIn::VertexIndex, "gl_SampleID" => BuiltIn::SampleIndex, "gl_LocalInvocationIndex" => BuiltIn::LocalInvocationIndex, "gl_DrawID" => BuiltIn::DrawIndex, _ => return Ok(None), }; BuiltInData { inner: TypeInner::Scalar(Scalar::U32), builtin, mutable: false, storage: StorageQualifier::Input, } } }; self.add_builtin(ctx, name, data, meta) } pub(crate) fn make_variable_invariant( &mut self, ctx: &mut Context, name: &str, meta: Span, ) -> Result<()> { if let Some(var) = self.lookup_variable(ctx, name, meta)? { if let Some(index) = var.entry_arg { if let Binding::BuiltIn(BuiltIn::Position { ref mut invariant }) = self.entry_args[index].binding { *invariant = true; } } } Ok(()) } pub(crate) fn field_selection( &mut self, ctx: &mut Context, pos: ExprPos, expression: Handle, name: &str, meta: Span, ) -> Result> { let (ty, is_pointer) = match *ctx.resolve_type(expression, meta)? { TypeInner::Pointer { base, .. } => (&ctx.module.types[base].inner, true), ref ty => (ty, false), }; match *ty { TypeInner::Struct { ref members, .. } => { let index = members .iter() .position(|m| m.name == Some(name.into())) .ok_or_else(|| Error { kind: ErrorKind::UnknownField(name.into()), meta, })?; let pointer = ctx.add_expression( Expression::AccessIndex { base: expression, index: index as u32, }, meta, )?; Ok(match pos { ExprPos::Rhs if is_pointer => { ctx.add_expression(Expression::Load { pointer }, meta)? } _ => pointer, }) } // swizzles (xyzw, rgba, stpq) TypeInner::Vector { size, .. } => { let check_swizzle_components = |comps: &str| { name.chars() .map(|c| { comps .find(c) .filter(|i| *i < size as usize) .map(|i| SwizzleComponent::from_index(i as u32)) }) .collect::>>() }; let components = check_swizzle_components("xyzw") .or_else(|| check_swizzle_components("rgba")) .or_else(|| check_swizzle_components("stpq")); if let Some(components) = components { if let ExprPos::Lhs = pos { let not_unique = (1..components.len()) .any(|i| components[i..].contains(&components[i - 1])); if not_unique { self.errors.push(Error { kind: ErrorKind::SemanticError( format!( concat!( "swizzle cannot have duplicate components in ", "left-hand-side expression for \"{:?}\"" ), name ) .into(), ), meta, }) } } let mut pattern = [SwizzleComponent::X; 4]; for (pat, component) in pattern.iter_mut().zip(&components) { *pat = *component; } // flatten nested swizzles (vec.zyx.xy.x => vec.z) let mut expression = expression; if let Expression::Swizzle { size: _, vector, pattern: ref src_pattern, } = ctx[expression] { expression = vector; for pat in &mut pattern { *pat = src_pattern[pat.index() as usize]; } } let size = match components.len() { // Swizzles with just one component are accesses and not swizzles 1 => { match pos { // If the position is in the right hand side and the base // vector is a pointer, load it, otherwise the swizzle would // produce a pointer ExprPos::Rhs if is_pointer => { expression = ctx.add_expression( Expression::Load { pointer: expression, }, meta, )?; } _ => {} }; return ctx.add_expression( Expression::AccessIndex { base: expression, index: pattern[0].index(), }, meta, ); } 2 => VectorSize::Bi, 3 => VectorSize::Tri, 4 => VectorSize::Quad, _ => { self.errors.push(Error { kind: ErrorKind::SemanticError( format!("Bad swizzle size for \"{name:?}\"").into(), ), meta, }); VectorSize::Quad } }; if is_pointer { // NOTE: for lhs expression, this extra load ends up as an unused expr, because the // assignment will extract the pointer and use it directly anyway. Unfortunately we // need it for validation to pass, as swizzles cannot operate on pointer values. expression = ctx.add_expression( Expression::Load { pointer: expression, }, meta, )?; } Ok(ctx.add_expression( Expression::Swizzle { size, vector: expression, pattern, }, meta, )?) } else { Err(Error { kind: ErrorKind::SemanticError( format!("Invalid swizzle for vector \"{name}\"").into(), ), meta, }) } } _ => Err(Error { kind: ErrorKind::SemanticError( format!("Can't lookup field on this type \"{name}\"").into(), ), meta, }), } } pub(crate) fn add_global_var( &mut self, ctx: &mut Context, VarDeclaration { qualifiers, mut ty, name, init, meta, }: VarDeclaration, ) -> Result { let storage = qualifiers.storage.0; let (ret, lookup) = match storage { StorageQualifier::Input | StorageQualifier::Output => { let input = storage == StorageQualifier::Input; // TODO: glslang seems to use a counter for variables without // explicit location (even if that causes collisions) let location = qualifiers .uint_layout_qualifier("location", &mut self.errors) .unwrap_or(0); let interpolation = qualifiers.interpolation.take().map(|(i, _)| i).or_else(|| { let kind = ctx.module.types[ty].inner.scalar_kind()?; Some(match kind { ScalarKind::Float => Interpolation::Perspective, _ => Interpolation::Flat, }) }); let sampling = qualifiers.sampling.take().map(|(s, _)| s); let handle = ctx.module.global_variables.append( GlobalVariable { name: name.clone(), space: AddressSpace::Private, binding: None, ty, init, memory_decorations: crate::MemoryDecorations::empty(), }, meta, ); let blend_src = qualifiers .layout_qualifiers .remove(&QualifierKey::Index) .and_then(|(value, _span)| match value { QualifierValue::Uint(index) => Some(index), _ => None, }); let idx = self.entry_args.len(); self.entry_args.push(EntryArg { name: name.clone(), binding: Binding::Location { location, interpolation, sampling, blend_src, per_primitive: false, }, handle, storage, }); let lookup = GlobalLookup { kind: GlobalLookupKind::Variable(handle), entry_arg: Some(idx), mutable: !input, }; (GlobalOrConstant::Global(handle), lookup) } StorageQualifier::Const => { // Check if this is a specialization constant with constant_id let constant_id = qualifiers.uint_layout_qualifier("constant_id", &mut self.errors); if let Some(id) = constant_id { // This is a specialization constant - convert to Override let id: Option = match id.try_into() { Ok(v) => Some(v), Err(_) => { self.errors.push(Error { kind: ErrorKind::SemanticError( format!( "constant_id value {id} is too high (maximum is {})", u16::MAX ) .into(), ), meta, }); None } }; let override_handle = ctx.module.overrides.append( Override { name: name.clone(), id, ty, init, }, meta, ); let lookup = GlobalLookup { kind: GlobalLookupKind::Override(override_handle, ty), entry_arg: None, mutable: false, }; (GlobalOrConstant::Override(override_handle), lookup) } else { // Regular constant let init = init.ok_or_else(|| Error { kind: ErrorKind::SemanticError( "const values must have an initializer".into(), ), meta, })?; let constant = Constant { name: name.clone(), ty, init, }; let handle = ctx.module.constants.append(constant, meta); let lookup = GlobalLookup { kind: GlobalLookupKind::Constant(handle, ty), entry_arg: None, mutable: false, }; (GlobalOrConstant::Constant(handle), lookup) } } StorageQualifier::AddressSpace(mut space) => { match space { AddressSpace::Storage { ref mut access } => { if let Some((allowed_access, _)) = qualifiers.storage_access.take() { *access = allowed_access; } } AddressSpace::Uniform => match ctx.module.types[ty].inner { TypeInner::Image { class, dim, arrayed, } => { if let crate::ImageClass::Storage { mut access, mut format, } = class { if let Some((allowed_access, _)) = qualifiers.storage_access.take() { access = allowed_access; } match qualifiers.layout_qualifiers.remove(&QualifierKey::Format) { Some((QualifierValue::Format(f), _)) => format = f, // TODO: glsl supports images without format qualifier // if they are `writeonly` None => self.errors.push(Error { kind: ErrorKind::SemanticError( "image types require a format layout qualifier".into(), ), meta, }), _ => unreachable!(), } ty = ctx.module.types.insert( Type { name: None, inner: TypeInner::Image { dim, arrayed, class: crate::ImageClass::Storage { format, access }, }, }, meta, ); } space = AddressSpace::Handle } TypeInner::Sampler { .. } => space = AddressSpace::Handle, _ => { if qualifiers.none_layout_qualifier("push_constant", &mut self.errors) { space = AddressSpace::Immediate } } }, AddressSpace::Function => space = AddressSpace::Private, _ => {} }; let binding = match space { AddressSpace::Uniform | AddressSpace::Storage { .. } | AddressSpace::Handle => { let binding = qualifiers.uint_layout_qualifier("binding", &mut self.errors); if binding.is_none() { self.errors.push(Error { kind: ErrorKind::SemanticError( "uniform/buffer blocks require layout(binding=X)".into(), ), meta, }); } let set = qualifiers.uint_layout_qualifier("set", &mut self.errors); binding.map(|binding| ResourceBinding { group: set.unwrap_or(0), binding, }) } _ => None, }; let handle = ctx.module.global_variables.append( GlobalVariable { name: name.clone(), space, binding, ty, init, memory_decorations: crate::MemoryDecorations::empty(), }, meta, ); let lookup = GlobalLookup { kind: GlobalLookupKind::Variable(handle), entry_arg: None, mutable: true, }; (GlobalOrConstant::Global(handle), lookup) } }; if let Some(name) = name { ctx.add_global(&name, lookup)?; self.global_variables.push((name, lookup)); } qualifiers.unused_errors(&mut self.errors); Ok(ret) } pub(crate) fn add_local_var( &mut self, ctx: &mut Context, decl: VarDeclaration, ) -> Result> { let storage = decl.qualifiers.storage; let mutable = match storage.0 { StorageQualifier::AddressSpace(AddressSpace::Function) => true, StorageQualifier::Const => false, _ => { self.errors.push(Error { kind: ErrorKind::SemanticError("Locals cannot have a storage qualifier".into()), meta: storage.1, }); true } }; let handle = ctx.locals.append( LocalVariable { name: decl.name.clone(), ty: decl.ty, init: decl.init, }, decl.meta, ); let expr = ctx.add_expression(Expression::LocalVariable(handle), decl.meta)?; if let Some(name) = decl.name { let maybe_var = ctx.add_local_var(name.clone(), expr, mutable); if maybe_var.is_some() { self.errors.push(Error { kind: ErrorKind::VariableAlreadyDeclared(name), meta: decl.meta, }) } } decl.qualifiers.unused_errors(&mut self.errors); Ok(expr) } } ================================================ FILE: naga/src/front/interpolator.rs ================================================ /*! Interpolation defaults. */ impl crate::Binding { /// Apply the usual default interpolation for `ty` to `binding`. /// /// This function is a utility front ends may use to satisfy the Naga IR's /// requirement, meant to ensure that input languages' policies have been /// applied appropriately, that all I/O `Binding`s from the vertex shader to the /// fragment shader must have non-`None` `interpolation` values. /// /// All the shader languages Naga supports have similar rules: /// perspective-correct, center-sampled interpolation is the default for any /// binding that can vary, and everything else either defaults to flat, or /// requires an explicit flat qualifier/attribute/what-have-you. /// /// If `binding` is not a [`Location`] binding, or if its [`interpolation`] is /// already set, then make no changes. Otherwise, set `binding`'s interpolation /// and sampling to reasonable defaults depending on `ty`, the type of the value /// being interpolated: /// /// - If `ty` is a floating-point scalar, vector, or matrix type, then /// default to [`Perspective`] interpolation and [`Center`] sampling. /// /// - If `ty` is an integral scalar or vector, then default to [`Flat`] /// interpolation, which has no associated sampling. /// /// - For any other types, make no change. Such types are not permitted as /// user-defined IO values, and will probably be flagged by the verifier /// /// When structs appear in input or output types, each member ought to have its /// own [`Binding`], so structs are simply covered by the third case. /// /// [`Binding`]: crate::Binding /// [`Location`]: crate::Binding::Location /// [`interpolation`]: crate::Binding::Location::interpolation /// [`Perspective`]: crate::Interpolation::Perspective /// [`Flat`]: crate::Interpolation::Flat /// [`Center`]: crate::Sampling::Center pub fn apply_default_interpolation(&mut self, ty: &crate::TypeInner) { if let crate::Binding::Location { location: _, interpolation: ref mut interpolation @ None, ref mut sampling, blend_src: _, per_primitive: _, } = *self { match ty.scalar_kind() { Some(crate::ScalarKind::Float) => { *interpolation = Some(crate::Interpolation::Perspective); *sampling = Some(crate::Sampling::Center); } Some(crate::ScalarKind::Sint | crate::ScalarKind::Uint) => { *interpolation = Some(crate::Interpolation::Flat); *sampling = None; } Some(_) | None => {} } } } } ================================================ FILE: naga/src/front/mod.rs ================================================ /*! Frontend parsers that consume binary and text shaders and load them into [`Module`](super::Module)s. */ mod interpolator; mod type_gen; #[cfg(feature = "spv-in")] pub mod atomic_upgrade; #[cfg(feature = "glsl-in")] pub mod glsl; #[cfg(feature = "spv-in")] pub mod spv; #[cfg(feature = "wgsl-in")] pub mod wgsl; use alloc::{boxed::Box, vec, vec::Vec}; use core::ops; use crate::{ arena::{Arena, Handle, HandleVec, UniqueArena}, proc::{ResolveContext, ResolveError, TypeResolution}, FastHashMap, }; /// A table of types for an `Arena`. /// /// A front end can use a `Typifier` to get types for an arena's expressions /// while it is still contributing expressions to it. At any point, you can call /// [`typifier.grow(expr, arena, ctx)`], where `expr` is a `Handle` /// referring to something in `arena`, and the `Typifier` will resolve the types /// of all the expressions up to and including `expr`. Then you can write /// `typifier[handle]` to get the type of any handle at or before `expr`. /// /// Note that `Typifier` does *not* build an `Arena` as a part of its /// usual operation. Ideally, a module's type arena should only contain types /// actually needed by `Handle`s elsewhere in the module — functions, /// variables, [`Compose`] expressions, other types, and so on — so we don't /// want every little thing that occurs as the type of some intermediate /// expression to show up there. /// /// Instead, `Typifier` accumulates a [`TypeResolution`] for each expression, /// which refers to the `Arena` in the [`ResolveContext`] passed to `grow` /// as needed. [`TypeResolution`] is a lightweight representation for /// intermediate types like this; see its documentation for details. /// /// If you do need to register a `Typifier`'s conclusion in an `Arena` /// (say, for a [`LocalVariable`] whose type you've inferred), you can use /// [`register_type`] to do so. /// /// [`typifier.grow(expr, arena)`]: Typifier::grow /// [`register_type`]: Typifier::register_type /// [`Compose`]: crate::Expression::Compose /// [`LocalVariable`]: crate::LocalVariable #[derive(Debug, Default)] pub struct Typifier { resolutions: HandleVec, } impl Typifier { pub const fn new() -> Self { Typifier { resolutions: HandleVec::new(), } } pub fn reset(&mut self) { self.resolutions.clear() } pub fn get<'a>( &'a self, expr_handle: Handle, types: &'a UniqueArena, ) -> &'a crate::TypeInner { self.resolutions[expr_handle].inner_with(types) } /// Add an expression's type to an `Arena`. /// /// Add the type of `expr_handle` to `types`, and return a `Handle` /// referring to it. /// /// # Note /// /// If you just need a [`TypeInner`] for `expr_handle`'s type, consider /// using `typifier[expression].inner_with(types)` instead. Calling /// [`TypeResolution::inner_with`] often lets us avoid adding anything to /// the arena, which can significantly reduce the number of types that end /// up in the final module. /// /// [`TypeInner`]: crate::TypeInner pub fn register_type( &self, expr_handle: Handle, types: &mut UniqueArena, ) -> Handle { match self[expr_handle].clone() { TypeResolution::Handle(handle) => handle, TypeResolution::Value(inner) => { types.insert(crate::Type { name: None, inner }, crate::Span::UNDEFINED) } } } /// Grow this typifier until it contains a type for `expr_handle`. pub fn grow( &mut self, expr_handle: Handle, expressions: &Arena, ctx: &ResolveContext, ) -> Result<(), ResolveError> { if self.resolutions.len() <= expr_handle.index() { for (eh, expr) in expressions.iter().skip(self.resolutions.len()) { //Note: the closure can't `Err` by construction let resolution = ctx.resolve(expr, |h| Ok(&self.resolutions[h]))?; log::debug!("Resolving {eh:?} = {expr:?} : {resolution:?}"); self.resolutions.insert(eh, resolution); } } Ok(()) } /// Recompute the type resolution for `expr_handle`. /// /// If the type of `expr_handle` hasn't yet been calculated, call /// [`grow`](Self::grow) to ensure it is covered. /// /// In either case, when this returns, `self[expr_handle]` should be an /// updated type resolution for `expr_handle`. pub fn invalidate( &mut self, expr_handle: Handle, expressions: &Arena, ctx: &ResolveContext, ) -> Result<(), ResolveError> { if self.resolutions.len() <= expr_handle.index() { self.grow(expr_handle, expressions, ctx) } else { let expr = &expressions[expr_handle]; //Note: the closure can't `Err` by construction let resolution = ctx.resolve(expr, |h| Ok(&self.resolutions[h]))?; self.resolutions[expr_handle] = resolution; Ok(()) } } } impl ops::Index> for Typifier { type Output = TypeResolution; fn index(&self, handle: Handle) -> &Self::Output { &self.resolutions[handle] } } /// Type representing a lexical scope, associating a name to a single variable /// /// The scope is generic over the variable representation and name representation /// in order to allow larger flexibility on the frontends on how they might /// represent them. type Scope = FastHashMap; /// Structure responsible for managing variable lookups and keeping track of /// lexical scopes /// /// The symbol table is generic over the variable representation and its name /// to allow larger flexibility on the frontends on how they might represent them. /// /// ``` /// use naga::front::SymbolTable; /// /// // Create a new symbol table with `u32`s representing the variable /// let mut symbol_table: SymbolTable<&str, u32> = SymbolTable::default(); /// /// // Add two variables named `var1` and `var2` with 0 and 2 respectively /// symbol_table.add("var1", 0); /// symbol_table.add("var2", 2); /// /// // Check that `var1` exists and is `0` /// assert_eq!(symbol_table.lookup("var1"), Some(&0)); /// /// // Push a new scope and add a variable to it named `var1` shadowing the /// // variable of our previous scope /// symbol_table.push_scope(); /// symbol_table.add("var1", 1); /// /// // Check that `var1` now points to the new value of `1` and `var2` still /// // exists with its value of `2` /// assert_eq!(symbol_table.lookup("var1"), Some(&1)); /// assert_eq!(symbol_table.lookup("var2"), Some(&2)); /// /// // Pop the scope /// symbol_table.pop_scope(); /// /// // Check that `var1` now refers to our initial variable with value `0` /// assert_eq!(symbol_table.lookup("var1"), Some(&0)); /// ``` /// /// Scopes are ordered as a LIFO stack so a variable defined in a later scope /// with the same name as another variable defined in a earlier scope will take /// precedence in the lookup. Scopes can be added with [`push_scope`] and /// removed with [`pop_scope`]. /// /// A root scope is added when the symbol table is created and must always be /// present. Trying to pop it will result in a panic. /// /// Variables can be added with [`add`] and looked up with [`lookup`]. Adding a /// variable will do so in the currently active scope and as mentioned /// previously a lookup will search from the current scope to the root scope. /// /// [`push_scope`]: Self::push_scope /// [`pop_scope`]: Self::push_scope /// [`add`]: Self::add /// [`lookup`]: Self::lookup pub struct SymbolTable { /// Stack of lexical scopes. Not all scopes are active; see [`cursor`]. /// /// [`cursor`]: Self::cursor scopes: Vec>, /// Limit of the [`scopes`] stack (exclusive). By using a separate value for /// the stack length instead of `Vec`'s own internal length, the scopes can /// be reused to cache memory allocations. /// /// [`scopes`]: Self::scopes cursor: usize, lookup_cursor_is_one_behind: bool, } impl SymbolTable { /// Adds a new lexical scope. /// /// All variables declared after this point will be added to this scope /// until another scope is pushed or [`pop_scope`] is called, causing this /// scope to be removed along with all variables added to it. /// /// # PANICS /// - If the current lookup scope doesn't match the current scope /// /// [`pop_scope`]: Self::pop_scope pub fn push_scope(&mut self) { self.check_lookup_scope_matches_current_scope(); // If the cursor is equal to the scope's stack length then we need to // push another empty scope. Otherwise we can reuse the already existing // scope. if self.scopes.len() == self.cursor { self.scopes.push(FastHashMap::default()) } else { self.scopes[self.cursor].clear(); } self.cursor += 1; } /// Removes the current lexical scope and all its variables /// /// # PANICS /// - If the current lexical scope is the root scope /// - If the current lookup scope doesn't match the current scope pub fn pop_scope(&mut self) { // Despite the method title, the variables are only deleted when the // scope is reused. This is because while a clear is inevitable if the // scope needs to be reused, there are cases where the scope might be // popped and not reused, i.e. if another scope with the same nesting // level is never pushed again. assert!(self.cursor != 1, "Tried to pop the root scope"); self.check_lookup_scope_matches_current_scope(); self.cursor -= 1; } /// Reduces the lookup scope by one level. /// /// # PANICS /// - If the current lookup scope doesn't match the current scope pub fn reduce_lookup_scope(&mut self) { self.check_lookup_scope_matches_current_scope(); self.lookup_cursor_is_one_behind = true; } /// Resets the lookup scope to the current scope. /// /// # PANICS /// - If the current lookup scope already matches the current scope pub fn reset_lookup_scope(&mut self) { assert!( self.lookup_cursor_is_one_behind, "current lookup scope already matches the current scope" ); self.lookup_cursor_is_one_behind = false; } fn check_lookup_scope_matches_current_scope(&self) { assert!( !self.lookup_cursor_is_one_behind, "current lookup scope doesn't match the current scope" ); } } impl SymbolTable where Name: core::hash::Hash + Eq, { /// Perform a lookup for a variable named `name`. /// /// As stated in the struct level documentation the lookup will proceed from /// the current scope to the root scope, returning `Some` when a variable is /// found or `None` if there doesn't exist a variable with `name` in any /// scope. pub fn lookup(&self, name: &Q) -> Option<&Var> where Name: core::borrow::Borrow, Q: core::hash::Hash + Eq + ?Sized, { let cursor = self .cursor .saturating_sub(self.lookup_cursor_is_one_behind.into()); // Iterate backwards through the scopes and try to find the variable for scope in self.scopes[..cursor].iter().rev() { if let Some(var) = scope.get(name) { return Some(var); } } None } /// Adds a new variable to the current scope. /// /// Returns the previous variable with the same name in this scope if it /// exists, so that the frontend might handle it in case variable shadowing /// is disallowed. pub fn add(&mut self, name: Name, var: Var) -> Option { self.scopes[self.cursor - 1].insert(name, var) } /// Adds a new variable to the root scope. /// /// This is used in GLSL for builtins which aren't known in advance and only /// when used for the first time, so there must be a way to add those /// declarations to the root unconditionally from the current scope. /// /// Returns the previous variable with the same name in the root scope if it /// exists, so that the frontend might handle it in case variable shadowing /// is disallowed. pub fn add_root(&mut self, name: Name, var: Var) -> Option { self.scopes[0].insert(name, var) } } impl Default for SymbolTable { /// Constructs a new symbol table with a root scope fn default() -> Self { Self { scopes: vec![FastHashMap::default()], cursor: 1, lookup_cursor_is_one_behind: false, } } } use core::fmt; impl fmt::Debug for SymbolTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("SymbolTable ")?; f.debug_list() .entries(self.scopes[..self.cursor].iter()) .finish() } } impl crate::Module { pub fn get_or_insert_default_doc_comments(&mut self) -> &mut Box { self.doc_comments .get_or_insert_with(|| Box::new(crate::DocComments::default())) } } ================================================ FILE: naga/src/front/spv/convert.rs ================================================ use core::convert::TryInto; use super::error::Error; pub(super) const fn map_binary_operator(word: spirv::Op) -> Result { use crate::BinaryOperator; use spirv::Op; match word { // Arithmetic Instructions +, -, *, /, % Op::IAdd | Op::FAdd => Ok(BinaryOperator::Add), Op::ISub | Op::FSub => Ok(BinaryOperator::Subtract), Op::IMul | Op::FMul => Ok(BinaryOperator::Multiply), Op::UDiv | Op::SDiv | Op::FDiv => Ok(BinaryOperator::Divide), Op::SRem => Ok(BinaryOperator::Modulo), // Relational and Logical Instructions Op::IEqual | Op::FOrdEqual | Op::FUnordEqual | Op::LogicalEqual => { Ok(BinaryOperator::Equal) } Op::INotEqual | Op::FOrdNotEqual | Op::FUnordNotEqual | Op::LogicalNotEqual => { Ok(BinaryOperator::NotEqual) } Op::ULessThan | Op::SLessThan | Op::FOrdLessThan | Op::FUnordLessThan => { Ok(BinaryOperator::Less) } Op::ULessThanEqual | Op::SLessThanEqual | Op::FOrdLessThanEqual | Op::FUnordLessThanEqual => Ok(BinaryOperator::LessEqual), Op::UGreaterThan | Op::SGreaterThan | Op::FOrdGreaterThan | Op::FUnordGreaterThan => { Ok(BinaryOperator::Greater) } Op::UGreaterThanEqual | Op::SGreaterThanEqual | Op::FOrdGreaterThanEqual | Op::FUnordGreaterThanEqual => Ok(BinaryOperator::GreaterEqual), Op::BitwiseOr => Ok(BinaryOperator::InclusiveOr), Op::BitwiseXor => Ok(BinaryOperator::ExclusiveOr), Op::BitwiseAnd => Ok(BinaryOperator::And), _ => Err(Error::UnknownBinaryOperator(word)), } } pub(super) const fn map_relational_fun( word: spirv::Op, ) -> Result { use crate::RelationalFunction as Rf; use spirv::Op; match word { Op::All => Ok(Rf::All), Op::Any => Ok(Rf::Any), Op::IsNan => Ok(Rf::IsNan), Op::IsInf => Ok(Rf::IsInf), _ => Err(Error::UnknownRelationalFunction(word)), } } pub(super) const fn map_vector_size(word: spirv::Word) -> Result { match word { 2 => Ok(crate::VectorSize::Bi), 3 => Ok(crate::VectorSize::Tri), 4 => Ok(crate::VectorSize::Quad), _ => Err(Error::InvalidVectorSize(word)), } } pub(super) fn map_image_dim(word: spirv::Word) -> Result { use spirv::Dim as D; match D::from_u32(word) { Some(D::Dim1D) => Ok(crate::ImageDimension::D1), Some(D::Dim2D) => Ok(crate::ImageDimension::D2), Some(D::Dim3D) => Ok(crate::ImageDimension::D3), Some(D::DimCube) => Ok(crate::ImageDimension::Cube), _ => Err(Error::UnsupportedImageDim(word)), } } pub(super) fn map_image_format(word: spirv::Word) -> Result { match spirv::ImageFormat::from_u32(word) { Some(spirv::ImageFormat::R8) => Ok(crate::StorageFormat::R8Unorm), Some(spirv::ImageFormat::R8Snorm) => Ok(crate::StorageFormat::R8Snorm), Some(spirv::ImageFormat::R8ui) => Ok(crate::StorageFormat::R8Uint), Some(spirv::ImageFormat::R8i) => Ok(crate::StorageFormat::R8Sint), Some(spirv::ImageFormat::R16) => Ok(crate::StorageFormat::R16Unorm), Some(spirv::ImageFormat::R16Snorm) => Ok(crate::StorageFormat::R16Snorm), Some(spirv::ImageFormat::R16ui) => Ok(crate::StorageFormat::R16Uint), Some(spirv::ImageFormat::R16i) => Ok(crate::StorageFormat::R16Sint), Some(spirv::ImageFormat::R16f) => Ok(crate::StorageFormat::R16Float), Some(spirv::ImageFormat::Rg8) => Ok(crate::StorageFormat::Rg8Unorm), Some(spirv::ImageFormat::Rg8Snorm) => Ok(crate::StorageFormat::Rg8Snorm), Some(spirv::ImageFormat::Rg8ui) => Ok(crate::StorageFormat::Rg8Uint), Some(spirv::ImageFormat::Rg8i) => Ok(crate::StorageFormat::Rg8Sint), Some(spirv::ImageFormat::R32ui) => Ok(crate::StorageFormat::R32Uint), Some(spirv::ImageFormat::R32i) => Ok(crate::StorageFormat::R32Sint), Some(spirv::ImageFormat::R32f) => Ok(crate::StorageFormat::R32Float), Some(spirv::ImageFormat::Rg16) => Ok(crate::StorageFormat::Rg16Unorm), Some(spirv::ImageFormat::Rg16Snorm) => Ok(crate::StorageFormat::Rg16Snorm), Some(spirv::ImageFormat::Rg16ui) => Ok(crate::StorageFormat::Rg16Uint), Some(spirv::ImageFormat::Rg16i) => Ok(crate::StorageFormat::Rg16Sint), Some(spirv::ImageFormat::Rg16f) => Ok(crate::StorageFormat::Rg16Float), Some(spirv::ImageFormat::Rgba8) => Ok(crate::StorageFormat::Rgba8Unorm), Some(spirv::ImageFormat::Rgba8Snorm) => Ok(crate::StorageFormat::Rgba8Snorm), Some(spirv::ImageFormat::Rgba8ui) => Ok(crate::StorageFormat::Rgba8Uint), Some(spirv::ImageFormat::Rgba8i) => Ok(crate::StorageFormat::Rgba8Sint), Some(spirv::ImageFormat::Rgb10a2ui) => Ok(crate::StorageFormat::Rgb10a2Uint), Some(spirv::ImageFormat::Rgb10A2) => Ok(crate::StorageFormat::Rgb10a2Unorm), Some(spirv::ImageFormat::R11fG11fB10f) => Ok(crate::StorageFormat::Rg11b10Ufloat), Some(spirv::ImageFormat::R64ui) => Ok(crate::StorageFormat::R64Uint), Some(spirv::ImageFormat::Rg32ui) => Ok(crate::StorageFormat::Rg32Uint), Some(spirv::ImageFormat::Rg32i) => Ok(crate::StorageFormat::Rg32Sint), Some(spirv::ImageFormat::Rg32f) => Ok(crate::StorageFormat::Rg32Float), Some(spirv::ImageFormat::Rgba16) => Ok(crate::StorageFormat::Rgba16Unorm), Some(spirv::ImageFormat::Rgba16Snorm) => Ok(crate::StorageFormat::Rgba16Snorm), Some(spirv::ImageFormat::Rgba16ui) => Ok(crate::StorageFormat::Rgba16Uint), Some(spirv::ImageFormat::Rgba16i) => Ok(crate::StorageFormat::Rgba16Sint), Some(spirv::ImageFormat::Rgba16f) => Ok(crate::StorageFormat::Rgba16Float), Some(spirv::ImageFormat::Rgba32ui) => Ok(crate::StorageFormat::Rgba32Uint), Some(spirv::ImageFormat::Rgba32i) => Ok(crate::StorageFormat::Rgba32Sint), Some(spirv::ImageFormat::Rgba32f) => Ok(crate::StorageFormat::Rgba32Float), _ => Err(Error::UnsupportedImageFormat(word)), } } pub(super) fn map_width(word: spirv::Word) -> Result { (word >> 3) // bits to bytes .try_into() .map_err(|_| Error::InvalidTypeWidth(word)) } pub(super) fn map_builtin(word: spirv::Word, invariant: bool) -> Result { use spirv::BuiltIn as Bi; Ok(match spirv::BuiltIn::from_u32(word) { Some(Bi::Position | Bi::FragCoord) => crate::BuiltIn::Position { invariant }, Some(Bi::ViewIndex) => crate::BuiltIn::ViewIndex, // vertex Some(Bi::BaseInstance) => crate::BuiltIn::BaseInstance, Some(Bi::BaseVertex) => crate::BuiltIn::BaseVertex, Some(Bi::ClipDistance) => crate::BuiltIn::ClipDistances, Some(Bi::CullDistance) => crate::BuiltIn::CullDistance, Some(Bi::InstanceIndex) => crate::BuiltIn::InstanceIndex, Some(Bi::PointSize) => crate::BuiltIn::PointSize, Some(Bi::VertexIndex) => crate::BuiltIn::VertexIndex, Some(Bi::DrawIndex) => crate::BuiltIn::DrawIndex, // fragment Some(Bi::FragDepth) => crate::BuiltIn::FragDepth, Some(Bi::PointCoord) => crate::BuiltIn::PointCoord, Some(Bi::FrontFacing) => crate::BuiltIn::FrontFacing, Some(Bi::PrimitiveId) => crate::BuiltIn::PrimitiveIndex, Some(Bi::BaryCoordKHR) => crate::BuiltIn::Barycentric { perspective: true }, Some(Bi::BaryCoordNoPerspKHR) => crate::BuiltIn::Barycentric { perspective: false }, Some(Bi::SampleId) => crate::BuiltIn::SampleIndex, Some(Bi::SampleMask) => crate::BuiltIn::SampleMask, // compute Some(Bi::GlobalInvocationId) => crate::BuiltIn::GlobalInvocationId, Some(Bi::LocalInvocationId) => crate::BuiltIn::LocalInvocationId, Some(Bi::LocalInvocationIndex) => crate::BuiltIn::LocalInvocationIndex, Some(Bi::WorkgroupId) => crate::BuiltIn::WorkGroupId, Some(Bi::WorkgroupSize) => crate::BuiltIn::WorkGroupSize, Some(Bi::NumWorkgroups) => crate::BuiltIn::NumWorkGroups, // subgroup Some(Bi::NumSubgroups) => crate::BuiltIn::NumSubgroups, Some(Bi::SubgroupId) => crate::BuiltIn::SubgroupId, Some(Bi::SubgroupSize) => crate::BuiltIn::SubgroupSize, Some(Bi::SubgroupLocalInvocationId) => crate::BuiltIn::SubgroupInvocationId, _ => return Err(Error::UnsupportedBuiltIn(word)), }) } pub(super) fn map_storage_class(word: spirv::Word) -> Result { use super::ExtendedClass as Ec; use spirv::StorageClass as Sc; Ok(match Sc::from_u32(word) { Some(Sc::Function) => Ec::Global(crate::AddressSpace::Function), Some(Sc::Input) => Ec::Input, Some(Sc::Output) => Ec::Output, Some(Sc::Private) => Ec::Global(crate::AddressSpace::Private), Some(Sc::UniformConstant) => Ec::Global(crate::AddressSpace::Handle), Some(Sc::StorageBuffer) => Ec::Global(crate::AddressSpace::Storage { //Note: this is restricted by decorations later access: crate::StorageAccess::LOAD | crate::StorageAccess::STORE, }), // we expect the `Storage` case to be filtered out before calling this function. Some(Sc::Uniform) => Ec::Global(crate::AddressSpace::Uniform), Some(Sc::Workgroup) => Ec::Global(crate::AddressSpace::WorkGroup), Some(Sc::PushConstant) => Ec::Global(crate::AddressSpace::Immediate), _ => return Err(Error::UnsupportedStorageClass(word)), }) } ================================================ FILE: naga/src/front/spv/error.rs ================================================ use alloc::{ format, string::{String, ToString}, }; use codespan_reporting::diagnostic::Diagnostic; use codespan_reporting::files::SimpleFile; use codespan_reporting::term; use super::ModuleState; #[cfg(feature = "stderr")] use crate::error::ErrorWrite; use crate::{arena::Handle, error::replace_control_chars, front::atomic_upgrade}; #[derive(Clone, Debug, thiserror::Error)] pub enum Error { #[error("invalid header")] InvalidHeader, #[error("invalid word count")] InvalidWordCount, #[error("unknown instruction {0}")] UnknownInstruction(u16), #[error("unknown capability %{0}")] UnknownCapability(spirv::Word), #[error("unsupported instruction {1:?} at {0:?}")] UnsupportedInstruction(ModuleState, spirv::Op), #[error("unsupported capability {0:?}")] UnsupportedCapability(spirv::Capability), #[error("unsupported extension {0}")] UnsupportedExtension(String), #[error("unsupported extension set {0}")] UnsupportedExtSet(String), #[error("unsupported extension instantiation set %{0}")] UnsupportedExtInstSet(spirv::Word), #[error("unsupported extension instantiation %{0}")] UnsupportedExtInst(spirv::Word), #[error("unsupported type {0:?}")] UnsupportedType(Handle), #[error("unsupported execution model %{0}")] UnsupportedExecutionModel(spirv::Word), #[error("unsupported execution mode %{0}")] UnsupportedExecutionMode(spirv::Word), #[error("unsupported storage class %{0}")] UnsupportedStorageClass(spirv::Word), #[error("unsupported image dimension %{0}")] UnsupportedImageDim(spirv::Word), #[error("unsupported image format %{0}")] UnsupportedImageFormat(spirv::Word), #[error("unsupported builtin %{0}")] UnsupportedBuiltIn(spirv::Word), #[error("unsupported control flow %{0}")] UnsupportedControlFlow(spirv::Word), #[error("unsupported binary operator %{0}")] UnsupportedBinaryOperator(spirv::Word), #[error("Naga supports OpTypeRuntimeArray in the StorageBuffer storage class only")] UnsupportedRuntimeArrayStorageClass, #[error( "unsupported matrix stride {} for a {}x{} matrix with scalar width={}", stride, columns, rows, width )] UnsupportedMatrixStride { stride: u32, columns: u8, rows: u8, width: u8, }, #[error("unknown binary operator {0:?}")] UnknownBinaryOperator(spirv::Op), #[error("unknown relational function {0:?}")] UnknownRelationalFunction(spirv::Op), #[error("unsupported group operation %{0}")] UnsupportedGroupOperation(spirv::Word), #[error("invalid parameter {0:?}")] InvalidParameter(spirv::Op), #[error("invalid operand count {1} for {0:?}")] InvalidOperandCount(spirv::Op, u16), #[error("invalid operand")] InvalidOperand, #[error("invalid id %{0}")] InvalidId(spirv::Word), #[error("invalid decoration %{0}")] InvalidDecoration(spirv::Word), #[error("invalid type width %{0}")] InvalidTypeWidth(spirv::Word), #[error("invalid sign %{0}")] InvalidSign(spirv::Word), #[error("invalid inner type %{0}")] InvalidInnerType(spirv::Word), #[error("invalid vector size %{0}")] InvalidVectorSize(spirv::Word), #[error("invalid access type %{0}")] InvalidAccessType(spirv::Word), #[error("invalid access {0:?}")] InvalidAccess(crate::Expression), #[error("invalid access index %{0}")] InvalidAccessIndex(spirv::Word), #[error("invalid index type %{0}")] InvalidIndexType(spirv::Word), #[error("invalid binding %{0}")] InvalidBinding(spirv::Word), #[error("invalid global var {0:?}")] InvalidGlobalVar(crate::Expression), #[error("invalid image/sampler expression {0:?}")] InvalidImageExpression(crate::Expression), #[error("cannot create a OpTypeImage as both a depth and storage image")] InvalidImageDepthStorage, #[error("image read/write without format is not currently supported. See https://github.com/gfx-rs/wgpu/issues/6797")] InvalidStorageImageWithoutFormat, #[error("invalid image base type {0:?}")] InvalidImageBaseType(Handle), #[error("invalid image {0:?}")] InvalidImage(Handle), #[error("invalid as type {0:?}")] InvalidAsType(Handle), #[error("invalid vector type {0:?}")] InvalidVectorType(Handle), #[error("inconsistent comparison sampling {0:?}")] InconsistentComparisonSampling(Handle), #[error("wrong function result type %{0}")] WrongFunctionResultType(spirv::Word), #[error("wrong function argument type %{0}")] WrongFunctionArgumentType(spirv::Word), #[error("missing decoration {0:?}")] MissingDecoration(spirv::Decoration), #[error("bad string")] BadString, #[error("incomplete data")] IncompleteData, #[error("invalid terminator")] InvalidTerminator, #[error("invalid edge classification")] InvalidEdgeClassification, #[error("cycle detected in the CFG during traversal at {0}")] ControlFlowGraphCycle(crate::front::spv::BlockId), #[error("recursive function call %{0}")] FunctionCallCycle(spirv::Word), #[error("invalid array size %{0}")] InvalidArraySize(spirv::Word), #[error("invalid barrier scope %{0}")] InvalidBarrierScope(spirv::Word), #[error("invalid barrier memory semantics %{0}")] InvalidBarrierMemorySemantics(spirv::Word), #[error( "arrays of images / samplers are supported only through bindings for \ now (i.e. you can't create an array of images or samplers that doesn't \ come from a binding)" )] NonBindingArrayOfImageOrSamplers, #[error("naga only supports specialization constant IDs up to 65535 but was given {0}")] SpecIdTooHigh(u32), #[error("atomic upgrade error: {0}")] AtomicUpgradeError(atomic_upgrade::Error), } impl Error { #[cfg(feature = "stderr")] pub fn emit_to_writer(&self, writer: &mut impl ErrorWrite, source: &str) { self.emit_to_writer_with_path(writer, source, "spv"); } #[cfg(feature = "stderr")] pub fn emit_to_writer_with_path(&self, writer: &mut impl ErrorWrite, source: &str, path: &str) { let path = path.to_string(); let files = SimpleFile::new(path, replace_control_chars(source)); let config = term::Config::default(); let diagnostic = Diagnostic::error().with_message(format!("{self:?}")); crate::error::emit_to_writer(writer, &config, &files, &diagnostic) .expect("cannot write error"); } pub fn emit_to_string(&self, source: &str) -> String { self.emit_to_string_with_path(source, "spv") } pub fn emit_to_string_with_path(&self, source: &str, path: &str) -> String { let path = path.to_string(); let files = SimpleFile::new(path, replace_control_chars(source)); let config = term::Config::default(); let diagnostic = Diagnostic::error().with_message(format!("{self:?}")); let mut writer = crate::error::DiagnosticBuffer::new(); writer .emit_to_self(&config, &files, &diagnostic) .expect("cannot write error"); writer.into_string() } } impl From for Error { fn from(source: atomic_upgrade::Error) -> Self { Error::AtomicUpgradeError(source) } } ================================================ FILE: naga/src/front/spv/function.rs ================================================ use alloc::{format, vec, vec::Vec}; use super::{Error, Instruction, LookupExpression, LookupHelper as _}; use crate::proc::Emitter; use crate::{ arena::{Arena, Handle}, front::spv::{BlockContext, BodyIndex}, }; pub type BlockId = u32; impl> super::Frontend { // Registers a function call. It will generate a dummy handle to call, which // gets resolved after all the functions are processed. pub(super) fn add_call( &mut self, from: spirv::Word, to: spirv::Word, ) -> Handle { let dummy_handle = self .dummy_functions .append(crate::Function::default(), Default::default()); self.deferred_function_calls.push(to); self.function_call_graph.add_edge(from, to, ()); dummy_handle } pub(super) fn parse_function(&mut self, module: &mut crate::Module) -> Result<(), Error> { let start = self.data_offset; self.lookup_expression.clear(); self.lookup_load_override.clear(); self.lookup_sampled_image.clear(); let result_type_id = self.next()?; let fun_id = self.next()?; let _fun_control = self.next()?; let fun_type_id = self.next()?; let mut fun = { let ft = self.lookup_function_type.lookup(fun_type_id)?; if ft.return_type_id != result_type_id { return Err(Error::WrongFunctionResultType(result_type_id)); } crate::Function { name: self.future_decor.remove(&fun_id).and_then(|dec| dec.name), arguments: Vec::with_capacity(ft.parameter_type_ids.len()), result: if self.lookup_void_type == Some(result_type_id) { None } else { let lookup_result_ty = self.lookup_type.lookup(result_type_id)?; Some(crate::FunctionResult { ty: lookup_result_ty.handle, binding: None, }) }, local_variables: Arena::new(), expressions: self.make_expression_storage( &module.global_variables, &module.constants, &module.overrides, ), named_expressions: crate::NamedExpressions::default(), body: crate::Block::new(), diagnostic_filter_leaf: None, } }; // read parameters for i in 0..fun.arguments.capacity() { let start = self.data_offset; match self.next_inst()? { Instruction { op: spirv::Op::FunctionParameter, wc: 3, } => { let type_id = self.next()?; let id = self.next()?; let handle = fun.expressions.append( crate::Expression::FunctionArgument(i as u32), self.span_from(start), ); self.lookup_expression.insert( id, LookupExpression { handle, type_id, // Setting this to an invalid id will cause get_expr_handle // to default to the main body making sure no load/stores // are added. block_id: 0, }, ); //Note: we redo the lookup in order to work around `self` borrowing if type_id != self .lookup_function_type .lookup(fun_type_id)? .parameter_type_ids[i] { return Err(Error::WrongFunctionArgumentType(type_id)); } let ty = self.lookup_type.lookup(type_id)?.handle; let decor = self.future_decor.remove(&id).unwrap_or_default(); fun.arguments.push(crate::FunctionArgument { name: decor.name, ty, binding: None, }); } Instruction { op, .. } => return Err(Error::InvalidParameter(op)), } } // Note the index this function's handle will be assigned, for tracing. let function_index = module.functions.len(); // Read body self.function_call_graph.add_node(fun_id); let mut parameters_sampling = vec![super::image::SamplingFlags::empty(); fun.arguments.len()]; let mut block_ctx = BlockContext { phis: Default::default(), blocks: Default::default(), body_for_label: Default::default(), mergers: Default::default(), bodies: Default::default(), module, function_id: fun_id, expressions: &mut fun.expressions, local_arena: &mut fun.local_variables, arguments: &fun.arguments, parameter_sampling: &mut parameters_sampling, }; // Insert the main body whose parent is also himself block_ctx.bodies.push(super::Body::with_parent(0)); // Scan the blocks and add them as nodes loop { let fun_inst = self.next_inst()?; log::debug!("{:?}", fun_inst.op); match fun_inst.op { spirv::Op::Line => { fun_inst.expect(4)?; let _file_id = self.next()?; let _row_id = self.next()?; let _col_id = self.next()?; } spirv::Op::Label => { // Read the label ID fun_inst.expect(2)?; let block_id = self.next()?; self.next_block(block_id, &mut block_ctx)?; } spirv::Op::FunctionEnd => { fun_inst.expect(1)?; break; } spirv::Op::ExtInst => { let _ = self.next()?; let _ = self.next()?; let set_id = self.next()?; if Some(set_id) == self.ext_non_semantic_id { for _ in 0..fun_inst.wc - 4 { self.next()?; } } else { return Err(Error::UnsupportedInstruction(self.state, fun_inst.op)); } } _ => { return Err(Error::UnsupportedInstruction(self.state, fun_inst.op)); } } } if let Some(ref prefix) = self.options.block_ctx_dump_prefix { let dump_suffix = match self.lookup_entry_point.get(&fun_id) { Some(ep) => format!("block_ctx.{:?}-{}.txt", ep.stage, ep.name), None => format!("block_ctx.Fun-{function_index}.txt"), }; cfg_if::cfg_if! { if #[cfg(feature = "fs")] { let prefix: &std::path::Path = prefix.as_ref(); let dest = prefix.join(dump_suffix); let dump = format!("{block_ctx:#?}"); if let Err(e) = std::fs::write(&dest, dump) { log::error!("Unable to dump the block context into {dest:?}: {e}"); } } else { log::error!("Unable to dump the block context into {prefix:?}/{dump_suffix}: file system integration was not enabled with the `fs` feature"); } } } // Emit `Store` statements to properly initialize all the local variables we // created for `phi` expressions. // // Note that get_expr_handle also contributes slightly odd entries to this table, // to get the spill. for phi in block_ctx.phis.iter() { // Get a pointer to the local variable for the phi's value. let phi_pointer: Handle = block_ctx.expressions.append( crate::Expression::LocalVariable(phi.local), crate::Span::default(), ); // At the end of each of `phi`'s predecessor blocks, store the corresponding // source value in the phi's local variable. for &(source, predecessor) in phi.expressions.iter() { let source_lexp = &self.lookup_expression[&source]; let predecessor_body_idx = block_ctx.body_for_label[&predecessor]; // If the expression is a global/argument it will have a 0 block // id so we must use a default value instead of panicking let source_body_idx = block_ctx .body_for_label .get(&source_lexp.block_id) .copied() .unwrap_or(0); // If the Naga `Expression` generated for `source` is in scope, then we // can simply store that in the phi's local variable. // // Otherwise, spill the source value to a local variable in the block that // defines it. (We know this store dominates the predecessor; otherwise, // the phi wouldn't have been able to refer to that source expression in // the first place.) Then, the predecessor block can count on finding the // source's value in that local variable. let value = if super::is_parent(predecessor_body_idx, source_body_idx, &block_ctx) { source_lexp.handle } else { // The source SPIR-V expression is not defined in the phi's // predecessor block, nor is it a globally available expression. So it // must be defined off in some other block that merely dominates the // predecessor. This means that the corresponding Naga `Expression` // may not be in scope in the predecessor block. // // In the block that defines `source`, spill it to a fresh local // variable, to ensure we can still use it at the end of the // predecessor. let ty = self.lookup_type[&source_lexp.type_id].handle; let local = block_ctx.local_arena.append( crate::LocalVariable { name: None, ty, init: None, }, crate::Span::default(), ); let pointer = block_ctx.expressions.append( crate::Expression::LocalVariable(local), crate::Span::default(), ); // Get the spilled value of the source expression. let start = block_ctx.expressions.len(); let expr = block_ctx .expressions .append(crate::Expression::Load { pointer }, crate::Span::default()); let range = block_ctx.expressions.range_from(start); block_ctx .blocks .get_mut(&predecessor) .unwrap() .push(crate::Statement::Emit(range), crate::Span::default()); // At the end of the block that defines it, spill the source // expression's value. block_ctx .blocks .get_mut(&source_lexp.block_id) .unwrap() .push( crate::Statement::Store { pointer, value: source_lexp.handle, }, crate::Span::default(), ); expr }; // At the end of the phi predecessor block, store the source // value in the phi's value. block_ctx.blocks.get_mut(&predecessor).unwrap().push( crate::Statement::Store { pointer: phi_pointer, value, }, crate::Span::default(), ) } } fun.body = block_ctx.lower(); // done let fun_handle = module.functions.append(fun, self.span_from_with_op(start)); self.lookup_function.insert( fun_id, super::LookupFunction { handle: fun_handle, parameters_sampling, }, ); if let Some(ep) = self.lookup_entry_point.remove(&fun_id) { self.deferred_entry_points.push((ep, fun_id)); } Ok(()) } pub(super) fn process_entry_point( &mut self, module: &mut crate::Module, ep: super::EntryPoint, fun_id: u32, ) -> Result<(), Error> { // create a wrapping function let mut function = crate::Function { name: Some(format!("{}_wrap", ep.name)), arguments: Vec::new(), result: None, local_variables: Arena::new(), expressions: Arena::new(), named_expressions: crate::NamedExpressions::default(), body: crate::Block::new(), diagnostic_filter_leaf: None, }; // 1. copy the inputs from arguments to privates for &v_id in ep.variable_ids.iter() { let lvar = self.lookup_variable.lookup(v_id)?; if let super::Variable::Input(ref arg) = lvar.inner { let span = module.global_variables.get_span(lvar.handle); let arg_expr = function.expressions.append( crate::Expression::FunctionArgument(function.arguments.len() as u32), span, ); let load_expr = if arg.ty == module.global_variables[lvar.handle].ty { arg_expr } else { // The only case where the type is different is if we need to treat // unsigned integer as signed. let mut emitter = Emitter::default(); emitter.start(&function.expressions); let handle = function.expressions.append( crate::Expression::As { expr: arg_expr, kind: crate::ScalarKind::Sint, convert: Some(4), }, span, ); function.body.extend(emitter.finish(&function.expressions)); handle }; function.body.push( crate::Statement::Store { pointer: function .expressions .append(crate::Expression::GlobalVariable(lvar.handle), span), value: load_expr, }, span, ); let mut arg = arg.clone(); if ep.stage == crate::ShaderStage::Fragment { if let Some(ref mut binding) = arg.binding { binding.apply_default_interpolation(&module.types[arg.ty].inner); } } function.arguments.push(arg); } } // 2. call the wrapped function let fake_id = !(module.entry_points.len() as u32); // doesn't matter, as long as it's not a collision let dummy_handle = self.add_call(fake_id, fun_id); function.body.push( crate::Statement::Call { function: dummy_handle, arguments: Vec::new(), result: None, }, crate::Span::default(), ); // 3. copy the outputs from privates to the result // // It would be nice to share struct layout code here with `parse_type_struct`, // but that case needs to take into account offset decorations, which makes an // abstraction harder to follow than just writing out what we mean. `Layouter` // and `Alignment` cover the worst parts already. let mut members = Vec::new(); self.layouter.update(module.to_ctx()).unwrap(); let mut next_member_offset = 0; let mut struct_alignment = crate::proc::Alignment::ONE; let mut components = Vec::new(); for &v_id in ep.variable_ids.iter() { let lvar = self.lookup_variable.lookup(v_id)?; if let super::Variable::Output(ref result) = lvar.inner { let span = module.global_variables.get_span(lvar.handle); let expr_handle = function .expressions .append(crate::Expression::GlobalVariable(lvar.handle), span); // Cull problematic builtins of gl_PerVertex. // See the docs for `Frontend::gl_per_vertex_builtin_access`. { let ty = &module.types[result.ty]; if let crate::TypeInner::Struct { members: ref original_members, span, } = ty.inner { let mut new_members = None; for (idx, member) in original_members.iter().enumerate() { if let Some(crate::Binding::BuiltIn(built_in)) = member.binding { if !self.gl_per_vertex_builtin_access.contains(&built_in) { new_members.get_or_insert_with(|| original_members.clone()) [idx] .binding = None; } } } if let Some(new_members) = new_members { module.types.replace( result.ty, crate::Type { name: ty.name.clone(), inner: crate::TypeInner::Struct { members: new_members, span, }, }, ); } } } match module.types[result.ty].inner { crate::TypeInner::Struct { members: ref sub_members, .. } => { for (index, sm) in sub_members.iter().enumerate() { if sm.binding.is_none() { continue; } let mut sm = sm.clone(); if let Some(ref mut binding) = sm.binding { if ep.stage == crate::ShaderStage::Vertex { binding.apply_default_interpolation(&module.types[sm.ty].inner); } } let member_alignment = self.layouter[sm.ty].alignment; next_member_offset = member_alignment.round_up(next_member_offset); sm.offset = next_member_offset; struct_alignment = struct_alignment.max(member_alignment); next_member_offset += self.layouter[sm.ty].size; members.push(sm); components.push(function.expressions.append( crate::Expression::AccessIndex { base: expr_handle, index: index as u32, }, span, )); } } ref inner => { let mut binding = result.binding.clone(); if let Some(ref mut binding) = binding { if ep.stage == crate::ShaderStage::Vertex { binding.apply_default_interpolation(inner); } } let member_alignment = self.layouter[result.ty].alignment; next_member_offset = member_alignment.round_up(next_member_offset); members.push(crate::StructMember { name: None, ty: result.ty, binding, offset: next_member_offset, }); struct_alignment = struct_alignment.max(member_alignment); next_member_offset += self.layouter[result.ty].size; // populate just the globals first, then do `Load` in a // separate step, so that we can get a range. components.push(expr_handle); } } } } for (member_index, member) in members.iter().enumerate() { match member.binding { Some(crate::Binding::BuiltIn(crate::BuiltIn::Position { .. })) if self.options.adjust_coordinate_space => { let mut emitter = Emitter::default(); emitter.start(&function.expressions); let global_expr = components[member_index]; let span = function.expressions.get_span(global_expr); let access_expr = function.expressions.append( crate::Expression::AccessIndex { base: global_expr, index: 1, }, span, ); let load_expr = function.expressions.append( crate::Expression::Load { pointer: access_expr, }, span, ); let neg_expr = function.expressions.append( crate::Expression::Unary { op: crate::UnaryOperator::Negate, expr: load_expr, }, span, ); function.body.extend(emitter.finish(&function.expressions)); function.body.push( crate::Statement::Store { pointer: access_expr, value: neg_expr, }, span, ); } _ => {} } } let mut emitter = Emitter::default(); emitter.start(&function.expressions); for component in components.iter_mut() { let load_expr = crate::Expression::Load { pointer: *component, }; let span = function.expressions.get_span(*component); *component = function.expressions.append(load_expr, span); } match members[..] { [] => {} [ref member] => { function.body.extend(emitter.finish(&function.expressions)); let span = function.expressions.get_span(components[0]); function.body.push( crate::Statement::Return { value: components.first().cloned(), }, span, ); function.result = Some(crate::FunctionResult { ty: member.ty, binding: member.binding.clone(), }); } _ => { let span = crate::Span::total_span( components.iter().map(|h| function.expressions.get_span(*h)), ); let ty = module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Struct { members, span: struct_alignment.round_up(next_member_offset), }, }, span, ); let result_expr = function .expressions .append(crate::Expression::Compose { ty, components }, span); function.body.extend(emitter.finish(&function.expressions)); function.body.push( crate::Statement::Return { value: Some(result_expr), }, span, ); function.result = Some(crate::FunctionResult { ty, binding: None }); } } module.entry_points.push(crate::EntryPoint { name: ep.name, stage: ep.stage, early_depth_test: ep.early_depth_test, workgroup_size: ep.workgroup_size, workgroup_size_overrides: None, function, mesh_info: None, task_payload: None, incoming_ray_payload: None, }); Ok(()) } } impl BlockContext<'_> { pub(super) const fn gctx(&self) -> crate::proc::GlobalCtx<'_> { crate::proc::GlobalCtx { types: &self.module.types, constants: &self.module.constants, overrides: &self.module.overrides, global_expressions: &self.module.global_expressions, } } /// Consumes the `BlockContext` producing a Ir [`Block`](crate::Block) fn lower(mut self) -> crate::Block { fn lower_impl( blocks: &mut crate::FastHashMap, bodies: &[super::Body], body_idx: BodyIndex, ) -> crate::Block { let mut block = crate::Block::new(); for item in bodies[body_idx].data.iter() { match *item { super::BodyFragment::BlockId(id) => block.append(blocks.get_mut(&id).unwrap()), super::BodyFragment::If { condition, accept, reject, } => { let accept = lower_impl(blocks, bodies, accept); let reject = lower_impl(blocks, bodies, reject); block.push( crate::Statement::If { condition, accept, reject, }, crate::Span::default(), ) } super::BodyFragment::Loop { body, continuing, break_if, } => { let body = lower_impl(blocks, bodies, body); let continuing = lower_impl(blocks, bodies, continuing); block.push( crate::Statement::Loop { body, continuing, break_if, }, crate::Span::default(), ) } super::BodyFragment::Switch { selector, ref cases, default, } => { let mut ir_cases: Vec<_> = cases .iter() .map(|&(value, body_idx)| { let body = lower_impl(blocks, bodies, body_idx); // Handle simple cases that would make a fallthrough statement unreachable code let fall_through = body.last().is_none_or(|s| !s.is_terminator()); crate::SwitchCase { value: crate::SwitchValue::I32(value), body, fall_through, } }) .collect(); ir_cases.push(crate::SwitchCase { value: crate::SwitchValue::Default, body: lower_impl(blocks, bodies, default), fall_through: false, }); block.push( crate::Statement::Switch { selector, cases: ir_cases, }, crate::Span::default(), ) } super::BodyFragment::Break => { block.push(crate::Statement::Break, crate::Span::default()) } super::BodyFragment::Continue => { block.push(crate::Statement::Continue, crate::Span::default()) } } } block } lower_impl(&mut self.blocks, &self.bodies, 0) } } ================================================ FILE: naga/src/front/spv/image.rs ================================================ use alloc::vec::Vec; use crate::{ arena::{Handle, UniqueArena}, Scalar, }; use super::{Error, LookupExpression, LookupHelper as _}; #[derive(Clone, Debug)] pub(super) struct LookupSampledImage { image: Handle, sampler: Handle, } bitflags::bitflags! { /// Flags describing sampling method. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct SamplingFlags: u32 { /// Regular sampling. const REGULAR = 0x1; /// Comparison sampling. const COMPARISON = 0x2; } } impl super::BlockContext<'_> { fn get_image_expr_ty( &self, handle: Handle, ) -> Result, Error> { match self.expressions[handle] { crate::Expression::GlobalVariable(handle) => { Ok(self.module.global_variables[handle].ty) } crate::Expression::FunctionArgument(i) => Ok(self.arguments[i as usize].ty), crate::Expression::Access { base, .. } => Ok(self.get_image_expr_ty(base)?), ref other => Err(Error::InvalidImageExpression(other.clone())), } } } /// Options of a sampling operation. #[derive(Debug)] pub struct SamplingOptions { /// Projection sampling: the division by W is expected to happen /// in the texture unit. pub project: bool, /// Depth comparison sampling with a reference value. pub compare: bool, /// Gather sampling: Operates on four samples of one channel. pub gather: bool, } enum ExtraCoordinate { ArrayLayer, Projection, Garbage, } /// Return the texture coordinates separated from the array layer, /// and/or divided by the projection term. /// /// The Proj sampling ops expect an extra coordinate for the W. /// The arrayed (can't be Proj!) images expect an extra coordinate for the layer. fn extract_image_coordinates( image_dim: crate::ImageDimension, extra_coordinate: ExtraCoordinate, base: Handle, coordinate_ty: Handle, ctx: &mut super::BlockContext, ) -> (Handle, Option>) { let (given_size, kind) = match ctx.module.types[coordinate_ty].inner { crate::TypeInner::Scalar(Scalar { kind, .. }) => (None, kind), crate::TypeInner::Vector { size, scalar: Scalar { kind, .. }, } => (Some(size), kind), ref other => unreachable!("Unexpected texture coordinate {:?}", other), }; let required_size = image_dim.required_coordinate_size(); let required_ty = required_size.map(|size| { ctx.module .types .get(&crate::Type { name: None, inner: crate::TypeInner::Vector { size, scalar: Scalar { kind, width: 4 }, }, }) .expect("Required coordinate type should have been set up by `parse_type_image`!") }); let extra_expr = crate::Expression::AccessIndex { base, index: required_size.map_or(1, |size| size as u32), }; let base_span = ctx.expressions.get_span(base); match extra_coordinate { ExtraCoordinate::ArrayLayer => { let extracted = match required_size { None => ctx .expressions .append(crate::Expression::AccessIndex { base, index: 0 }, base_span), Some(size) => { let mut components = Vec::with_capacity(size as usize); for index in 0..size as u32 { let comp = ctx .expressions .append(crate::Expression::AccessIndex { base, index }, base_span); components.push(comp); } ctx.expressions.append( crate::Expression::Compose { ty: required_ty.unwrap(), components, }, base_span, ) } }; let array_index_f32 = ctx.expressions.append(extra_expr, base_span); let array_index = ctx.expressions.append( crate::Expression::As { kind: crate::ScalarKind::Sint, expr: array_index_f32, convert: Some(4), }, base_span, ); (extracted, Some(array_index)) } ExtraCoordinate::Projection => { let projection = ctx.expressions.append(extra_expr, base_span); let divided = match required_size { None => { let temp = ctx .expressions .append(crate::Expression::AccessIndex { base, index: 0 }, base_span); ctx.expressions.append( crate::Expression::Binary { op: crate::BinaryOperator::Divide, left: temp, right: projection, }, base_span, ) } Some(size) => { let mut components = Vec::with_capacity(size as usize); for index in 0..size as u32 { let temp = ctx .expressions .append(crate::Expression::AccessIndex { base, index }, base_span); let comp = ctx.expressions.append( crate::Expression::Binary { op: crate::BinaryOperator::Divide, left: temp, right: projection, }, base_span, ); components.push(comp); } ctx.expressions.append( crate::Expression::Compose { ty: required_ty.unwrap(), components, }, base_span, ) } }; (divided, None) } ExtraCoordinate::Garbage if given_size == required_size => (base, None), ExtraCoordinate::Garbage => { use crate::SwizzleComponent as Sc; let cut_expr = match required_size { None => crate::Expression::AccessIndex { base, index: 0 }, Some(size) => crate::Expression::Swizzle { size, vector: base, pattern: [Sc::X, Sc::Y, Sc::Z, Sc::W], }, }; (ctx.expressions.append(cut_expr, base_span), None) } } } pub(super) fn patch_comparison_type( flags: SamplingFlags, var: &mut crate::GlobalVariable, arena: &mut UniqueArena, ) -> bool { if !flags.contains(SamplingFlags::COMPARISON) { return true; } if flags == SamplingFlags::all() { return false; } log::debug!("Flipping comparison for {var:?}"); let original_ty = &arena[var.ty]; let original_ty_span = arena.get_span(var.ty); let ty_inner = match original_ty.inner { crate::TypeInner::Image { class: crate::ImageClass::Sampled { multi, .. }, dim, arrayed, } => crate::TypeInner::Image { class: crate::ImageClass::Depth { multi }, dim, arrayed, }, crate::TypeInner::Sampler { .. } => crate::TypeInner::Sampler { comparison: true }, ref other => unreachable!("Unexpected type for comparison mutation: {:?}", other), }; let name = original_ty.name.clone(); var.ty = arena.insert( crate::Type { name, inner: ty_inner, }, original_ty_span, ); true } impl> super::Frontend { pub(super) fn parse_image_couple(&mut self) -> Result<(), Error> { let _result_type_id = self.next()?; let result_id = self.next()?; let image_id = self.next()?; let sampler_id = self.next()?; let image_lexp = self.lookup_expression.lookup(image_id)?; let sampler_lexp = self.lookup_expression.lookup(sampler_id)?; self.lookup_sampled_image.insert( result_id, LookupSampledImage { image: image_lexp.handle, sampler: sampler_lexp.handle, }, ); Ok(()) } pub(super) fn parse_image_uncouple(&mut self, block_id: spirv::Word) -> Result<(), Error> { let result_type_id = self.next()?; let result_id = self.next()?; let sampled_image_id = self.next()?; self.lookup_expression.insert( result_id, LookupExpression { handle: self.lookup_sampled_image.lookup(sampled_image_id)?.image, type_id: result_type_id, block_id, }, ); Ok(()) } pub(super) fn parse_image_write( &mut self, words_left: u16, ctx: &mut super::BlockContext, emitter: &mut crate::proc::Emitter, block: &mut crate::Block, body_idx: usize, ) -> Result { let image_id = self.next()?; let coordinate_id = self.next()?; let value_id = self.next()?; let image_ops = if words_left != 0 { self.next()? } else { 0 }; if image_ops != 0 { let other = spirv::ImageOperands::from_bits_truncate(image_ops); log::warn!("Unknown image write ops {other:?}"); for _ in 1..words_left { self.next()?; } } let image_lexp = self.lookup_expression.lookup(image_id)?; let image_ty = ctx.get_image_expr_ty(image_lexp.handle)?; let coord_lexp = self.lookup_expression.lookup(coordinate_id)?; let coord_handle = self.get_expr_handle(coordinate_id, coord_lexp, ctx, emitter, block, body_idx); let coord_type_handle = self.lookup_type.lookup(coord_lexp.type_id)?.handle; let (coordinate, array_index) = match ctx.module.types[image_ty].inner { crate::TypeInner::Image { dim, arrayed, class: _, } => extract_image_coordinates( dim, if arrayed { ExtraCoordinate::ArrayLayer } else { ExtraCoordinate::Garbage }, coord_handle, coord_type_handle, ctx, ), _ => return Err(Error::InvalidImage(image_ty)), }; let value_lexp = self.lookup_expression.lookup(value_id)?; let value = self.get_expr_handle(value_id, value_lexp, ctx, emitter, block, body_idx); let value_type = self.lookup_type.lookup(value_lexp.type_id)?.handle; // In hlsl etc, the write value may not be the vector 4. let expanded_value = match ctx.module.types[value_type].inner { crate::TypeInner::Scalar(_) => Some(crate::Expression::Splat { value, size: crate::VectorSize::Quad, }), crate::TypeInner::Vector { size, .. } => match size { crate::VectorSize::Bi => Some(crate::Expression::Swizzle { size: crate::VectorSize::Quad, vector: value, pattern: [ crate::SwizzleComponent::X, crate::SwizzleComponent::Y, crate::SwizzleComponent::Y, crate::SwizzleComponent::Y, ], }), crate::VectorSize::Tri => Some(crate::Expression::Swizzle { size: crate::VectorSize::Quad, vector: value, pattern: [ crate::SwizzleComponent::X, crate::SwizzleComponent::Y, crate::SwizzleComponent::Z, crate::SwizzleComponent::Z, ], }), crate::VectorSize::Quad => None, }, _ => return Err(Error::InvalidVectorType(value_type)), }; let value_patched = if let Some(s) = expanded_value { ctx.expressions.append(s, crate::Span::default()) } else { value }; Ok(crate::Statement::ImageStore { image: image_lexp.handle, coordinate, array_index, value: value_patched, }) } pub(super) fn parse_image_load( &mut self, mut words_left: u16, ctx: &mut super::BlockContext, emitter: &mut crate::proc::Emitter, block: &mut crate::Block, block_id: spirv::Word, body_idx: usize, ) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let image_id = self.next()?; let coordinate_id = self.next()?; let mut image_ops = if words_left != 0 { words_left -= 1; self.next()? } else { 0 }; let mut sample = None; let mut level = None; while image_ops != 0 { let bit = 1 << image_ops.trailing_zeros(); match spirv::ImageOperands::from_bits_truncate(bit) { spirv::ImageOperands::LOD => { let lod_expr = self.next()?; let lod_lexp = self.lookup_expression.lookup(lod_expr)?; let lod_handle = self.get_expr_handle(lod_expr, lod_lexp, ctx, emitter, block, body_idx); level = Some(lod_handle); words_left -= 1; } spirv::ImageOperands::SAMPLE => { let sample_expr = self.next()?; let sample_handle = self.lookup_expression.lookup(sample_expr)?.handle; sample = Some(sample_handle); words_left -= 1; } other => { log::warn!("Unknown image load op {other:?}"); for _ in 0..words_left { self.next()?; } break; } } image_ops ^= bit; } // No need to call get_expr_handle here since only globals/arguments are // allowed as images and they are always in the root scope let image_lexp = self.lookup_expression.lookup(image_id)?; let image_ty = ctx.get_image_expr_ty(image_lexp.handle)?; let coord_lexp = self.lookup_expression.lookup(coordinate_id)?; let coord_handle = self.get_expr_handle(coordinate_id, coord_lexp, ctx, emitter, block, body_idx); let coord_type_handle = self.lookup_type.lookup(coord_lexp.type_id)?.handle; let (coordinate, array_index, is_depth) = match ctx.module.types[image_ty].inner { crate::TypeInner::Image { dim, arrayed, class, } => { let (coord, array_index) = extract_image_coordinates( dim, if arrayed { ExtraCoordinate::ArrayLayer } else { ExtraCoordinate::Garbage }, coord_handle, coord_type_handle, ctx, ); (coord, array_index, class.is_depth()) } _ => return Err(Error::InvalidImage(image_ty)), }; let image_load_expr = crate::Expression::ImageLoad { image: image_lexp.handle, coordinate, array_index, sample, level, }; let image_load_handle = ctx .expressions .append(image_load_expr, self.span_from_with_op(start)); let handle = if is_depth { let result_ty = self.lookup_type.lookup(result_type_id)?; // The return type of `OpImageRead` can be a scalar or vector. match ctx.module.types[result_ty.handle].inner { crate::TypeInner::Vector { size, .. } => { let splat_expr = crate::Expression::Splat { size, value: image_load_handle, }; ctx.expressions .append(splat_expr, self.span_from_with_op(start)) } _ => image_load_handle, } } else { image_load_handle }; self.lookup_expression.insert( result_id, LookupExpression { handle, type_id: result_type_id, block_id, }, ); Ok(()) } #[allow(clippy::too_many_arguments)] pub(super) fn parse_image_sample( &mut self, mut words_left: u16, options: SamplingOptions, ctx: &mut super::BlockContext, emitter: &mut crate::proc::Emitter, block: &mut crate::Block, block_id: spirv::Word, body_idx: usize, ) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let sampled_image_id = self.next()?; let coordinate_id = self.next()?; let (component_id, dref_id) = match (options.gather, options.compare) { (true, false) => (Some(self.next()?), None), (_, true) => (None, Some(self.next()?)), (_, _) => (None, None), }; let span = self.span_from_with_op(start); let mut image_ops = if words_left != 0 { words_left -= 1; self.next()? } else { 0 }; let mut level = crate::SampleLevel::Auto; let mut offset = None; while image_ops != 0 { let bit = 1 << image_ops.trailing_zeros(); match spirv::ImageOperands::from_bits_truncate(bit) { spirv::ImageOperands::BIAS => { let bias_expr = self.next()?; let bias_lexp = self.lookup_expression.lookup(bias_expr)?; let bias_handle = self.get_expr_handle(bias_expr, bias_lexp, ctx, emitter, block, body_idx); level = crate::SampleLevel::Bias(bias_handle); words_left -= 1; } spirv::ImageOperands::LOD => { let lod_expr = self.next()?; let lod_lexp = self.lookup_expression.lookup(lod_expr)?; let lod_handle = self.get_expr_handle(lod_expr, lod_lexp, ctx, emitter, block, body_idx); let is_depth_image = { let image_lexp = self.lookup_sampled_image.lookup(sampled_image_id)?; let image_ty = ctx.get_image_expr_ty(image_lexp.image)?; matches!( ctx.module.types[image_ty].inner, crate::TypeInner::Image { class: crate::ImageClass::Depth { .. }, .. } ) }; level = if options.compare { log::debug!("Assuming {lod_handle:?} is zero"); crate::SampleLevel::Zero } else if is_depth_image { log::debug!( "Assuming level {lod_handle:?} converts losslessly to an integer" ); let expr = crate::Expression::As { expr: lod_handle, kind: crate::ScalarKind::Sint, convert: Some(4), }; let s32_lod_handle = ctx.expressions.append(expr, span); crate::SampleLevel::Exact(s32_lod_handle) } else { crate::SampleLevel::Exact(lod_handle) }; words_left -= 1; } spirv::ImageOperands::GRAD => { let grad_x_expr = self.next()?; let grad_x_lexp = self.lookup_expression.lookup(grad_x_expr)?; let grad_x_handle = self.get_expr_handle( grad_x_expr, grad_x_lexp, ctx, emitter, block, body_idx, ); let grad_y_expr = self.next()?; let grad_y_lexp = self.lookup_expression.lookup(grad_y_expr)?; let grad_y_handle = self.get_expr_handle( grad_y_expr, grad_y_lexp, ctx, emitter, block, body_idx, ); level = if options.compare { log::debug!( "Assuming gradients {grad_x_handle:?} and {grad_y_handle:?} are not greater than 1" ); crate::SampleLevel::Zero } else { crate::SampleLevel::Gradient { x: grad_x_handle, y: grad_y_handle, } }; words_left -= 2; } spirv::ImageOperands::CONST_OFFSET => { let offset_expr = self.next()?; let offset_lexp = self.lookup_expression.lookup(offset_expr)?; let offset_handle = self.get_expr_handle( offset_expr, offset_lexp, ctx, emitter, block, body_idx, ); offset = Some(offset_handle); words_left -= 1; } other => { log::warn!("Unknown image sample operand {other:?}"); for _ in 0..words_left { self.next()?; } break; } } image_ops ^= bit; } let si_lexp = self.lookup_sampled_image.lookup(sampled_image_id)?; let coord_lexp = self.lookup_expression.lookup(coordinate_id)?; let coord_handle = self.get_expr_handle(coordinate_id, coord_lexp, ctx, emitter, block, body_idx); let coord_type_handle = self.lookup_type.lookup(coord_lexp.type_id)?.handle; let gather = match (options.gather, component_id) { (true, Some(component_id)) => { let component_lexp = self.lookup_expression.lookup(component_id)?; let component_value = match ctx.expressions[component_lexp.handle] { // VUID-StandaloneSpirv-OpImageGather-04664: // The “Component” operand of OpImageGather, and OpImageSparseGather must be the // of a constant instruction. crate::Expression::Constant(const_handle) => { let constant = &ctx.module.constants[const_handle]; match ctx.module.global_expressions[constant.init] { // SPIR-V specification: "It must be a 32-bit integer type scalar." crate::Expression::Literal(crate::Literal::U32(value)) => value, crate::Expression::Literal(crate::Literal::I32(value)) => value as u32, _ => { log::error!( "Image gather component constant must be a 32-bit integer literal" ); return Err(Error::InvalidOperand); } } } _ => { log::error!("Image gather component must be a constant"); return Err(Error::InvalidOperand); } }; debug_assert_eq!(level, crate::SampleLevel::Auto); level = crate::SampleLevel::Zero; // SPIR-V specification: "Behavior is undefined if its value is not 0, 1, 2 or 3." match component_value { 0 => Some(crate::SwizzleComponent::X), 1 => Some(crate::SwizzleComponent::Y), 2 => Some(crate::SwizzleComponent::Z), 3 => Some(crate::SwizzleComponent::W), other => { log::error!("Invalid gather component operand: {other}"); return Err(Error::InvalidOperand); } } } (true, None) => { debug_assert_eq!(level, crate::SampleLevel::Auto); level = crate::SampleLevel::Zero; Some(crate::SwizzleComponent::X) } (_, _) => None, }; let sampling_bit = if options.compare { SamplingFlags::COMPARISON } else { SamplingFlags::REGULAR }; let image_ty = match ctx.expressions[si_lexp.image] { crate::Expression::GlobalVariable(handle) => { if let Some(flags) = self.handle_sampling.get_mut(&handle) { *flags |= sampling_bit; } ctx.module.global_variables[handle].ty } crate::Expression::FunctionArgument(i) => { ctx.parameter_sampling[i as usize] |= sampling_bit; ctx.arguments[i as usize].ty } crate::Expression::Access { base, .. } => match ctx.expressions[base] { crate::Expression::GlobalVariable(handle) => { if let Some(flags) = self.handle_sampling.get_mut(&handle) { *flags |= sampling_bit; } match ctx.module.types[ctx.module.global_variables[handle].ty].inner { crate::TypeInner::BindingArray { base, .. } => base, _ => return Err(Error::InvalidGlobalVar(ctx.expressions[base].clone())), } } ref other => return Err(Error::InvalidGlobalVar(other.clone())), }, ref other => return Err(Error::InvalidGlobalVar(other.clone())), }; match ctx.expressions[si_lexp.sampler] { crate::Expression::GlobalVariable(handle) => { *self.handle_sampling.get_mut(&handle).unwrap() |= sampling_bit; } crate::Expression::FunctionArgument(i) => { ctx.parameter_sampling[i as usize] |= sampling_bit; } crate::Expression::Access { base, .. } => match ctx.expressions[base] { crate::Expression::GlobalVariable(handle) => { *self.handle_sampling.get_mut(&handle).unwrap() |= sampling_bit; } ref other => return Err(Error::InvalidGlobalVar(other.clone())), }, ref other => return Err(Error::InvalidGlobalVar(other.clone())), } let ((coordinate, array_index), depth_ref, is_depth) = match ctx.module.types[image_ty].inner { crate::TypeInner::Image { dim, arrayed, class, } => ( extract_image_coordinates( dim, if options.project { ExtraCoordinate::Projection } else if arrayed { ExtraCoordinate::ArrayLayer } else { ExtraCoordinate::Garbage }, coord_handle, coord_type_handle, ctx, ), { match dref_id { Some(id) => { let expr_lexp = self.lookup_expression.lookup(id)?; let mut expr = self .get_expr_handle(id, expr_lexp, ctx, emitter, block, body_idx); if options.project { let required_size = dim.required_coordinate_size(); let right = ctx.expressions.append( crate::Expression::AccessIndex { base: coord_handle, index: required_size.map_or(1, |size| size as u32), }, crate::Span::default(), ); expr = ctx.expressions.append( crate::Expression::Binary { op: crate::BinaryOperator::Divide, left: expr, right, }, crate::Span::default(), ) }; Some(expr) } None => None, } }, class.is_depth(), ), _ => return Err(Error::InvalidImage(image_ty)), }; let expr = crate::Expression::ImageSample { image: si_lexp.image, sampler: si_lexp.sampler, gather, coordinate, array_index, offset, level, depth_ref, clamp_to_edge: false, }; let image_sample_handle = ctx.expressions.append(expr, self.span_from_with_op(start)); let handle = if is_depth && depth_ref.is_none() { let splat_expr = crate::Expression::Splat { size: crate::VectorSize::Quad, value: image_sample_handle, }; ctx.expressions .append(splat_expr, self.span_from_with_op(start)) } else { image_sample_handle }; self.lookup_expression.insert( result_id, LookupExpression { handle, type_id: result_type_id, block_id, }, ); Ok(()) } pub(super) fn parse_image_query_size( &mut self, at_level: bool, ctx: &mut super::BlockContext, emitter: &mut crate::proc::Emitter, block: &mut crate::Block, block_id: spirv::Word, body_idx: usize, ) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let image_id = self.next()?; let level = if at_level { let level_id = self.next()?; let level_lexp = self.lookup_expression.lookup(level_id)?; Some(self.get_expr_handle(level_id, level_lexp, ctx, emitter, block, body_idx)) } else { None }; // No need to call get_expr_handle here since only globals/arguments are // allowed as images and they are always in the root scope //TODO: handle arrays and cubes let image_lexp = self.lookup_expression.lookup(image_id)?; let expr = crate::Expression::ImageQuery { image: image_lexp.handle, query: crate::ImageQuery::Size { level }, }; let result_type_handle = self.lookup_type.lookup(result_type_id)?.handle; let maybe_scalar_kind = ctx.module.types[result_type_handle].inner.scalar_kind(); let expr = if maybe_scalar_kind == Some(crate::ScalarKind::Sint) { crate::Expression::As { expr: ctx.expressions.append(expr, self.span_from_with_op(start)), kind: crate::ScalarKind::Sint, convert: Some(4), } } else { expr }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, self.span_from_with_op(start)), type_id: result_type_id, block_id, }, ); Ok(()) } pub(super) fn parse_image_query_other( &mut self, query: crate::ImageQuery, ctx: &mut super::BlockContext, block_id: spirv::Word, ) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let image_id = self.next()?; // No need to call get_expr_handle here since only globals/arguments are // allowed as images and they are always in the root scope let image_lexp = self.lookup_expression.lookup(image_id)?.clone(); let expr = crate::Expression::ImageQuery { image: image_lexp.handle, query, }; let result_type_handle = self.lookup_type.lookup(result_type_id)?.handle; let maybe_scalar_kind = ctx.module.types[result_type_handle].inner.scalar_kind(); let expr = if maybe_scalar_kind == Some(crate::ScalarKind::Sint) { crate::Expression::As { expr: ctx.expressions.append(expr, self.span_from_with_op(start)), kind: crate::ScalarKind::Sint, convert: Some(4), } } else { expr }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, self.span_from_with_op(start)), type_id: result_type_id, block_id, }, ); Ok(()) } } ================================================ FILE: naga/src/front/spv/mod.rs ================================================ /*! Frontend for [SPIR-V][spv] (Standard Portable Intermediate Representation). ## ID lookups Our IR links to everything with `Handle`, while SPIR-V uses IDs. In order to keep track of the associations, the parser has many lookup tables. There map `spv::Word` into a specific IR handle, plus potentially a bit of extra info, such as the related SPIR-V type ID. TODO: would be nice to find ways that avoid looking up as much ## Inputs/Outputs We create a private variable for each input/output. The relevant inputs are populated at the start of an entry point. The outputs are saved at the end. The function associated with an entry point is wrapped in another function, such that we can handle any `Return` statements without problems. ## Row-major matrices We don't handle them natively, since the IR only expects column majority. Instead, we detect when such matrix is accessed in the `OpAccessChain`, and we generate a parallel expression that loads the value, but transposed. This value then gets used instead of `OpLoad` result later on. [spv]: https://www.khronos.org/registry/SPIR-V/ */ mod convert; mod error; mod function; mod image; mod next_block; mod null; pub use error::Error; use alloc::{borrow::ToOwned, string::String, vec, vec::Vec}; use core::{convert::TryInto, mem, num::NonZeroU32}; use half::f16; use petgraph::graphmap::GraphMap; use super::atomic_upgrade::Upgrades; use crate::{ arena::{Arena, Handle, UniqueArena}, proc::{Alignment, Layouter}, FastHashMap, FastHashSet, FastIndexMap, }; use convert::*; use function::*; pub const SUPPORTED_CAPABILITIES: &[spirv::Capability] = &[ spirv::Capability::Shader, spirv::Capability::VulkanMemoryModel, spirv::Capability::ClipDistance, spirv::Capability::CullDistance, spirv::Capability::SampleRateShading, spirv::Capability::DerivativeControl, spirv::Capability::Matrix, spirv::Capability::ImageQuery, spirv::Capability::Sampled1D, spirv::Capability::Image1D, spirv::Capability::SampledCubeArray, spirv::Capability::ImageCubeArray, spirv::Capability::StorageImageExtendedFormats, spirv::Capability::Int8, spirv::Capability::Int16, spirv::Capability::Int64, spirv::Capability::Int64Atomics, spirv::Capability::Float16, spirv::Capability::AtomicFloat32AddEXT, spirv::Capability::Float64, spirv::Capability::Geometry, spirv::Capability::MultiView, spirv::Capability::StorageBuffer16BitAccess, spirv::Capability::UniformAndStorageBuffer16BitAccess, spirv::Capability::GroupNonUniform, spirv::Capability::GroupNonUniformVote, spirv::Capability::GroupNonUniformArithmetic, spirv::Capability::GroupNonUniformBallot, spirv::Capability::GroupNonUniformShuffle, spirv::Capability::GroupNonUniformShuffleRelative, spirv::Capability::RuntimeDescriptorArray, spirv::Capability::StorageImageMultisample, spirv::Capability::FragmentBarycentricKHR, // tricky ones spirv::Capability::UniformBufferArrayDynamicIndexing, spirv::Capability::StorageBufferArrayDynamicIndexing, ]; pub const SUPPORTED_EXTENSIONS: &[&str] = &[ "SPV_KHR_storage_buffer_storage_class", "SPV_KHR_vulkan_memory_model", "SPV_KHR_multiview", "SPV_EXT_descriptor_indexing", "SPV_EXT_shader_atomic_float_add", "SPV_KHR_16bit_storage", "SPV_KHR_non_semantic_info", "SPV_KHR_fragment_shader_barycentric", ]; #[derive(Copy, Clone)] pub struct Instruction { op: spirv::Op, wc: u16, } impl Instruction { const fn expect(self, count: u16) -> Result<(), Error> { if self.wc == count { Ok(()) } else { Err(Error::InvalidOperandCount(self.op, self.wc)) } } fn expect_at_least(self, count: u16) -> Result { self.wc .checked_sub(count) .ok_or(Error::InvalidOperandCount(self.op, self.wc)) } } impl crate::TypeInner { fn can_comparison_sample(&self, module: &crate::Module) -> bool { match *self { crate::TypeInner::Image { class: crate::ImageClass::Sampled { kind: crate::ScalarKind::Float, multi: false, }, .. } => true, crate::TypeInner::Sampler { .. } => true, crate::TypeInner::BindingArray { base, .. } => { module.types[base].inner.can_comparison_sample(module) } _ => false, } } } #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] pub enum ModuleState { Empty, Capability, Extension, ExtInstImport, MemoryModel, EntryPoint, ExecutionMode, Source, Name, ModuleProcessed, Annotation, Type, Function, } trait LookupHelper { type Target; fn lookup(&self, key: spirv::Word) -> Result<&Self::Target, Error>; } impl LookupHelper for FastHashMap { type Target = T; fn lookup(&self, key: spirv::Word) -> Result<&T, Error> { self.get(&key).ok_or(Error::InvalidId(key)) } } impl crate::ImageDimension { const fn required_coordinate_size(&self) -> Option { match *self { crate::ImageDimension::D1 => None, crate::ImageDimension::D2 => Some(crate::VectorSize::Bi), crate::ImageDimension::D3 => Some(crate::VectorSize::Tri), crate::ImageDimension::Cube => Some(crate::VectorSize::Tri), } } } type MemberIndex = u32; bitflags::bitflags! { #[derive(Clone, Copy, Debug, Default)] struct DecorationFlags: u32 { const NON_READABLE = 0x1; const NON_WRITABLE = 0x2; const COHERENT = 0x4; const VOLATILE = 0x8; } } impl DecorationFlags { fn to_storage_access(self) -> crate::StorageAccess { let mut access = crate::StorageAccess::LOAD | crate::StorageAccess::STORE; if self.contains(DecorationFlags::NON_READABLE) { access &= !crate::StorageAccess::LOAD; } if self.contains(DecorationFlags::NON_WRITABLE) { access &= !crate::StorageAccess::STORE; } access } fn to_memory_decorations(self) -> crate::MemoryDecorations { let mut decorations = crate::MemoryDecorations::empty(); if self.contains(DecorationFlags::COHERENT) { decorations |= crate::MemoryDecorations::COHERENT; } if self.contains(DecorationFlags::VOLATILE) { decorations |= crate::MemoryDecorations::VOLATILE; } decorations } } #[derive(Debug, PartialEq)] enum Majority { Column, Row, } #[derive(Debug, Default)] struct Decoration { name: Option, built_in: Option, location: Option, index: Option, desc_set: Option, desc_index: Option, specialization_constant_id: Option, storage_buffer: bool, offset: Option, array_stride: Option, matrix_stride: Option, matrix_major: Option, invariant: bool, interpolation: Option, sampling: Option, flags: DecorationFlags, } impl Decoration { const fn debug_name(&self) -> &str { match self.name { Some(ref name) => name.as_str(), None => "?", } } const fn resource_binding(&self) -> Option { match *self { Decoration { desc_set: Some(group), desc_index: Some(binding), .. } => Some(crate::ResourceBinding { group, binding }), _ => None, } } fn io_binding(&self) -> Result { match *self { Decoration { built_in: Some(built_in), location: None, invariant, .. } => Ok(crate::Binding::BuiltIn(map_builtin(built_in, invariant)?)), Decoration { built_in: None, location: Some(location), index: Some(index), .. } => Ok(crate::Binding::Location { location, interpolation: None, sampling: None, blend_src: Some(index), per_primitive: false, }), Decoration { built_in: None, location: Some(location), interpolation, sampling, .. } => Ok(crate::Binding::Location { location, interpolation, sampling, blend_src: None, per_primitive: false, }), _ => Err(Error::MissingDecoration(spirv::Decoration::Location)), } } } #[derive(Debug)] struct LookupFunctionType { parameter_type_ids: Vec, return_type_id: spirv::Word, } struct LookupFunction { handle: Handle, parameters_sampling: Vec, } #[derive(Debug)] struct EntryPoint { stage: crate::ShaderStage, name: String, early_depth_test: Option, workgroup_size: [u32; 3], variable_ids: Vec, } #[derive(Clone, Debug)] struct LookupType { handle: Handle, base_id: Option, } #[derive(Debug)] enum Constant { Constant(Handle), Override(Handle), } impl Constant { const fn to_expr(&self) -> crate::Expression { match *self { Self::Constant(c) => crate::Expression::Constant(c), Self::Override(o) => crate::Expression::Override(o), } } } #[derive(Debug)] struct LookupConstant { inner: Constant, type_id: spirv::Word, } #[derive(Debug)] enum Variable { Global, Input(crate::FunctionArgument), Output(crate::FunctionResult), } #[derive(Debug)] struct LookupVariable { inner: Variable, handle: Handle, type_id: spirv::Word, } /// Information about SPIR-V result ids, stored in `Frontend::lookup_expression`. #[derive(Clone, Debug)] struct LookupExpression { /// The `Expression` constructed for this result. /// /// Note that, while a SPIR-V result id can be used in any block dominated /// by its definition, a Naga `Expression` is only in scope for the rest of /// its subtree. `Frontend::get_expr_handle` takes care of spilling the result /// to a `LocalVariable` which can then be used anywhere. handle: Handle, /// The SPIR-V type of this result. type_id: spirv::Word, /// The label id of the block that defines this expression. /// /// This is zero for globals, constants, and function parameters, since they /// originate outside any function's block. block_id: spirv::Word, } #[derive(Debug)] struct LookupMember { type_id: spirv::Word, // This is true for either matrices, or arrays of matrices (yikes). row_major: bool, } #[derive(Clone, Debug)] enum LookupLoadOverride { /// For arrays of matrices, we track them but not loading yet. Pending, /// For matrices, vectors, and scalars, we pre-load the data. Loaded(Handle), } #[derive(PartialEq)] enum ExtendedClass { Global(crate::AddressSpace), Input, Output, } #[derive(Clone, Debug)] pub struct Options { /// The IR coordinate space matches all the APIs except SPIR-V, /// so by default we flip the Y coordinate of the `BuiltIn::Position`. /// This flag can be used to avoid this. pub adjust_coordinate_space: bool, /// Only allow shaders with the known set of capabilities. pub strict_capabilities: bool, pub block_ctx_dump_prefix: Option, } impl Default for Options { fn default() -> Self { Options { adjust_coordinate_space: true, strict_capabilities: true, block_ctx_dump_prefix: None, } } } /// An index into the `BlockContext::bodies` table. type BodyIndex = usize; /// An intermediate representation of a Naga [`Statement`]. /// /// `Body` and `BodyFragment` values form a tree: the `BodyIndex` fields of the /// variants are indices of the child `Body` values in [`BlockContext::bodies`]. /// The `lower` function assembles the final `Statement` tree from this `Body` /// tree. See [`BlockContext`] for details. /// /// [`Statement`]: crate::Statement #[derive(Debug)] enum BodyFragment { BlockId(spirv::Word), If { condition: Handle, accept: BodyIndex, reject: BodyIndex, }, Loop { /// The body of the loop. Its [`Body::parent`] is the block containing /// this `Loop` fragment. body: BodyIndex, /// The loop's continuing block. This is a grandchild: its /// [`Body::parent`] is the loop body block, whose index is above. continuing: BodyIndex, /// If the SPIR-V loop's back-edge branch is conditional, this is the /// expression that must be `false` for the back-edge to be taken, with /// `true` being for the "loop merge" (which breaks out of the loop). break_if: Option>, }, Switch { selector: Handle, cases: Vec<(i32, BodyIndex)>, default: BodyIndex, }, Break, Continue, } /// An intermediate representation of a Naga [`Block`]. /// /// This will be assembled into a `Block` once we've added spills for phi nodes /// and out-of-scope expressions. See [`BlockContext`] for details. /// /// [`Block`]: crate::Block #[derive(Debug)] struct Body { /// The index of the direct parent of this body parent: usize, data: Vec, } impl Body { /// Creates a new empty `Body` with the specified `parent` pub const fn with_parent(parent: usize) -> Self { Body { parent, data: Vec::new(), } } } #[derive(Debug)] struct PhiExpression { /// The local variable used for the phi node local: Handle, /// List of (expression, block) expressions: Vec<(spirv::Word, spirv::Word)>, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum MergeBlockInformation { LoopMerge, LoopContinue, SelectionMerge, SwitchMerge, } /// Fragments of Naga IR, to be assembled into `Statements` once data flow is /// resolved. /// /// We can't build a Naga `Statement` tree directly from SPIR-V blocks for three /// main reasons: /// /// - We parse a function's SPIR-V blocks in the order they appear in the file. /// Within a function, SPIR-V requires that a block must precede any blocks it /// structurally dominates, but doesn't say much else about the order in which /// they must appear. So while we know we'll see control flow header blocks /// before their child constructs and merge blocks, those children and the /// merge blocks may appear in any order - perhaps even intermingled with /// children of other constructs. /// /// - A SPIR-V expression can be used in any SPIR-V block dominated by its /// definition, whereas Naga expressions are scoped to the rest of their /// subtree. This means that discovering an expression use later in the /// function retroactively requires us to have spilled that expression into a /// local variable back before we left its scope. (The docs for /// [`Frontend::get_expr_handle`] explain this in more detail.) /// /// - We translate SPIR-V OpPhi expressions as Naga local variables in which we /// store the appropriate value before jumping to the OpPhi's block. /// /// All these cases require us to go back and amend previously generated Naga IR /// based on things we discover later. But modifying old blocks in arbitrary /// spots in a `Statement` tree is awkward. /// /// Instead, as we iterate through the function's body, we accumulate /// control-flow-free fragments of Naga IR in the [`blocks`] table, while /// building a skeleton of the Naga `Statement` tree in [`bodies`]. We note any /// spills and temporaries we must introduce in [`phis`]. /// /// Finally, once we've processed the entire function, we add temporaries and /// spills to the fragmentary `Blocks` as directed by `phis`, and assemble them /// into the final Naga `Statement` tree as directed by `bodies`. /// /// [`blocks`]: BlockContext::blocks /// [`bodies`]: BlockContext::bodies /// [`phis`]: BlockContext::phis #[derive(Debug)] struct BlockContext<'function> { /// Phi nodes encountered when parsing the function, used to generate spills /// to local variables. phis: Vec, /// Fragments of control-flow-free Naga IR. /// /// These will be stitched together into a proper [`Statement`] tree according /// to `bodies`, once parsing is complete. /// /// [`Statement`]: crate::Statement blocks: FastHashMap, /// Map from each SPIR-V block's label id to the index of the [`Body`] in /// [`bodies`] the block should append its contents to. /// /// Since each statement in a Naga [`Block`] dominates the next, we are sure /// to encounter their SPIR-V blocks in order. Thus, by having this table /// map a SPIR-V structured control flow construct's merge block to the same /// body index as its header block, when we encounter the merge block, we /// will simply pick up building the [`Body`] where the header left off. /// /// A function's first block is special: it is the only block we encounter /// without having seen its label mentioned in advance. (It's simply the /// first `OpLabel` after the `OpFunction`.) We thus assume that any block /// missing an entry here must be the first block, which always has body /// index zero. /// /// [`bodies`]: BlockContext::bodies /// [`Block`]: crate::Block body_for_label: FastHashMap, /// SPIR-V metadata about merge/continue blocks. mergers: FastHashMap, /// A table of `Body` values, each representing a block in the final IR. /// /// The first element is always the function's top-level block. bodies: Vec, /// The module we're building. module: &'function mut crate::Module, /// Id of the function currently being processed function_id: spirv::Word, /// Expression arena of the function currently being processed expressions: &'function mut Arena, /// Local variables arena of the function currently being processed local_arena: &'function mut Arena, /// Arguments of the function currently being processed arguments: &'function [crate::FunctionArgument], /// Metadata about the usage of function parameters as sampling objects parameter_sampling: &'function mut [image::SamplingFlags], } enum SignAnchor { Result, Operand, } pub struct Frontend { data: I, data_offset: usize, state: ModuleState, layouter: Layouter, temp_bytes: Vec, ext_glsl_id: Option, ext_non_semantic_id: Option, future_decor: FastHashMap, future_member_decor: FastHashMap<(spirv::Word, MemberIndex), Decoration>, lookup_member: FastHashMap<(Handle, MemberIndex), LookupMember>, handle_sampling: FastHashMap, image::SamplingFlags>, /// A record of what is accessed by [`Atomic`] statements we've /// generated, so we can upgrade the types of their operands. /// /// [`Atomic`]: crate::Statement::Atomic upgrade_atomics: Upgrades, lookup_type: FastHashMap, lookup_void_type: Option, lookup_storage_buffer_types: FastHashMap, crate::StorageAccess>, lookup_constant: FastHashMap, lookup_variable: FastHashMap, lookup_expression: FastHashMap, // Load overrides are used to work around row-major matrices lookup_load_override: FastHashMap, lookup_sampled_image: FastHashMap, lookup_function_type: FastHashMap, lookup_function: FastHashMap, lookup_entry_point: FastHashMap, // When parsing functions, each entry point function gets an entry here so that additional // processing for them can be performed after all function parsing. deferred_entry_points: Vec<(EntryPoint, spirv::Word)>, //Note: each `OpFunctionCall` gets a single entry here, indexed by the // dummy `Handle` of the call site. deferred_function_calls: Vec, dummy_functions: Arena, // Graph of all function calls through the module. // It's used to sort the functions (as nodes) topologically, // so that in the IR any called function is already known. function_call_graph: GraphMap< spirv::Word, (), petgraph::Directed, core::hash::BuildHasherDefault, >, options: Options, /// Maps for a switch from a case target to the respective body and associated literals that /// use that target block id. /// /// Used to preserve allocations between instruction parsing. switch_cases: FastIndexMap)>, /// Tracks access to gl_PerVertex's builtins, it is used to cull unused builtins since initializing those can /// affect performance and the mere presence of some of these builtins might cause backends to error since they /// might be unsupported. /// /// The problematic builtins are: PointSize, ClipDistance and CullDistance. /// /// glslang declares those by default even though they are never written to /// (see ) gl_per_vertex_builtin_access: FastHashSet, } impl> Frontend { pub fn new(data: I, options: &Options) -> Self { Frontend { data, data_offset: 0, state: ModuleState::Empty, layouter: Layouter::default(), temp_bytes: Vec::new(), ext_glsl_id: None, ext_non_semantic_id: None, future_decor: FastHashMap::default(), future_member_decor: FastHashMap::default(), handle_sampling: FastHashMap::default(), lookup_member: FastHashMap::default(), upgrade_atomics: Default::default(), lookup_type: FastHashMap::default(), lookup_void_type: None, lookup_storage_buffer_types: FastHashMap::default(), lookup_constant: FastHashMap::default(), lookup_variable: FastHashMap::default(), lookup_expression: FastHashMap::default(), lookup_load_override: FastHashMap::default(), lookup_sampled_image: FastHashMap::default(), lookup_function_type: FastHashMap::default(), lookup_function: FastHashMap::default(), lookup_entry_point: FastHashMap::default(), deferred_entry_points: Vec::default(), deferred_function_calls: Vec::default(), dummy_functions: Arena::new(), function_call_graph: GraphMap::new(), options: options.clone(), switch_cases: FastIndexMap::default(), gl_per_vertex_builtin_access: FastHashSet::default(), } } fn span_from(&self, from: usize) -> crate::Span { crate::Span::from(from..self.data_offset) } fn span_from_with_op(&self, from: usize) -> crate::Span { crate::Span::from((from - 4)..self.data_offset) } fn next(&mut self) -> Result { if let Some(res) = self.data.next() { self.data_offset += 4; Ok(res) } else { Err(Error::IncompleteData) } } fn next_inst(&mut self) -> Result { let word = self.next()?; let (wc, opcode) = ((word >> 16) as u16, (word & 0xffff) as u16); if wc == 0 { return Err(Error::InvalidWordCount); } let op = spirv::Op::from_u32(opcode as u32).ok_or(Error::UnknownInstruction(opcode))?; Ok(Instruction { op, wc }) } fn next_string(&mut self, mut count: u16) -> Result<(String, u16), Error> { self.temp_bytes.clear(); loop { if count == 0 { return Err(Error::BadString); } count -= 1; let chars = self.next()?.to_le_bytes(); let pos = chars.iter().position(|&c| c == 0).unwrap_or(4); self.temp_bytes.extend_from_slice(&chars[..pos]); if pos < 4 { break; } } core::str::from_utf8(&self.temp_bytes) .map(|s| (s.to_owned(), count)) .map_err(|_| Error::BadString) } fn next_decoration( &mut self, inst: Instruction, base_words: u16, dec: &mut Decoration, ) -> Result<(), Error> { let raw = self.next()?; let dec_typed = spirv::Decoration::from_u32(raw).ok_or(Error::InvalidDecoration(raw))?; log::trace!("\t\t{}: {:?}", dec.debug_name(), dec_typed); match dec_typed { spirv::Decoration::BuiltIn => { inst.expect(base_words + 2)?; dec.built_in = Some(self.next()?); } spirv::Decoration::Location => { inst.expect(base_words + 2)?; dec.location = Some(self.next()?); } spirv::Decoration::Index => { inst.expect(base_words + 2)?; dec.index = Some(self.next()?); } spirv::Decoration::DescriptorSet => { inst.expect(base_words + 2)?; dec.desc_set = Some(self.next()?); } spirv::Decoration::Binding => { inst.expect(base_words + 2)?; dec.desc_index = Some(self.next()?); } spirv::Decoration::BufferBlock => { dec.storage_buffer = true; } spirv::Decoration::Offset => { inst.expect(base_words + 2)?; dec.offset = Some(self.next()?); } spirv::Decoration::ArrayStride => { inst.expect(base_words + 2)?; dec.array_stride = NonZeroU32::new(self.next()?); } spirv::Decoration::MatrixStride => { inst.expect(base_words + 2)?; dec.matrix_stride = NonZeroU32::new(self.next()?); } spirv::Decoration::Invariant => { dec.invariant = true; } spirv::Decoration::NoPerspective => { dec.interpolation = Some(crate::Interpolation::Linear); } spirv::Decoration::Flat => { dec.interpolation = Some(crate::Interpolation::Flat); } spirv::Decoration::PerVertexKHR => { dec.interpolation = Some(crate::Interpolation::PerVertex); } spirv::Decoration::Centroid => { dec.sampling = Some(crate::Sampling::Centroid); } spirv::Decoration::Sample => { dec.sampling = Some(crate::Sampling::Sample); } spirv::Decoration::NonReadable => { dec.flags |= DecorationFlags::NON_READABLE; } spirv::Decoration::NonWritable => { dec.flags |= DecorationFlags::NON_WRITABLE; } spirv::Decoration::Coherent => { dec.flags |= DecorationFlags::COHERENT; } spirv::Decoration::Volatile => { dec.flags |= DecorationFlags::VOLATILE; } spirv::Decoration::ColMajor => { dec.matrix_major = Some(Majority::Column); } spirv::Decoration::RowMajor => { dec.matrix_major = Some(Majority::Row); } spirv::Decoration::SpecId => { dec.specialization_constant_id = Some(self.next()?); } other => { let level = match other { // Block decorations show up everywhere and we don't // really care about them, so to prevent log spam // we demote them to debug level. spirv::Decoration::Block => log::Level::Debug, _ => log::Level::Warn, }; log::log!(level, "Unknown decoration {other:?}"); for _ in base_words + 1..inst.wc { let _var = self.next()?; } } } Ok(()) } /// Return the Naga [`Expression`] to use in `body_idx` to refer to the SPIR-V result `id`. /// /// Ideally, we would just have a map from each SPIR-V instruction id to the /// [`Handle`] for the Naga [`Expression`] we generated for it. /// Unfortunately, SPIR-V and Naga IR are different enough that such a /// straightforward relationship isn't possible. /// /// In SPIR-V, an instruction's result id can be used by any instruction /// dominated by that instruction. In Naga, an [`Expression`] is only in /// scope for the remainder of its [`Block`]. In pseudocode: /// /// ```ignore /// loop { /// a = f(); /// g(a); /// break; /// } /// h(a); /// ``` /// /// Suppose the calls to `f`, `g`, and `h` are SPIR-V instructions. In /// SPIR-V, both the `g` and `h` instructions are allowed to refer to `a`, /// because the loop body, including `f`, dominates both of them. /// /// But if `a` is a Naga [`Expression`], its scope ends at the end of the /// block it's evaluated in: the loop body. Thus, while the [`Expression`] /// we generate for `g` can refer to `a`, the one we generate for `h` /// cannot. /// /// Instead, the SPIR-V front end must generate Naga IR like this: /// /// ```ignore /// var temp; // INTRODUCED /// loop { /// a = f(); /// g(a); /// temp = a; // INTRODUCED /// } /// h(temp); // ADJUSTED /// ``` /// /// In other words, where `a` is in scope, [`Expression`]s can refer to it /// directly; but once it is out of scope, we need to spill it to a /// temporary and refer to that instead. /// /// Given a SPIR-V expression `id` and the index `body_idx` of the [body] /// that wants to refer to it: /// /// - If the Naga [`Expression`] we generated for `id` is in scope in /// `body_idx`, then we simply return its `Handle`. /// /// - Otherwise, introduce a new [`LocalVariable`], and add an entry to /// [`BlockContext::phis`] to arrange for `id`'s value to be spilled to /// it. Then emit a fresh [`Load`] of that temporary variable for use in /// `body_idx`'s block, and return its `Handle`. /// /// The SPIR-V domination rule ensures that the introduced [`LocalVariable`] /// will always have been initialized before it is used. /// /// `lookup` must be the [`LookupExpression`] for `id`. /// /// `body_idx` argument must be the index of the [`Body`] that hopes to use /// `id`'s [`Expression`]. /// /// [`Expression`]: crate::Expression /// [`Handle`]: crate::Handle /// [`Block`]: crate::Block /// [body]: BlockContext::bodies /// [`LocalVariable`]: crate::LocalVariable /// [`Load`]: crate::Expression::Load fn get_expr_handle( &self, id: spirv::Word, lookup: &LookupExpression, ctx: &mut BlockContext, emitter: &mut crate::proc::Emitter, block: &mut crate::Block, body_idx: BodyIndex, ) -> Handle { // What `Body` was `id` defined in? let expr_body_idx = ctx .body_for_label .get(&lookup.block_id) .copied() .unwrap_or(0); // Don't need to do a load/store if the expression is in the main body // or if the expression is in the same body as where the query was // requested. The body_idx might actually not be the final one if a loop // or conditional occurs but in those cases we know that the new body // will be a subscope of the body that was passed so we can still reuse // the handle and not issue a load/store. if is_parent(body_idx, expr_body_idx, ctx) { lookup.handle } else { // Add a temporary variable of the same type which will be used to // store the original expression and used in the current block let ty = self.lookup_type[&lookup.type_id].handle; let local = ctx.local_arena.append( crate::LocalVariable { name: None, ty, init: None, }, crate::Span::default(), ); block.extend(emitter.finish(ctx.expressions)); let pointer = ctx.expressions.append( crate::Expression::LocalVariable(local), crate::Span::default(), ); emitter.start(ctx.expressions); let expr = ctx .expressions .append(crate::Expression::Load { pointer }, crate::Span::default()); // Add a slightly odd entry to the phi table, so that while `id`'s // `Expression` is still in scope, the usual phi processing will // spill its value to `local`, where we can find it later. // // This pretends that the block in which `id` is defined is the // predecessor of some other block with a phi in it that cites id as // one of its sources, and uses `local` as its variable. There is no // such phi, but nobody needs to know that. ctx.phis.push(PhiExpression { local, expressions: vec![(id, lookup.block_id)], }); expr } } fn parse_expr_unary_op( &mut self, ctx: &mut BlockContext, emitter: &mut crate::proc::Emitter, block: &mut crate::Block, block_id: spirv::Word, body_idx: usize, op: crate::UnaryOperator, ) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let p_id = self.next()?; let p_lexp = self.lookup_expression.lookup(p_id)?; let handle = self.get_expr_handle(p_id, p_lexp, ctx, emitter, block, body_idx); let expr = crate::Expression::Unary { op, expr: handle }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, self.span_from_with_op(start)), type_id: result_type_id, block_id, }, ); Ok(()) } fn parse_expr_binary_op( &mut self, ctx: &mut BlockContext, emitter: &mut crate::proc::Emitter, block: &mut crate::Block, block_id: spirv::Word, body_idx: usize, op: crate::BinaryOperator, ) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let p1_id = self.next()?; let p2_id = self.next()?; let p1_lexp = self.lookup_expression.lookup(p1_id)?; let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx); let p2_lexp = self.lookup_expression.lookup(p2_id)?; let right = self.get_expr_handle(p2_id, p2_lexp, ctx, emitter, block, body_idx); let expr = crate::Expression::Binary { op, left, right }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, self.span_from_with_op(start)), type_id: result_type_id, block_id, }, ); Ok(()) } /// A more complicated version of the unary op, /// where we force the operand to have the same type as the result. fn parse_expr_unary_op_sign_adjusted( &mut self, ctx: &mut BlockContext, emitter: &mut crate::proc::Emitter, block: &mut crate::Block, block_id: spirv::Word, body_idx: usize, op: crate::UnaryOperator, ) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let p1_id = self.next()?; let span = self.span_from_with_op(start); let p1_lexp = self.lookup_expression.lookup(p1_id)?; let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx); let result_lookup_ty = self.lookup_type.lookup(result_type_id)?; let kind = ctx.module.types[result_lookup_ty.handle] .inner .scalar_kind() .unwrap(); let expr = crate::Expression::Unary { op, expr: if p1_lexp.type_id == result_type_id { left } else { ctx.expressions.append( crate::Expression::As { expr: left, kind, convert: None, }, span, ) }, }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); Ok(()) } /// A more complicated version of the binary op, /// where we force the operand to have the same type as the result. /// This is mostly needed for "i++" and "i--" coming from GLSL. #[allow(clippy::too_many_arguments)] fn parse_expr_binary_op_sign_adjusted( &mut self, ctx: &mut BlockContext, emitter: &mut crate::proc::Emitter, block: &mut crate::Block, block_id: spirv::Word, body_idx: usize, op: crate::BinaryOperator, // For arithmetic operations, we need the sign of operands to match the result. // For boolean operations, however, the operands need to match the signs, but // result is always different - a boolean. anchor: SignAnchor, ) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let p1_id = self.next()?; let p2_id = self.next()?; let span = self.span_from_with_op(start); let p1_lexp = self.lookup_expression.lookup(p1_id)?; let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx); let p2_lexp = self.lookup_expression.lookup(p2_id)?; let right = self.get_expr_handle(p2_id, p2_lexp, ctx, emitter, block, body_idx); let expected_type_id = match anchor { SignAnchor::Result => result_type_id, SignAnchor::Operand => p1_lexp.type_id, }; let expected_lookup_ty = self.lookup_type.lookup(expected_type_id)?; let kind = ctx.module.types[expected_lookup_ty.handle] .inner .scalar_kind() .unwrap(); let expr = crate::Expression::Binary { op, left: if p1_lexp.type_id == expected_type_id { left } else { ctx.expressions.append( crate::Expression::As { expr: left, kind, convert: None, }, span, ) }, right: if p2_lexp.type_id == expected_type_id { right } else { ctx.expressions.append( crate::Expression::As { expr: right, kind, convert: None, }, span, ) }, }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); Ok(()) } /// A version of the binary op where one or both of the arguments might need to be casted to a /// specific integer kind (unsigned or signed), used for operations like OpINotEqual or /// OpUGreaterThan. #[allow(clippy::too_many_arguments)] fn parse_expr_int_comparison( &mut self, ctx: &mut BlockContext, emitter: &mut crate::proc::Emitter, block: &mut crate::Block, block_id: spirv::Word, body_idx: usize, op: crate::BinaryOperator, kind: crate::ScalarKind, ) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let p1_id = self.next()?; let p2_id = self.next()?; let span = self.span_from_with_op(start); let p1_lexp = self.lookup_expression.lookup(p1_id)?; let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx); let p1_lookup_ty = self.lookup_type.lookup(p1_lexp.type_id)?; let p1_kind = ctx.module.types[p1_lookup_ty.handle] .inner .scalar_kind() .unwrap(); let p2_lexp = self.lookup_expression.lookup(p2_id)?; let right = self.get_expr_handle(p2_id, p2_lexp, ctx, emitter, block, body_idx); let p2_lookup_ty = self.lookup_type.lookup(p2_lexp.type_id)?; let p2_kind = ctx.module.types[p2_lookup_ty.handle] .inner .scalar_kind() .unwrap(); let expr = crate::Expression::Binary { op, left: if p1_kind == kind { left } else { ctx.expressions.append( crate::Expression::As { expr: left, kind, convert: None, }, span, ) }, right: if p2_kind == kind { right } else { ctx.expressions.append( crate::Expression::As { expr: right, kind, convert: None, }, span, ) }, }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); Ok(()) } fn parse_expr_shift_op( &mut self, ctx: &mut BlockContext, emitter: &mut crate::proc::Emitter, block: &mut crate::Block, block_id: spirv::Word, body_idx: usize, op: crate::BinaryOperator, ) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let p1_id = self.next()?; let p2_id = self.next()?; let span = self.span_from_with_op(start); let p1_lexp = self.lookup_expression.lookup(p1_id)?; let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx); let p2_lexp = self.lookup_expression.lookup(p2_id)?; let p2_handle = self.get_expr_handle(p2_id, p2_lexp, ctx, emitter, block, body_idx); // convert the shift to Uint let right = ctx.expressions.append( crate::Expression::As { expr: p2_handle, kind: crate::ScalarKind::Uint, convert: None, }, span, ); let expr = crate::Expression::Binary { op, left, right }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); Ok(()) } fn parse_expr_derivative( &mut self, ctx: &mut BlockContext, emitter: &mut crate::proc::Emitter, block: &mut crate::Block, block_id: spirv::Word, body_idx: usize, (axis, ctrl): (crate::DerivativeAxis, crate::DerivativeControl), ) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let arg_id = self.next()?; let arg_lexp = self.lookup_expression.lookup(arg_id)?; let arg_handle = self.get_expr_handle(arg_id, arg_lexp, ctx, emitter, block, body_idx); let expr = crate::Expression::Derivative { axis, ctrl, expr: arg_handle, }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, self.span_from_with_op(start)), type_id: result_type_id, block_id, }, ); Ok(()) } #[allow(clippy::too_many_arguments)] fn insert_composite( &self, root_expr: Handle, root_type_id: spirv::Word, object_expr: Handle, selections: &[spirv::Word], type_arena: &UniqueArena, expressions: &mut Arena, span: crate::Span, ) -> Result, Error> { let selection = match selections.first() { Some(&index) => index, None => return Ok(object_expr), }; let root_span = expressions.get_span(root_expr); let root_lookup = self.lookup_type.lookup(root_type_id)?; let (count, child_type_id) = match type_arena[root_lookup.handle].inner { crate::TypeInner::Struct { ref members, .. } => { let child_member = self .lookup_member .get(&(root_lookup.handle, selection)) .ok_or(Error::InvalidAccessType(root_type_id))?; (members.len(), child_member.type_id) } crate::TypeInner::Array { size, .. } => { let size = match size { crate::ArraySize::Constant(size) => size.get(), crate::ArraySize::Pending(_) => { unreachable!(); } // A runtime sized array is not a composite type crate::ArraySize::Dynamic => { return Err(Error::InvalidAccessType(root_type_id)) } }; let child_type_id = root_lookup .base_id .ok_or(Error::InvalidAccessType(root_type_id))?; (size as usize, child_type_id) } crate::TypeInner::Vector { size, .. } | crate::TypeInner::Matrix { columns: size, .. } => { let child_type_id = root_lookup .base_id .ok_or(Error::InvalidAccessType(root_type_id))?; (size as usize, child_type_id) } _ => return Err(Error::InvalidAccessType(root_type_id)), }; let mut components = Vec::with_capacity(count); for index in 0..count as u32 { let expr = expressions.append( crate::Expression::AccessIndex { base: root_expr, index, }, if index == selection { span } else { root_span }, ); components.push(expr); } components[selection as usize] = self.insert_composite( components[selection as usize], child_type_id, object_expr, &selections[1..], type_arena, expressions, span, )?; Ok(expressions.append( crate::Expression::Compose { ty: root_lookup.handle, components, }, span, )) } /// Return the Naga [`Expression`] for `pointer_id`, and its referent [`Type`]. /// /// Return a [`Handle`] for a Naga [`Expression`] that holds the value of /// the SPIR-V instruction `pointer_id`, along with the [`Type`] to which it /// is a pointer. /// /// This may entail spilling `pointer_id`'s value to a temporary: /// see [`get_expr_handle`]'s documentation. /// /// [`Expression`]: crate::Expression /// [`Type`]: crate::Type /// [`Handle`]: crate::Handle /// [`get_expr_handle`]: Frontend::get_expr_handle fn get_exp_and_base_ty_handles( &self, pointer_id: spirv::Word, ctx: &mut BlockContext, emitter: &mut crate::proc::Emitter, block: &mut crate::Block, body_idx: usize, ) -> Result<(Handle, Handle), Error> { log::trace!("\t\t\tlooking up pointer expr {pointer_id:?}"); let p_lexp_handle; let p_lexp_ty_id; { let lexp = self.lookup_expression.lookup(pointer_id)?; p_lexp_handle = self.get_expr_handle(pointer_id, lexp, ctx, emitter, block, body_idx); p_lexp_ty_id = lexp.type_id; }; log::trace!("\t\t\tlooking up pointer type {pointer_id:?}"); let p_ty = self.lookup_type.lookup(p_lexp_ty_id)?; let p_ty_base_id = p_ty.base_id.ok_or(Error::InvalidAccessType(p_lexp_ty_id))?; log::trace!("\t\t\tlooking up pointer base type {p_ty_base_id:?} of {p_ty:?}"); let p_base_ty = self.lookup_type.lookup(p_ty_base_id)?; Ok((p_lexp_handle, p_base_ty.handle)) } #[allow(clippy::too_many_arguments)] fn parse_atomic_expr_with_value( &mut self, inst: Instruction, emitter: &mut crate::proc::Emitter, ctx: &mut BlockContext, block: &mut crate::Block, block_id: spirv::Word, body_idx: usize, atomic_function: crate::AtomicFunction, ) -> Result<(), Error> { inst.expect(7)?; let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let pointer_id = self.next()?; let _scope_id = self.next()?; let _memory_semantics_id = self.next()?; let value_id = self.next()?; let span = self.span_from_with_op(start); let (p_lexp_handle, p_base_ty_handle) = self.get_exp_and_base_ty_handles(pointer_id, ctx, emitter, block, body_idx)?; log::trace!("\t\t\tlooking up value expr {value_id:?}"); let v_lexp_handle = self.lookup_expression.lookup(value_id)?.handle; block.extend(emitter.finish(ctx.expressions)); // Create an expression for our result let r_lexp_handle = { let expr = crate::Expression::AtomicResult { ty: p_base_ty_handle, comparison: false, }; let handle = ctx.expressions.append(expr, span); self.lookup_expression.insert( result_id, LookupExpression { handle, type_id: result_type_id, block_id, }, ); handle }; emitter.start(ctx.expressions); // Create a statement for the op itself let stmt = crate::Statement::Atomic { pointer: p_lexp_handle, fun: atomic_function, value: v_lexp_handle, result: Some(r_lexp_handle), }; block.push(stmt, span); // Store any associated global variables so we can upgrade their types later self.record_atomic_access(ctx, p_lexp_handle)?; Ok(()) } fn make_expression_storage( &mut self, globals: &Arena, constants: &Arena, overrides: &Arena, ) -> Arena { let mut expressions = Arena::new(); assert!(self.lookup_expression.is_empty()); // register global variables for (&id, var) in self.lookup_variable.iter() { let span = globals.get_span(var.handle); let handle = expressions.append(crate::Expression::GlobalVariable(var.handle), span); self.lookup_expression.insert( id, LookupExpression { type_id: var.type_id, handle, // Setting this to an invalid id will cause get_expr_handle // to default to the main body making sure no load/stores // are added. block_id: 0, }, ); } // register constants for (&id, con) in self.lookup_constant.iter() { let (expr, span) = match con.inner { Constant::Constant(c) => (crate::Expression::Constant(c), constants.get_span(c)), Constant::Override(o) => (crate::Expression::Override(o), overrides.get_span(o)), }; let handle = expressions.append(expr, span); self.lookup_expression.insert( id, LookupExpression { type_id: con.type_id, handle, // Setting this to an invalid id will cause get_expr_handle // to default to the main body making sure no load/stores // are added. block_id: 0, }, ); } // done expressions } fn switch(&mut self, state: ModuleState, op: spirv::Op) -> Result<(), Error> { if state < self.state { Err(Error::UnsupportedInstruction(self.state, op)) } else { self.state = state; Ok(()) } } /// Walk the statement tree and patch it in the following cases: /// 1. Function call targets are replaced by `deferred_function_calls` map fn patch_statements( &mut self, statements: &mut crate::Block, expressions: &mut Arena, fun_parameter_sampling: &mut [image::SamplingFlags], ) -> Result<(), Error> { use crate::Statement as S; let mut i = 0usize; while i < statements.len() { match statements[i] { S::Emit(_) => {} S::Block(ref mut block) => { self.patch_statements(block, expressions, fun_parameter_sampling)?; } S::If { condition: _, ref mut accept, ref mut reject, } => { self.patch_statements(reject, expressions, fun_parameter_sampling)?; self.patch_statements(accept, expressions, fun_parameter_sampling)?; } S::Switch { selector: _, ref mut cases, } => { for case in cases.iter_mut() { self.patch_statements(&mut case.body, expressions, fun_parameter_sampling)?; } } S::Loop { ref mut body, ref mut continuing, break_if: _, } => { self.patch_statements(body, expressions, fun_parameter_sampling)?; self.patch_statements(continuing, expressions, fun_parameter_sampling)?; } S::Break | S::Continue | S::Return { .. } | S::Kill | S::ControlBarrier(_) | S::MemoryBarrier(_) | S::Store { .. } | S::ImageStore { .. } | S::Atomic { .. } | S::ImageAtomic { .. } | S::RayQuery { .. } | S::SubgroupBallot { .. } | S::SubgroupCollectiveOperation { .. } | S::SubgroupGather { .. } | S::RayPipelineFunction(..) => {} S::Call { function: ref mut callee, ref arguments, .. } => { let fun_id = self.deferred_function_calls[callee.index()]; let fun_lookup = self.lookup_function.lookup(fun_id)?; *callee = fun_lookup.handle; // Patch sampling flags for (arg_index, arg) in arguments.iter().enumerate() { let flags = match fun_lookup.parameters_sampling.get(arg_index) { Some(&flags) if !flags.is_empty() => flags, _ => continue, }; match expressions[*arg] { crate::Expression::GlobalVariable(handle) => { if let Some(sampling) = self.handle_sampling.get_mut(&handle) { *sampling |= flags } } crate::Expression::FunctionArgument(i) => { fun_parameter_sampling[i as usize] |= flags; } ref other => return Err(Error::InvalidGlobalVar(other.clone())), } } } S::WorkGroupUniformLoad { .. } => unreachable!(), S::CooperativeStore { .. } => unreachable!(), } i += 1; } Ok(()) } fn patch_function( &mut self, handle: Option>, fun: &mut crate::Function, ) -> Result<(), Error> { // Note: this search is a bit unfortunate let (fun_id, mut parameters_sampling) = match handle { Some(h) => { let (&fun_id, lookup) = self .lookup_function .iter_mut() .find(|&(_, ref lookup)| lookup.handle == h) .unwrap(); (fun_id, mem::take(&mut lookup.parameters_sampling)) } None => (0, Vec::new()), }; for (_, expr) in fun.expressions.iter_mut() { if let crate::Expression::CallResult(ref mut function) = *expr { let fun_id = self.deferred_function_calls[function.index()]; *function = self.lookup_function.lookup(fun_id)?.handle; } } self.patch_statements( &mut fun.body, &mut fun.expressions, &mut parameters_sampling, )?; if let Some(lookup) = self.lookup_function.get_mut(&fun_id) { lookup.parameters_sampling = parameters_sampling; } Ok(()) } pub fn parse(mut self) -> Result { let mut module = { if self.next()? != spirv::MAGIC_NUMBER { return Err(Error::InvalidHeader); } let version_raw = self.next()?; let generator = self.next()?; let _bound = self.next()?; let _schema = self.next()?; log::debug!("Generated by {generator} version {version_raw:x}"); crate::Module::default() }; self.layouter.clear(); self.dummy_functions = Arena::new(); self.lookup_function.clear(); self.function_call_graph.clear(); loop { use spirv::Op; let inst = match self.next_inst() { Ok(inst) => inst, Err(Error::IncompleteData) => break, Err(other) => return Err(other), }; log::debug!("\t{:?} [{}]", inst.op, inst.wc); match inst.op { Op::Capability => self.parse_capability(inst), Op::Extension => self.parse_extension(inst), Op::ExtInstImport => self.parse_ext_inst_import(inst), Op::MemoryModel => self.parse_memory_model(inst), Op::EntryPoint => self.parse_entry_point(inst), Op::ExecutionMode => self.parse_execution_mode(inst), Op::String => self.parse_string(inst), Op::Source => self.parse_source(inst), Op::SourceExtension => self.parse_source_extension(inst), Op::Name => self.parse_name(inst), Op::MemberName => self.parse_member_name(inst), Op::ModuleProcessed => self.parse_module_processed(inst), Op::Decorate => self.parse_decorate(inst), Op::MemberDecorate => self.parse_member_decorate(inst), Op::TypeVoid => self.parse_type_void(inst), Op::TypeBool => self.parse_type_bool(inst, &mut module), Op::TypeInt => self.parse_type_int(inst, &mut module), Op::TypeFloat => self.parse_type_float(inst, &mut module), Op::TypeVector => self.parse_type_vector(inst, &mut module), Op::TypeMatrix => self.parse_type_matrix(inst, &mut module), Op::TypeFunction => self.parse_type_function(inst), Op::TypePointer => self.parse_type_pointer(inst, &mut module), Op::TypeArray => self.parse_type_array(inst, &mut module), Op::TypeRuntimeArray => self.parse_type_runtime_array(inst, &mut module), Op::TypeStruct => self.parse_type_struct(inst, &mut module), Op::TypeImage => self.parse_type_image(inst, &mut module), Op::TypeSampledImage => self.parse_type_sampled_image(inst), Op::TypeSampler => self.parse_type_sampler(inst, &mut module), Op::Constant | Op::SpecConstant => self.parse_constant(inst, &mut module), Op::ConstantComposite | Op::SpecConstantComposite => { self.parse_composite_constant(inst, &mut module) } Op::ConstantNull | Op::Undef => self.parse_null_constant(inst, &mut module), Op::ConstantTrue | Op::SpecConstantTrue => { self.parse_bool_constant(inst, true, &mut module) } Op::ConstantFalse | Op::SpecConstantFalse => { self.parse_bool_constant(inst, false, &mut module) } Op::Variable => self.parse_global_variable(inst, &mut module), Op::Function => { self.switch(ModuleState::Function, inst.op)?; inst.expect(5)?; self.parse_function(&mut module) } Op::ExtInst => { // Ignore the result type and result id let _ = self.next()?; let _ = self.next()?; let set_id = self.next()?; if Some(set_id) == self.ext_non_semantic_id { // We've already skipped the instruction byte, result type, result id, and instruction set id for _ in 0..inst.wc - 4 { self.next()?; } Ok(()) } else { return Err(Error::UnsupportedInstruction(self.state, inst.op)); } } _ => Err(Error::UnsupportedInstruction(self.state, inst.op)), //TODO }?; } if !self.upgrade_atomics.is_empty() { log::debug!("Upgrading atomic pointers..."); module.upgrade_atomics(&self.upgrade_atomics)?; } // Do entry point specific processing after all functions are parsed so that we can // cull unused problematic builtins of gl_PerVertex. for (ep, fun_id) in mem::take(&mut self.deferred_entry_points) { self.process_entry_point(&mut module, ep, fun_id)?; } log::debug!("Patching..."); { let mut nodes = petgraph::algo::toposort(&self.function_call_graph, None) .map_err(|cycle| Error::FunctionCallCycle(cycle.node_id()))?; nodes.reverse(); // we need dominated first let mut functions = mem::take(&mut module.functions); for fun_id in nodes { if fun_id > !(functions.len() as u32) { // skip all the fake IDs registered for the entry points continue; } let lookup = self.lookup_function.get_mut(&fun_id).unwrap(); // take out the function from the old array let fun = mem::take(&mut functions[lookup.handle]); // add it to the newly formed arena, and adjust the lookup lookup.handle = module .functions .append(fun, functions.get_span(lookup.handle)); } } // patch all the functions for (handle, fun) in module.functions.iter_mut() { self.patch_function(Some(handle), fun)?; } for ep in module.entry_points.iter_mut() { self.patch_function(None, &mut ep.function)?; } // Check all the images and samplers to have consistent comparison property. for (handle, flags) in self.handle_sampling.drain() { if !image::patch_comparison_type( flags, module.global_variables.get_mut(handle), &mut module.types, ) { return Err(Error::InconsistentComparisonSampling(handle)); } } if !self.future_decor.is_empty() { log::debug!("Unused item decorations: {:?}", self.future_decor); self.future_decor.clear(); } if !self.future_member_decor.is_empty() { log::debug!("Unused member decorations: {:?}", self.future_member_decor); self.future_member_decor.clear(); } Ok(module) } fn parse_capability(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Capability, inst.op)?; inst.expect(2)?; let capability = self.next()?; let cap = spirv::Capability::from_u32(capability).ok_or(Error::UnknownCapability(capability))?; if !SUPPORTED_CAPABILITIES.contains(&cap) { if self.options.strict_capabilities { return Err(Error::UnsupportedCapability(cap)); } else { log::warn!("Unknown capability {cap:?}"); } } Ok(()) } fn parse_extension(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Extension, inst.op)?; inst.expect_at_least(2)?; let (name, left) = self.next_string(inst.wc - 1)?; if left != 0 { return Err(Error::InvalidOperand); } if !SUPPORTED_EXTENSIONS.contains(&name.as_str()) { return Err(Error::UnsupportedExtension(name)); } Ok(()) } fn parse_ext_inst_import(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Extension, inst.op)?; inst.expect_at_least(3)?; let result_id = self.next()?; let (name, left) = self.next_string(inst.wc - 2)?; if left != 0 { return Err(Error::InvalidOperand); } if &name == "GLSL.std.450" { self.ext_glsl_id = Some(result_id); } else if &name == "NonSemantic.Shader.DebugInfo.100" { // We completely ignore this extension. All related instructions are // non-semantic and only for debug purposes, and the spec says they // are ignorable. Many compilers (dxc, slang, etc) will emit these // instructions depending on configuration. self.ext_non_semantic_id = Some(result_id); } else { return Err(Error::UnsupportedExtSet(name)); } Ok(()) } fn parse_memory_model(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::MemoryModel, inst.op)?; inst.expect(3)?; let _addressing_model = self.next()?; let _memory_model = self.next()?; Ok(()) } fn parse_entry_point(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::EntryPoint, inst.op)?; inst.expect_at_least(4)?; let exec_model = self.next()?; let exec_model = spirv::ExecutionModel::from_u32(exec_model) .ok_or(Error::UnsupportedExecutionModel(exec_model))?; let function_id = self.next()?; let (name, left) = self.next_string(inst.wc - 3)?; let ep = EntryPoint { stage: match exec_model { spirv::ExecutionModel::Vertex => crate::ShaderStage::Vertex, spirv::ExecutionModel::Fragment => crate::ShaderStage::Fragment, spirv::ExecutionModel::GLCompute => crate::ShaderStage::Compute, spirv::ExecutionModel::TaskEXT => crate::ShaderStage::Task, spirv::ExecutionModel::MeshEXT => crate::ShaderStage::Mesh, _ => return Err(Error::UnsupportedExecutionModel(exec_model as u32)), }, name, early_depth_test: None, workgroup_size: [0; 3], variable_ids: self.data.by_ref().take(left as usize).collect(), }; self.lookup_entry_point.insert(function_id, ep); Ok(()) } fn parse_execution_mode(&mut self, inst: Instruction) -> Result<(), Error> { use spirv::ExecutionMode; self.switch(ModuleState::ExecutionMode, inst.op)?; inst.expect_at_least(3)?; let ep_id = self.next()?; let mode_id = self.next()?; let args: Vec = self.data.by_ref().take(inst.wc as usize - 3).collect(); let ep = self .lookup_entry_point .get_mut(&ep_id) .ok_or(Error::InvalidId(ep_id))?; let mode = ExecutionMode::from_u32(mode_id).ok_or(Error::UnsupportedExecutionMode(mode_id))?; match mode { ExecutionMode::EarlyFragmentTests => { ep.early_depth_test = Some(crate::EarlyDepthTest::Force); } ExecutionMode::DepthUnchanged => { if let &mut Some(ref mut early_depth_test) = &mut ep.early_depth_test { if let &mut crate::EarlyDepthTest::Allow { ref mut conservative, } = early_depth_test { *conservative = crate::ConservativeDepth::Unchanged; } } else { ep.early_depth_test = Some(crate::EarlyDepthTest::Allow { conservative: crate::ConservativeDepth::Unchanged, }); } } ExecutionMode::DepthGreater => { if let &mut Some(ref mut early_depth_test) = &mut ep.early_depth_test { if let &mut crate::EarlyDepthTest::Allow { ref mut conservative, } = early_depth_test { *conservative = crate::ConservativeDepth::GreaterEqual; } } else { ep.early_depth_test = Some(crate::EarlyDepthTest::Allow { conservative: crate::ConservativeDepth::GreaterEqual, }); } } ExecutionMode::DepthLess => { if let &mut Some(ref mut early_depth_test) = &mut ep.early_depth_test { if let &mut crate::EarlyDepthTest::Allow { ref mut conservative, } = early_depth_test { *conservative = crate::ConservativeDepth::LessEqual; } } else { ep.early_depth_test = Some(crate::EarlyDepthTest::Allow { conservative: crate::ConservativeDepth::LessEqual, }); } } ExecutionMode::DepthReplacing => { // Ignored because it can be deduced from the IR. } ExecutionMode::OriginUpperLeft => { // Ignored because the other option (OriginLowerLeft) is not valid in Vulkan mode. } ExecutionMode::LocalSize => { ep.workgroup_size = [args[0], args[1], args[2]]; } _ => { return Err(Error::UnsupportedExecutionMode(mode_id)); } } Ok(()) } fn parse_string(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Source, inst.op)?; inst.expect_at_least(3)?; let _id = self.next()?; let (_name, _) = self.next_string(inst.wc - 2)?; Ok(()) } fn parse_source(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Source, inst.op)?; for _ in 1..inst.wc { let _ = self.next()?; } Ok(()) } fn parse_source_extension(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Source, inst.op)?; inst.expect_at_least(2)?; let (_name, _) = self.next_string(inst.wc - 1)?; Ok(()) } fn parse_name(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Name, inst.op)?; inst.expect_at_least(3)?; let id = self.next()?; let (name, left) = self.next_string(inst.wc - 2)?; if left != 0 { return Err(Error::InvalidOperand); } self.future_decor.entry(id).or_default().name = Some(name); Ok(()) } fn parse_member_name(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Name, inst.op)?; inst.expect_at_least(4)?; let id = self.next()?; let member = self.next()?; let (name, left) = self.next_string(inst.wc - 3)?; if left != 0 { return Err(Error::InvalidOperand); } self.future_member_decor .entry((id, member)) .or_default() .name = Some(name); Ok(()) } fn parse_module_processed(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Name, inst.op)?; inst.expect_at_least(2)?; let (_info, left) = self.next_string(inst.wc - 1)?; //Note: string is ignored if left != 0 { return Err(Error::InvalidOperand); } Ok(()) } fn parse_decorate(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Annotation, inst.op)?; inst.expect_at_least(3)?; let id = self.next()?; let mut dec = self.future_decor.remove(&id).unwrap_or_default(); self.next_decoration(inst, 2, &mut dec)?; self.future_decor.insert(id, dec); Ok(()) } fn parse_member_decorate(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Annotation, inst.op)?; inst.expect_at_least(4)?; let id = self.next()?; let member = self.next()?; let mut dec = self .future_member_decor .remove(&(id, member)) .unwrap_or_default(); self.next_decoration(inst, 3, &mut dec)?; self.future_member_decor.insert((id, member), dec); Ok(()) } fn parse_type_void(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Type, inst.op)?; inst.expect(2)?; let id = self.next()?; self.lookup_void_type = Some(id); Ok(()) } fn parse_type_bool( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect(2)?; let id = self.next()?; let inner = crate::TypeInner::Scalar(crate::Scalar::BOOL); self.lookup_type.insert( id, LookupType { handle: module.types.insert( crate::Type { name: self.future_decor.remove(&id).and_then(|dec| dec.name), inner, }, self.span_from_with_op(start), ), base_id: None, }, ); Ok(()) } fn parse_type_int( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect(4)?; let id = self.next()?; let width = self.next()?; let sign = self.next()?; let inner = crate::TypeInner::Scalar(crate::Scalar { kind: match sign { 0 => crate::ScalarKind::Uint, 1 => crate::ScalarKind::Sint, _ => return Err(Error::InvalidSign(sign)), }, width: map_width(width)?, }); self.lookup_type.insert( id, LookupType { handle: module.types.insert( crate::Type { name: self.future_decor.remove(&id).and_then(|dec| dec.name), inner, }, self.span_from_with_op(start), ), base_id: None, }, ); Ok(()) } fn parse_type_float( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect(3)?; let id = self.next()?; let width = self.next()?; let inner = crate::TypeInner::Scalar(crate::Scalar::float(map_width(width)?)); self.lookup_type.insert( id, LookupType { handle: module.types.insert( crate::Type { name: self.future_decor.remove(&id).and_then(|dec| dec.name), inner, }, self.span_from_with_op(start), ), base_id: None, }, ); Ok(()) } fn parse_type_vector( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect(4)?; let id = self.next()?; let type_id = self.next()?; let type_lookup = self.lookup_type.lookup(type_id)?; let scalar = match module.types[type_lookup.handle].inner { crate::TypeInner::Scalar(scalar) => scalar, _ => return Err(Error::InvalidInnerType(type_id)), }; let component_count = self.next()?; let inner = crate::TypeInner::Vector { size: map_vector_size(component_count)?, scalar, }; self.lookup_type.insert( id, LookupType { handle: module.types.insert( crate::Type { name: self.future_decor.remove(&id).and_then(|dec| dec.name), inner, }, self.span_from_with_op(start), ), base_id: Some(type_id), }, ); Ok(()) } fn parse_type_matrix( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect(4)?; let id = self.next()?; let vector_type_id = self.next()?; let num_columns = self.next()?; let decor = self.future_decor.remove(&id); let vector_type_lookup = self.lookup_type.lookup(vector_type_id)?; let inner = match module.types[vector_type_lookup.handle].inner { crate::TypeInner::Vector { size, scalar } => crate::TypeInner::Matrix { columns: map_vector_size(num_columns)?, rows: size, scalar, }, _ => return Err(Error::InvalidInnerType(vector_type_id)), }; self.lookup_type.insert( id, LookupType { handle: module.types.insert( crate::Type { name: decor.and_then(|dec| dec.name), inner, }, self.span_from_with_op(start), ), base_id: Some(vector_type_id), }, ); Ok(()) } fn parse_type_function(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Type, inst.op)?; inst.expect_at_least(3)?; let id = self.next()?; let return_type_id = self.next()?; let parameter_type_ids = self.data.by_ref().take(inst.wc as usize - 3).collect(); self.lookup_function_type.insert( id, LookupFunctionType { parameter_type_ids, return_type_id, }, ); Ok(()) } fn parse_type_pointer( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect(4)?; let id = self.next()?; let storage_class = self.next()?; let type_id = self.next()?; let decor = self.future_decor.remove(&id); let base_lookup_ty = self.lookup_type.lookup(type_id)?; let base_inner = &module.types[base_lookup_ty.handle].inner; let space = if let Some(space) = base_inner.pointer_space() { space } else if self .lookup_storage_buffer_types .contains_key(&base_lookup_ty.handle) { crate::AddressSpace::Storage { access: crate::StorageAccess::default(), } } else { match map_storage_class(storage_class)? { ExtendedClass::Global(space) => space, ExtendedClass::Input | ExtendedClass::Output => crate::AddressSpace::Private, } }; // We don't support pointers to runtime-sized arrays in the `Uniform` // storage class with the `BufferBlock` decoration. Runtime-sized arrays // should be in the StorageBuffer class. if let crate::TypeInner::Array { size: crate::ArraySize::Dynamic, .. } = *base_inner { match space { crate::AddressSpace::Storage { .. } => {} _ => { return Err(Error::UnsupportedRuntimeArrayStorageClass); } } } // Don't bother with pointer stuff for `Handle` types. let lookup_ty = if space == crate::AddressSpace::Handle { base_lookup_ty.clone() } else { LookupType { handle: module.types.insert( crate::Type { name: decor.and_then(|dec| dec.name), inner: crate::TypeInner::Pointer { base: base_lookup_ty.handle, space, }, }, self.span_from_with_op(start), ), base_id: Some(type_id), } }; self.lookup_type.insert(id, lookup_ty); Ok(()) } fn parse_type_array( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect(4)?; let id = self.next()?; let type_id = self.next()?; let length_id = self.next()?; let length_const = self.lookup_constant.lookup(length_id)?; let size = resolve_constant(module.to_ctx(), &length_const.inner) .and_then(NonZeroU32::new) .ok_or(Error::InvalidArraySize(length_id))?; let decor = self.future_decor.remove(&id).unwrap_or_default(); let base = self.lookup_type.lookup(type_id)?.handle; self.layouter.update(module.to_ctx()).unwrap(); // HACK if the underlying type is an image or a sampler, let's assume // that we're dealing with a binding-array // // Note that it's not a strictly correct assumption, but rather a trade // off caused by an impedance mismatch between SPIR-V's and Naga's type // systems - Naga distinguishes between arrays and binding-arrays via // types (i.e. both kinds of arrays are just different types), while // SPIR-V distinguishes between them through usage - e.g. given: // // ``` // %image = OpTypeImage %float 2D 2 0 0 2 Rgba16f // %uint_256 = OpConstant %uint 256 // %image_array = OpTypeArray %image %uint_256 // ``` // // ``` // %image = OpTypeImage %float 2D 2 0 0 2 Rgba16f // %uint_256 = OpConstant %uint 256 // %image_array = OpTypeArray %image %uint_256 // %image_array_ptr = OpTypePointer UniformConstant %image_array // ``` // // ... in the first case, `%image_array` should technically correspond // to `TypeInner::Array`, while in the second case it should say // `TypeInner::BindingArray` (kinda, depending on whether `%image_array` // is ever used as a freestanding type or rather always through the // pointer-indirection). // // Anyway, at the moment we don't support other kinds of image / sampler // arrays than those binding-based, so this assumption is pretty safe // for now. let inner = if let crate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } = module.types[base].inner { crate::TypeInner::BindingArray { base, size: crate::ArraySize::Constant(size), } } else { crate::TypeInner::Array { base, size: crate::ArraySize::Constant(size), stride: match decor.array_stride { Some(stride) => stride.get(), None => self.layouter[base].to_stride(), }, } }; self.lookup_type.insert( id, LookupType { handle: module.types.insert( crate::Type { name: decor.name, inner, }, self.span_from_with_op(start), ), base_id: Some(type_id), }, ); Ok(()) } fn parse_type_runtime_array( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect(3)?; let id = self.next()?; let type_id = self.next()?; let decor = self.future_decor.remove(&id).unwrap_or_default(); let base = self.lookup_type.lookup(type_id)?.handle; self.layouter.update(module.to_ctx()).unwrap(); // HACK same case as in `parse_type_array()` let inner = if let crate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } = module.types[base].inner { crate::TypeInner::BindingArray { base: self.lookup_type.lookup(type_id)?.handle, size: crate::ArraySize::Dynamic, } } else { crate::TypeInner::Array { base: self.lookup_type.lookup(type_id)?.handle, size: crate::ArraySize::Dynamic, stride: match decor.array_stride { Some(stride) => stride.get(), None => self.layouter[base].to_stride(), }, } }; self.lookup_type.insert( id, LookupType { handle: module.types.insert( crate::Type { name: decor.name, inner, }, self.span_from_with_op(start), ), base_id: Some(type_id), }, ); Ok(()) } fn parse_type_struct( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect_at_least(2)?; let id = self.next()?; let parent_decor = self.future_decor.remove(&id); let is_storage_buffer = parent_decor .as_ref() .is_some_and(|decor| decor.storage_buffer); self.layouter.update(module.to_ctx()).unwrap(); let mut members = Vec::::with_capacity(inst.wc as usize - 2); let mut member_lookups = Vec::with_capacity(members.capacity()); let mut storage_access = crate::StorageAccess::empty(); let mut span = 0; let mut alignment = Alignment::ONE; for i in 0..u32::from(inst.wc) - 2 { let type_id = self.next()?; let ty = self.lookup_type.lookup(type_id)?.handle; let decor = self .future_member_decor .remove(&(id, i)) .unwrap_or_default(); storage_access |= decor.flags.to_storage_access(); member_lookups.push(LookupMember { type_id, row_major: decor.matrix_major == Some(Majority::Row), }); let member_alignment = self.layouter[ty].alignment; span = member_alignment.round_up(span); alignment = member_alignment.max(alignment); let binding = decor.io_binding().ok(); if let Some(offset) = decor.offset { span = offset; } let offset = span; span += self.layouter[ty].size; let inner = &module.types[ty].inner; if let crate::TypeInner::Matrix { columns, rows, scalar, } = *inner { if let Some(stride) = decor.matrix_stride { let expected_stride = Alignment::from(rows) * scalar.width as u32; if stride.get() != expected_stride { return Err(Error::UnsupportedMatrixStride { stride: stride.get(), columns: columns as u8, rows: rows as u8, width: scalar.width, }); } } } members.push(crate::StructMember { name: decor.name, ty, binding, offset, }); } span = alignment.round_up(span); let inner = crate::TypeInner::Struct { span, members }; let ty_handle = module.types.insert( crate::Type { name: parent_decor.and_then(|dec| dec.name), inner, }, self.span_from_with_op(start), ); if is_storage_buffer { self.lookup_storage_buffer_types .insert(ty_handle, storage_access); } for (i, member_lookup) in member_lookups.into_iter().enumerate() { self.lookup_member .insert((ty_handle, i as u32), member_lookup); } self.lookup_type.insert( id, LookupType { handle: ty_handle, base_id: None, }, ); Ok(()) } fn parse_type_image( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect(9)?; let id = self.next()?; let sample_type_id = self.next()?; let dim = self.next()?; let is_depth = self.next()?; let is_array = self.next()? != 0; let is_msaa = self.next()? != 0; let is_sampled = self.next()?; let format = self.next()?; let dim = map_image_dim(dim)?; let decor = self.future_decor.remove(&id).unwrap_or_default(); // ensure there is a type for texture coordinate without extra components module.types.insert( crate::Type { name: None, inner: { let scalar = crate::Scalar::F32; match dim.required_coordinate_size() { None => crate::TypeInner::Scalar(scalar), Some(size) => crate::TypeInner::Vector { size, scalar }, } }, }, Default::default(), ); let base_handle = self.lookup_type.lookup(sample_type_id)?.handle; let kind = module.types[base_handle] .inner .scalar_kind() .ok_or(Error::InvalidImageBaseType(base_handle))?; let inner = crate::TypeInner::Image { class: if is_depth == 1 { if is_sampled == 2 { return Err(Error::InvalidImageDepthStorage); } crate::ImageClass::Depth { multi: is_msaa } } // If we have an unknown format and storage texture, this is // StorageRead/WriteWithoutFormat. We don't currently support // this. else if is_sampled == 2 && format == 0 { return Err(Error::InvalidStorageImageWithoutFormat); } // If we have explicit class information (is_sampled = 2 = Storage), use it. // // If we have unknown class information (is_sampled = 0 = Unknown), infer the // class from the presence of an explicit format. else if format != 0 && (is_sampled == 0 || is_sampled == 2) { crate::ImageClass::Storage { format: map_image_format(format)?, access: crate::StorageAccess::default(), } } // We will hit this case either when sampled is 1, or if we have unknown // sampling information or when sampled is 0 and we have no explicit format. else { crate::ImageClass::Sampled { kind, multi: is_msaa, } }, dim, arrayed: is_array, }; let handle = module.types.insert( crate::Type { name: decor.name, inner, }, self.span_from_with_op(start), ); self.lookup_type.insert( id, LookupType { handle, base_id: Some(sample_type_id), }, ); Ok(()) } fn parse_type_sampled_image(&mut self, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Type, inst.op)?; inst.expect(3)?; let id = self.next()?; let image_id = self.next()?; self.lookup_type.insert( id, LookupType { handle: self.lookup_type.lookup(image_id)?.handle, base_id: Some(image_id), }, ); Ok(()) } fn parse_type_sampler( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect(2)?; let id = self.next()?; let decor = self.future_decor.remove(&id).unwrap_or_default(); let handle = module.types.insert( crate::Type { name: decor.name, inner: crate::TypeInner::Sampler { comparison: false }, }, self.span_from_with_op(start), ); self.lookup_type.insert( id, LookupType { handle, base_id: None, }, ); Ok(()) } fn parse_constant( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect_at_least(4)?; let type_id = self.next()?; let id = self.next()?; let type_lookup = self.lookup_type.lookup(type_id)?; let ty = type_lookup.handle; let literal = match module.types[ty].inner { crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Uint, width, }) => { let low = self.next()?; match width { 4 => crate::Literal::U32(low), 8 => { inst.expect(5)?; let high = self.next()?; crate::Literal::U64((u64::from(high) << 32) | u64::from(low)) } _ => return Err(Error::InvalidTypeWidth(width as u32)), } } crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Sint, width, }) => { let low = self.next()?; match width { 4 => crate::Literal::I32(low as i32), 8 => { inst.expect(5)?; let high = self.next()?; crate::Literal::I64(((u64::from(high) << 32) | u64::from(low)) as i64) } _ => return Err(Error::InvalidTypeWidth(width as u32)), } } crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Float, width, }) => { let low = self.next()?; match width { // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#Literal // If a numeric type’s bit width is less than 32-bits, the value appears in the low-order bits of the word. 2 => crate::Literal::F16(f16::from_bits(low as u16)), 4 => crate::Literal::F32(f32::from_bits(low)), 8 => { inst.expect(5)?; let high = self.next()?; crate::Literal::F64(f64::from_bits( (u64::from(high) << 32) | u64::from(low), )) } _ => return Err(Error::InvalidTypeWidth(width as u32)), } } _ => return Err(Error::UnsupportedType(type_lookup.handle)), }; let span = self.span_from_with_op(start); let init = module .global_expressions .append(crate::Expression::Literal(literal), span); self.insert_parsed_constant(module, id, type_id, ty, init, span) } fn parse_composite_constant( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect_at_least(3)?; let type_id = self.next()?; let id = self.next()?; let type_lookup = self.lookup_type.lookup(type_id)?; let ty = type_lookup.handle; let mut components = Vec::with_capacity(inst.wc as usize - 3); for _ in 0..components.capacity() { let start = self.data_offset; let component_id = self.next()?; let span = self.span_from_with_op(start); let constant = self.lookup_constant.lookup(component_id)?; let expr = module .global_expressions .append(constant.inner.to_expr(), span); components.push(expr); } let span = self.span_from_with_op(start); let init = module .global_expressions .append(crate::Expression::Compose { ty, components }, span); self.insert_parsed_constant(module, id, type_id, ty, init, span) } fn parse_null_constant( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect(3)?; let type_id = self.next()?; let id = self.next()?; let span = self.span_from_with_op(start); let type_lookup = self.lookup_type.lookup(type_id)?; let ty = type_lookup.handle; let init = module .global_expressions .append(crate::Expression::ZeroValue(ty), span); self.insert_parsed_constant(module, id, type_id, ty, init, span) } fn parse_bool_constant( &mut self, inst: Instruction, value: bool, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect(3)?; let type_id = self.next()?; let id = self.next()?; let span = self.span_from_with_op(start); let type_lookup = self.lookup_type.lookup(type_id)?; let ty = type_lookup.handle; let init = module.global_expressions.append( crate::Expression::Literal(crate::Literal::Bool(value)), span, ); self.insert_parsed_constant(module, id, type_id, ty, init, span) } fn insert_parsed_constant( &mut self, module: &mut crate::Module, id: u32, type_id: u32, ty: Handle, init: Handle, span: crate::Span, ) -> Result<(), Error> { let decor = self.future_decor.remove(&id).unwrap_or_default(); let inner = if let Some(id) = decor.specialization_constant_id { let o = crate::Override { name: decor.name, id: Some(id.try_into().map_err(|_| Error::SpecIdTooHigh(id))?), ty, init: Some(init), }; Constant::Override(module.overrides.append(o, span)) } else { let c = crate::Constant { name: decor.name, ty, init, }; Constant::Constant(module.constants.append(c, span)) }; self.lookup_constant .insert(id, LookupConstant { inner, type_id }); Ok(()) } fn parse_global_variable( &mut self, inst: Instruction, module: &mut crate::Module, ) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?; inst.expect_at_least(4)?; let type_id = self.next()?; let id = self.next()?; let storage_class = self.next()?; let init = if inst.wc > 4 { inst.expect(5)?; let start = self.data_offset; let init_id = self.next()?; let span = self.span_from_with_op(start); let lconst = self.lookup_constant.lookup(init_id)?; let expr = module .global_expressions .append(lconst.inner.to_expr(), span); Some(expr) } else { None }; let span = self.span_from_with_op(start); let dec = self.future_decor.remove(&id).unwrap_or_default(); let original_ty = self.lookup_type.lookup(type_id)?.handle; let mut ty = original_ty; if let crate::TypeInner::Pointer { base, space: _ } = module.types[original_ty].inner { ty = base; } if let crate::TypeInner::BindingArray { .. } = module.types[original_ty].inner { // Inside `parse_type_array()` we guess that an array of images or // samplers must be a binding array, and here we validate that guess if dec.desc_set.is_none() || dec.desc_index.is_none() { return Err(Error::NonBindingArrayOfImageOrSamplers); } } if let crate::TypeInner::Image { dim, arrayed, class: crate::ImageClass::Storage { format, access: _ }, } = module.types[ty].inner { // Storage image types in IR have to contain the access, but not in the SPIR-V. // The same image type in SPIR-V can be used (and has to be used) for multiple images. // So we copy the type out and apply the variable access decorations. let access = dec.flags.to_storage_access(); ty = module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Image { dim, arrayed, class: crate::ImageClass::Storage { format, access }, }, }, Default::default(), ); } let ext_class = match self.lookup_storage_buffer_types.get(&ty) { Some(&access) => ExtendedClass::Global(crate::AddressSpace::Storage { access }), None => map_storage_class(storage_class)?, }; let (inner, var) = match ext_class { ExtendedClass::Global(mut space) => { if let crate::AddressSpace::Storage { ref mut access } = space { *access &= dec.flags.to_storage_access(); } let var = crate::GlobalVariable { binding: dec.resource_binding(), name: dec.name, space, ty, init, memory_decorations: dec.flags.to_memory_decorations(), }; (Variable::Global, var) } ExtendedClass::Input => { let binding = dec.io_binding()?; let mut unsigned_ty = ty; if let crate::Binding::BuiltIn(built_in) = binding { let needs_inner_uint = match built_in { crate::BuiltIn::BaseInstance | crate::BuiltIn::BaseVertex | crate::BuiltIn::InstanceIndex | crate::BuiltIn::SampleIndex | crate::BuiltIn::VertexIndex | crate::BuiltIn::PrimitiveIndex | crate::BuiltIn::LocalInvocationIndex => { Some(crate::TypeInner::Scalar(crate::Scalar::U32)) } crate::BuiltIn::GlobalInvocationId | crate::BuiltIn::LocalInvocationId | crate::BuiltIn::WorkGroupId | crate::BuiltIn::WorkGroupSize => Some(crate::TypeInner::Vector { size: crate::VectorSize::Tri, scalar: crate::Scalar::U32, }), crate::BuiltIn::Barycentric { perspective: false } => { Some(crate::TypeInner::Vector { size: crate::VectorSize::Tri, scalar: crate::Scalar::F32, }) } _ => None, }; if let (Some(inner), Some(crate::ScalarKind::Sint)) = (needs_inner_uint, module.types[ty].inner.scalar_kind()) { unsigned_ty = module .types .insert(crate::Type { name: None, inner }, Default::default()); } } let var = crate::GlobalVariable { name: dec.name.clone(), space: crate::AddressSpace::Private, binding: None, ty, init: None, memory_decorations: crate::MemoryDecorations::empty(), }; let inner = Variable::Input(crate::FunctionArgument { name: dec.name, ty: unsigned_ty, binding: Some(binding), }); (inner, var) } ExtendedClass::Output => { // For output interface blocks, this would be a structure. let binding = dec.io_binding().ok(); let init = match binding { Some(crate::Binding::BuiltIn(built_in)) => { match null::generate_default_built_in( Some(built_in), ty, &mut module.global_expressions, span, ) { Ok(handle) => Some(handle), Err(e) => { log::warn!("Failed to initialize output built-in: {e}"); None } } } Some(crate::Binding::Location { .. }) => None, None => match module.types[ty].inner { crate::TypeInner::Struct { ref members, .. } => { let mut components = Vec::with_capacity(members.len()); for member in members.iter() { let built_in = match member.binding { Some(crate::Binding::BuiltIn(built_in)) => Some(built_in), _ => None, }; let handle = null::generate_default_built_in( built_in, member.ty, &mut module.global_expressions, span, )?; components.push(handle); } Some( module .global_expressions .append(crate::Expression::Compose { ty, components }, span), ) } _ => None, }, }; let var = crate::GlobalVariable { name: dec.name, space: crate::AddressSpace::Private, binding: None, ty, init, memory_decorations: crate::MemoryDecorations::empty(), }; let inner = Variable::Output(crate::FunctionResult { ty, binding }); (inner, var) } }; let handle = module.global_variables.append(var, span); if module.types[ty].inner.can_comparison_sample(module) { log::debug!("\t\ttracking {handle:?} for sampling properties"); self.handle_sampling .insert(handle, image::SamplingFlags::empty()); } self.lookup_variable.insert( id, LookupVariable { inner, handle, type_id, }, ); Ok(()) } /// Record an atomic access to some component of a global variable. /// /// Given `handle`, an expression referring to a scalar that has had an /// atomic operation applied to it, descend into the expression, noting /// which global variable it ultimately refers to, and which struct fields /// of that global's value it accesses. /// /// Return the handle of the type of the expression. /// /// If the expression doesn't actually refer to something in a global /// variable, we can't upgrade its type in a way that Naga validation would /// pass, so reject the input instead. fn record_atomic_access( &mut self, ctx: &BlockContext, handle: Handle, ) -> Result, Error> { log::debug!("\t\tlocating global variable in {handle:?}"); match ctx.expressions[handle] { crate::Expression::Access { base, index } => { log::debug!("\t\t access {handle:?} {index:?}"); let ty = self.record_atomic_access(ctx, base)?; let crate::TypeInner::Array { base, .. } = ctx.module.types[ty].inner else { unreachable!("Atomic operations on Access expressions only work for arrays"); }; Ok(base) } crate::Expression::AccessIndex { base, index } => { log::debug!("\t\t access index {handle:?} {index:?}"); let ty = self.record_atomic_access(ctx, base)?; match ctx.module.types[ty].inner { crate::TypeInner::Struct { ref members, .. } => { let index = index as usize; self.upgrade_atomics.insert_field(ty, index); Ok(members[index].ty) } crate::TypeInner::Array { base, .. } => { Ok(base) } _ => unreachable!("Atomic operations on AccessIndex expressions only work for structs and arrays"), } } crate::Expression::GlobalVariable(h) => { log::debug!("\t\t found {h:?}"); self.upgrade_atomics.insert_global(h); Ok(ctx.module.global_variables[h].ty) } _ => Err(Error::AtomicUpgradeError( crate::front::atomic_upgrade::Error::GlobalVariableMissing, )), } } } fn resolve_constant(gctx: crate::proc::GlobalCtx, constant: &Constant) -> Option { let constant = match *constant { Constant::Constant(constant) => constant, Constant::Override(_) => return None, }; match gctx.global_expressions[gctx.constants[constant].init] { crate::Expression::Literal(crate::Literal::U32(id)) => Some(id), crate::Expression::Literal(crate::Literal::I32(id)) => Some(id as u32), _ => None, } } pub fn parse_u8_slice(data: &[u8], options: &Options) -> Result { if !data.len().is_multiple_of(4) { return Err(Error::IncompleteData); } let words = data .chunks(4) .map(|c| u32::from_le_bytes(c.try_into().unwrap())); Frontend::new(words, options).parse() } /// Helper function to check if `child` is in the scope of `parent` fn is_parent(mut child: usize, parent: usize, block_ctx: &BlockContext) -> bool { loop { if child == parent { // The child is in the scope parent break true; } else if child == 0 { // Searched finished at the root the child isn't in the parent's body break false; } child = block_ctx.bodies[child].parent; } } #[cfg(test)] mod test { use alloc::vec; #[test] fn parse() { let bin = vec![ // Magic number. Version number: 1.0. 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, // Generator number: 0. Bound: 0. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Reserved word: 0. 0x00, 0x00, 0x00, 0x00, // OpMemoryModel. Logical. 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // GLSL450. 0x01, 0x00, 0x00, 0x00, ]; let _ = super::parse_u8_slice(&bin, &Default::default()).unwrap(); } } ================================================ FILE: naga/src/front/spv/next_block.rs ================================================ //! Implementation of [`Frontend::next_block()`]. //! //! This method is split out into its own module purely because it is so long. use alloc::{format, vec, vec::Vec}; use crate::front::spv::{ convert::{map_binary_operator, map_relational_fun}, image, resolve_constant, BlockContext, Body, BodyFragment, Constant, Error, Frontend, LookupExpression, LookupHelper as _, LookupLoadOverride, MergeBlockInformation, PhiExpression, SignAnchor, }; use crate::Handle; impl> Frontend { /// Add the next SPIR-V block's contents to `block_ctx`. /// /// Except for the function's entry block, `block_id` should be the label of /// a block we've seen mentioned before, with an entry in /// `block_ctx.body_for_label` to tell us which `Body` it contributes to. pub(in crate::front::spv) fn next_block( &mut self, block_id: spirv::Word, ctx: &mut BlockContext, ) -> Result<(), Error> { // Extend `body` with the correct form for a branch to `target`. fn merger(body: &mut Body, target: &MergeBlockInformation) { body.data.push(match *target { MergeBlockInformation::LoopContinue => BodyFragment::Continue, MergeBlockInformation::LoopMerge | MergeBlockInformation::SwitchMerge => { BodyFragment::Break } // Finishing a selection merge means just falling off the end of // the `accept` or `reject` block of the `If` statement. MergeBlockInformation::SelectionMerge => return, }) } let mut emitter = crate::proc::Emitter::default(); emitter.start(ctx.expressions); // Find the `Body` to which this block contributes. // // If this is some SPIR-V structured control flow construct's merge // block, then `body_idx` will refer to the same `Body` as the header, // so that we simply pick up accumulating the `Body` where the header // left off. Each of the statements in a block dominates the next, so // we're sure to encounter their SPIR-V blocks in order, ensuring that // the `Body` will be assembled in the proper order. // // Note that, unlike every other kind of SPIR-V block, we don't know the // function's first block's label in advance. Thus, we assume that if // this block has no entry in `ctx.body_for_label`, it must be the // function's first block. This always has body index zero. let mut body_idx = *ctx.body_for_label.entry(block_id).or_default(); // The Naga IR block this call builds. This will end up as // `ctx.blocks[&block_id]`, and `ctx.bodies[body_idx]` will refer to it // via a `BodyFragment::BlockId`. let mut block = crate::Block::new(); // Stores the merge block as defined by a `OpSelectionMerge` otherwise is `None` // // This is used in `OpSwitch` to promote the `MergeBlockInformation` from // `SelectionMerge` to `SwitchMerge` to allow `Break`s this isn't desirable for // `LoopMerge`s because otherwise `Continue`s wouldn't be allowed let mut selection_merge_block = None; macro_rules! get_expr_handle { ($id:expr, $lexp:expr) => { self.get_expr_handle($id, $lexp, ctx, &mut emitter, &mut block, body_idx) }; } macro_rules! parse_expr_op { ($op:expr, BINARY) => { self.parse_expr_binary_op(ctx, &mut emitter, &mut block, block_id, body_idx, $op) }; ($op:expr, SHIFT) => { self.parse_expr_shift_op(ctx, &mut emitter, &mut block, block_id, body_idx, $op) }; ($op:expr, UNARY) => { self.parse_expr_unary_op(ctx, &mut emitter, &mut block, block_id, body_idx, $op) }; ($axis:expr, $ctrl:expr, DERIVATIVE) => { self.parse_expr_derivative( ctx, &mut emitter, &mut block, block_id, body_idx, ($axis, $ctrl), ) }; } let terminator = loop { use spirv::Op; let start = self.data_offset; let inst = self.next_inst()?; let span = crate::Span::from(start..(start + 4 * (inst.wc as usize))); log::debug!("\t\t{:?} [{}]", inst.op, inst.wc); match inst.op { Op::Line => { inst.expect(4)?; let _file_id = self.next()?; let _row_id = self.next()?; let _col_id = self.next()?; } Op::NoLine => inst.expect(1)?, Op::Undef => { inst.expect(3)?; let type_id = self.next()?; let id = self.next()?; let type_lookup = self.lookup_type.lookup(type_id)?; let ty = type_lookup.handle; self.lookup_expression.insert( id, LookupExpression { handle: ctx .expressions .append(crate::Expression::ZeroValue(ty), span), type_id, block_id, }, ); } Op::Variable => { inst.expect_at_least(4)?; block.extend(emitter.finish(ctx.expressions)); let result_type_id = self.next()?; let result_id = self.next()?; let _storage_class = self.next()?; let init = if inst.wc > 4 { inst.expect(5)?; let init_id = self.next()?; let lconst = self.lookup_constant.lookup(init_id)?; Some(ctx.expressions.append(lconst.inner.to_expr(), span)) } else { None }; let name = self .future_decor .remove(&result_id) .and_then(|decor| decor.name); if let Some(ref name) = name { log::debug!("\t\t\tid={result_id} name={name}"); } let lookup_ty = self.lookup_type.lookup(result_type_id)?; let var_handle = ctx.local_arena.append( crate::LocalVariable { name, ty: match ctx.module.types[lookup_ty.handle].inner { crate::TypeInner::Pointer { base, .. } => base, _ => lookup_ty.handle, }, init, }, span, ); self.lookup_expression.insert( result_id, LookupExpression { handle: ctx .expressions .append(crate::Expression::LocalVariable(var_handle), span), type_id: result_type_id, block_id, }, ); emitter.start(ctx.expressions); } Op::Phi => { inst.expect_at_least(3)?; block.extend(emitter.finish(ctx.expressions)); let result_type_id = self.next()?; let result_id = self.next()?; let name = format!("phi_{result_id}"); let local = ctx.local_arena.append( crate::LocalVariable { name: Some(name), ty: self.lookup_type.lookup(result_type_id)?.handle, init: None, }, self.span_from(start), ); let pointer = ctx .expressions .append(crate::Expression::LocalVariable(local), span); let in_count = (inst.wc - 3) / 2; let mut phi = PhiExpression { local, expressions: Vec::with_capacity(in_count as usize), }; for _ in 0..in_count { let expr = self.next()?; let block = self.next()?; phi.expressions.push((expr, block)); } ctx.phis.push(phi); emitter.start(ctx.expressions); // Associate the lookup with an actual value, which is emitted // into the current block. self.lookup_expression.insert( result_id, LookupExpression { handle: ctx .expressions .append(crate::Expression::Load { pointer }, span), type_id: result_type_id, block_id, }, ); } Op::AccessChain | Op::InBoundsAccessChain => { struct AccessExpression { base_handle: Handle, type_id: spirv::Word, load_override: Option, } inst.expect_at_least(4)?; let result_type_id = self.next()?; let result_id = self.next()?; let base_id = self.next()?; log::trace!("\t\t\tlooking up expr {base_id:?}"); let mut acex = { let lexp = self.lookup_expression.lookup(base_id)?; let lty = self.lookup_type.lookup(lexp.type_id)?; // HACK `OpAccessChain` and `OpInBoundsAccessChain` // require for the result type to be a pointer, but if // we're given a pointer to an image / sampler, it will // be *already* dereferenced, since we do that early // during `parse_type_pointer()`. // // This can happen only through `BindingArray`, since // that's the only case where one can obtain a pointer // to an image / sampler, and so let's match on that: let dereference = match ctx.module.types[lty.handle].inner { crate::TypeInner::BindingArray { .. } => false, _ => true, }; let type_id = if dereference { lty.base_id.ok_or(Error::InvalidAccessType(lexp.type_id))? } else { lexp.type_id }; AccessExpression { base_handle: get_expr_handle!(base_id, lexp), type_id, load_override: self.lookup_load_override.get(&base_id).cloned(), } }; for _ in 4..inst.wc { let access_id = self.next()?; log::trace!("\t\t\tlooking up index expr {access_id:?}"); let index_expr = self.lookup_expression.lookup(access_id)?.clone(); let index_expr_handle = get_expr_handle!(access_id, &index_expr); let index_expr_data = &ctx.expressions[index_expr.handle]; let index_maybe = match *index_expr_data { crate::Expression::Constant(const_handle) => Some( ctx.gctx() .get_const_val(ctx.module.constants[const_handle].init) .map_err(|_| { Error::InvalidAccess(crate::Expression::Constant( const_handle, )) })?, ), _ => None, }; log::trace!("\t\t\tlooking up type {:?}", acex.type_id); let type_lookup = self.lookup_type.lookup(acex.type_id)?; let ty = &ctx.module.types[type_lookup.handle]; acex = match ty.inner { // can only index a struct with a constant crate::TypeInner::Struct { ref members, .. } => { let index = index_maybe .ok_or_else(|| Error::InvalidAccess(index_expr_data.clone()))?; let lookup_member = self .lookup_member .get(&(type_lookup.handle, index)) .ok_or(Error::InvalidAccessType(acex.type_id))?; let base_handle = ctx.expressions.append( crate::Expression::AccessIndex { base: acex.base_handle, index, }, span, ); if let Some(crate::Binding::BuiltIn(built_in)) = members[index as usize].binding { self.gl_per_vertex_builtin_access.insert(built_in); } AccessExpression { base_handle, type_id: lookup_member.type_id, load_override: if lookup_member.row_major { debug_assert!(acex.load_override.is_none()); let sub_type_lookup = self.lookup_type.lookup(lookup_member.type_id)?; Some(match ctx.module.types[sub_type_lookup.handle].inner { // load it transposed, to match column major expectations crate::TypeInner::Matrix { .. } => { let loaded = ctx.expressions.append( crate::Expression::Load { pointer: base_handle, }, span, ); let transposed = ctx.expressions.append( crate::Expression::Math { fun: crate::MathFunction::Transpose, arg: loaded, arg1: None, arg2: None, arg3: None, }, span, ); LookupLoadOverride::Loaded(transposed) } _ => LookupLoadOverride::Pending, }) } else { None }, } } crate::TypeInner::Matrix { .. } => { let load_override = match acex.load_override { // We are indexing inside a row-major matrix Some(LookupLoadOverride::Loaded(load_expr)) => { let index = index_maybe.ok_or_else(|| { Error::InvalidAccess(index_expr_data.clone()) })?; let sub_handle = ctx.expressions.append( crate::Expression::AccessIndex { base: load_expr, index, }, span, ); Some(LookupLoadOverride::Loaded(sub_handle)) } _ => None, }; let sub_expr = match index_maybe { Some(index) => crate::Expression::AccessIndex { base: acex.base_handle, index, }, None => crate::Expression::Access { base: acex.base_handle, index: index_expr_handle, }, }; AccessExpression { base_handle: ctx.expressions.append(sub_expr, span), type_id: type_lookup .base_id .ok_or(Error::InvalidAccessType(acex.type_id))?, load_override, } } // This must be a vector or an array. _ => { let base_handle = ctx.expressions.append( crate::Expression::Access { base: acex.base_handle, index: index_expr_handle, }, span, ); let load_override = match acex.load_override { // If there is a load override in place, then we always end up // with a side-loaded value here. Some(lookup_load_override) => { let sub_expr = match lookup_load_override { // We must be indexing into the array of row-major matrices. // Let's load the result of indexing and transpose it. LookupLoadOverride::Pending => { let loaded = ctx.expressions.append( crate::Expression::Load { pointer: base_handle, }, span, ); ctx.expressions.append( crate::Expression::Math { fun: crate::MathFunction::Transpose, arg: loaded, arg1: None, arg2: None, arg3: None, }, span, ) } // We are indexing inside a row-major matrix. LookupLoadOverride::Loaded(load_expr) => { ctx.expressions.append( crate::Expression::Access { base: load_expr, index: index_expr_handle, }, span, ) } }; Some(LookupLoadOverride::Loaded(sub_expr)) } None => None, }; AccessExpression { base_handle, type_id: type_lookup .base_id .ok_or(Error::InvalidAccessType(acex.type_id))?, load_override, } } }; } if let Some(load_expr) = acex.load_override { self.lookup_load_override.insert(result_id, load_expr); } let lookup_expression = LookupExpression { handle: acex.base_handle, type_id: result_type_id, block_id, }; self.lookup_expression.insert(result_id, lookup_expression); } Op::VectorExtractDynamic => { inst.expect(5)?; let result_type_id = self.next()?; let id = self.next()?; let composite_id = self.next()?; let index_id = self.next()?; let root_lexp = self.lookup_expression.lookup(composite_id)?; let root_handle = get_expr_handle!(composite_id, root_lexp); let root_type_lookup = self.lookup_type.lookup(root_lexp.type_id)?; let index_lexp = self.lookup_expression.lookup(index_id)?; let index_handle = get_expr_handle!(index_id, index_lexp); let index_type = self.lookup_type.lookup(index_lexp.type_id)?.handle; let num_components = match ctx.module.types[root_type_lookup.handle].inner { crate::TypeInner::Vector { size, .. } => size as u32, _ => return Err(Error::InvalidVectorType(root_type_lookup.handle)), }; let mut make_index = |ctx: &mut BlockContext, index: u32| { make_index_literal( ctx, index, &mut block, &mut emitter, index_type, index_lexp.type_id, span, ) }; let index_expr = make_index(ctx, 0)?; let mut handle = ctx.expressions.append( crate::Expression::Access { base: root_handle, index: index_expr, }, span, ); for index in 1..num_components { let index_expr = make_index(ctx, index)?; let access_expr = ctx.expressions.append( crate::Expression::Access { base: root_handle, index: index_expr, }, span, ); let cond = ctx.expressions.append( crate::Expression::Binary { op: crate::BinaryOperator::Equal, left: index_expr, right: index_handle, }, span, ); handle = ctx.expressions.append( crate::Expression::Select { condition: cond, accept: access_expr, reject: handle, }, span, ); } self.lookup_expression.insert( id, LookupExpression { handle, type_id: result_type_id, block_id, }, ); } Op::VectorInsertDynamic => { inst.expect(6)?; let result_type_id = self.next()?; let id = self.next()?; let composite_id = self.next()?; let object_id = self.next()?; let index_id = self.next()?; let object_lexp = self.lookup_expression.lookup(object_id)?; let object_handle = get_expr_handle!(object_id, object_lexp); let root_lexp = self.lookup_expression.lookup(composite_id)?; let root_handle = get_expr_handle!(composite_id, root_lexp); let root_type_lookup = self.lookup_type.lookup(root_lexp.type_id)?; let index_lexp = self.lookup_expression.lookup(index_id)?; let index_handle = get_expr_handle!(index_id, index_lexp); let index_type = self.lookup_type.lookup(index_lexp.type_id)?.handle; let num_components = match ctx.module.types[root_type_lookup.handle].inner { crate::TypeInner::Vector { size, .. } => size as u32, _ => return Err(Error::InvalidVectorType(root_type_lookup.handle)), }; let mut components = Vec::with_capacity(num_components as usize); for index in 0..num_components { let index_expr = make_index_literal( ctx, index, &mut block, &mut emitter, index_type, index_lexp.type_id, span, )?; let access_expr = ctx.expressions.append( crate::Expression::Access { base: root_handle, index: index_expr, }, span, ); let cond = ctx.expressions.append( crate::Expression::Binary { op: crate::BinaryOperator::Equal, left: index_expr, right: index_handle, }, span, ); let handle = ctx.expressions.append( crate::Expression::Select { condition: cond, accept: object_handle, reject: access_expr, }, span, ); components.push(handle); } let handle = ctx.expressions.append( crate::Expression::Compose { ty: root_type_lookup.handle, components, }, span, ); self.lookup_expression.insert( id, LookupExpression { handle, type_id: result_type_id, block_id, }, ); } Op::CompositeExtract => { inst.expect_at_least(4)?; let result_type_id = self.next()?; let result_id = self.next()?; let base_id = self.next()?; log::trace!("\t\t\tlooking up expr {base_id:?}"); let mut lexp = self.lookup_expression.lookup(base_id)?.clone(); lexp.handle = get_expr_handle!(base_id, &lexp); for _ in 4..inst.wc { let index = self.next()?; log::trace!("\t\t\tlooking up type {:?}", lexp.type_id); let type_lookup = self.lookup_type.lookup(lexp.type_id)?; let type_id = match ctx.module.types[type_lookup.handle].inner { crate::TypeInner::Struct { .. } => { self.lookup_member .get(&(type_lookup.handle, index)) .ok_or(Error::InvalidAccessType(lexp.type_id))? .type_id } crate::TypeInner::Array { .. } | crate::TypeInner::Vector { .. } | crate::TypeInner::Matrix { .. } => type_lookup .base_id .ok_or(Error::InvalidAccessType(lexp.type_id))?, ref other => { log::warn!("composite type {other:?}"); return Err(Error::UnsupportedType(type_lookup.handle)); } }; lexp = LookupExpression { handle: ctx.expressions.append( crate::Expression::AccessIndex { base: lexp.handle, index, }, span, ), type_id, block_id, }; } self.lookup_expression.insert( result_id, LookupExpression { handle: lexp.handle, type_id: result_type_id, block_id, }, ); } Op::CompositeInsert => { inst.expect_at_least(5)?; let result_type_id = self.next()?; let id = self.next()?; let object_id = self.next()?; let composite_id = self.next()?; let mut selections = Vec::with_capacity(inst.wc as usize - 5); for _ in 5..inst.wc { selections.push(self.next()?); } let object_lexp = self.lookup_expression.lookup(object_id)?.clone(); let object_handle = get_expr_handle!(object_id, &object_lexp); let root_lexp = self.lookup_expression.lookup(composite_id)?.clone(); let root_handle = get_expr_handle!(composite_id, &root_lexp); let handle = self.insert_composite( root_handle, result_type_id, object_handle, &selections, &ctx.module.types, ctx.expressions, span, )?; self.lookup_expression.insert( id, LookupExpression { handle, type_id: result_type_id, block_id, }, ); } Op::CompositeConstruct => { inst.expect_at_least(3)?; let result_type_id = self.next()?; let id = self.next()?; let mut components = Vec::with_capacity(inst.wc as usize - 2); for _ in 3..inst.wc { let comp_id = self.next()?; log::trace!("\t\t\tlooking up expr {comp_id:?}"); let lexp = self.lookup_expression.lookup(comp_id)?; let handle = get_expr_handle!(comp_id, lexp); components.push(handle); } let ty = self.lookup_type.lookup(result_type_id)?.handle; let first = components[0]; let expr = match ctx.module.types[ty].inner { // this is an optimization to detect the splat crate::TypeInner::Vector { size, .. } if components.len() == size as usize && components[1..].iter().all(|&c| c == first) => { crate::Expression::Splat { size, value: first } } _ => crate::Expression::Compose { ty, components }, }; self.lookup_expression.insert( id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); } Op::Load => { inst.expect_at_least(4)?; let result_type_id = self.next()?; let result_id = self.next()?; let pointer_id = self.next()?; if inst.wc != 4 { inst.expect(5)?; let _memory_access = self.next()?; } let base_lexp = self.lookup_expression.lookup(pointer_id)?; let base_handle = get_expr_handle!(pointer_id, base_lexp); let type_lookup = self.lookup_type.lookup(base_lexp.type_id)?; let handle = match ctx.module.types[type_lookup.handle].inner { crate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } => { base_handle } _ => match self.lookup_load_override.get(&pointer_id) { Some(&LookupLoadOverride::Loaded(handle)) => handle, //Note: we aren't handling `LookupLoadOverride::Pending` properly here _ => ctx.expressions.append( crate::Expression::Load { pointer: base_handle, }, span, ), }, }; self.lookup_expression.insert( result_id, LookupExpression { handle, type_id: result_type_id, block_id, }, ); } Op::Store => { inst.expect_at_least(3)?; let pointer_id = self.next()?; let value_id = self.next()?; if inst.wc != 3 { inst.expect(4)?; let _memory_access = self.next()?; } let base_expr = self.lookup_expression.lookup(pointer_id)?; let base_handle = get_expr_handle!(pointer_id, base_expr); let value_expr = self.lookup_expression.lookup(value_id)?; let value_handle = get_expr_handle!(value_id, value_expr); block.extend(emitter.finish(ctx.expressions)); block.push( crate::Statement::Store { pointer: base_handle, value: value_handle, }, span, ); emitter.start(ctx.expressions); } // Arithmetic Instructions +, -, *, /, % Op::SNegate | Op::FNegate => { inst.expect(4)?; self.parse_expr_unary_op_sign_adjusted( ctx, &mut emitter, &mut block, block_id, body_idx, crate::UnaryOperator::Negate, )?; } Op::IAdd | Op::ISub | Op::IMul | Op::BitwiseOr | Op::BitwiseXor | Op::BitwiseAnd | Op::SDiv | Op::SRem => { inst.expect(5)?; let operator = map_binary_operator(inst.op)?; self.parse_expr_binary_op_sign_adjusted( ctx, &mut emitter, &mut block, block_id, body_idx, operator, SignAnchor::Result, )?; } Op::IEqual | Op::INotEqual => { inst.expect(5)?; let operator = map_binary_operator(inst.op)?; self.parse_expr_binary_op_sign_adjusted( ctx, &mut emitter, &mut block, block_id, body_idx, operator, SignAnchor::Operand, )?; } Op::FAdd => { inst.expect(5)?; parse_expr_op!(crate::BinaryOperator::Add, BINARY)?; } Op::FSub => { inst.expect(5)?; parse_expr_op!(crate::BinaryOperator::Subtract, BINARY)?; } Op::FMul => { inst.expect(5)?; parse_expr_op!(crate::BinaryOperator::Multiply, BINARY)?; } Op::UDiv | Op::FDiv => { inst.expect(5)?; parse_expr_op!(crate::BinaryOperator::Divide, BINARY)?; } Op::UMod | Op::FRem => { inst.expect(5)?; parse_expr_op!(crate::BinaryOperator::Modulo, BINARY)?; } Op::SMod => { inst.expect(5)?; // x - y * int(floor(float(x) / float(y))) let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let p1_id = self.next()?; let p2_id = self.next()?; let span = self.span_from_with_op(start); let p1_lexp = self.lookup_expression.lookup(p1_id)?; let left = self.get_expr_handle( p1_id, p1_lexp, ctx, &mut emitter, &mut block, body_idx, ); let p2_lexp = self.lookup_expression.lookup(p2_id)?; let right = self.get_expr_handle( p2_id, p2_lexp, ctx, &mut emitter, &mut block, body_idx, ); let result_ty = self.lookup_type.lookup(result_type_id)?; let inner = &ctx.module.types[result_ty.handle].inner; let kind = inner.scalar_kind().unwrap(); let size = inner.size(ctx.gctx()) as u8; let left_cast = ctx.expressions.append( crate::Expression::As { expr: left, kind: crate::ScalarKind::Float, convert: Some(size), }, span, ); let right_cast = ctx.expressions.append( crate::Expression::As { expr: right, kind: crate::ScalarKind::Float, convert: Some(size), }, span, ); let div = ctx.expressions.append( crate::Expression::Binary { op: crate::BinaryOperator::Divide, left: left_cast, right: right_cast, }, span, ); let floor = ctx.expressions.append( crate::Expression::Math { fun: crate::MathFunction::Floor, arg: div, arg1: None, arg2: None, arg3: None, }, span, ); let cast = ctx.expressions.append( crate::Expression::As { expr: floor, kind, convert: Some(size), }, span, ); let mult = ctx.expressions.append( crate::Expression::Binary { op: crate::BinaryOperator::Multiply, left: cast, right, }, span, ); let sub = ctx.expressions.append( crate::Expression::Binary { op: crate::BinaryOperator::Subtract, left, right: mult, }, span, ); self.lookup_expression.insert( result_id, LookupExpression { handle: sub, type_id: result_type_id, block_id, }, ); } Op::FMod => { inst.expect(5)?; // x - y * floor(x / y) let start = self.data_offset; let span = self.span_from_with_op(start); let result_type_id = self.next()?; let result_id = self.next()?; let p1_id = self.next()?; let p2_id = self.next()?; let p1_lexp = self.lookup_expression.lookup(p1_id)?; let left = self.get_expr_handle( p1_id, p1_lexp, ctx, &mut emitter, &mut block, body_idx, ); let p2_lexp = self.lookup_expression.lookup(p2_id)?; let right = self.get_expr_handle( p2_id, p2_lexp, ctx, &mut emitter, &mut block, body_idx, ); let div = ctx.expressions.append( crate::Expression::Binary { op: crate::BinaryOperator::Divide, left, right, }, span, ); let floor = ctx.expressions.append( crate::Expression::Math { fun: crate::MathFunction::Floor, arg: div, arg1: None, arg2: None, arg3: None, }, span, ); let mult = ctx.expressions.append( crate::Expression::Binary { op: crate::BinaryOperator::Multiply, left: floor, right, }, span, ); let sub = ctx.expressions.append( crate::Expression::Binary { op: crate::BinaryOperator::Subtract, left, right: mult, }, span, ); self.lookup_expression.insert( result_id, LookupExpression { handle: sub, type_id: result_type_id, block_id, }, ); } Op::VectorTimesScalar | Op::VectorTimesMatrix | Op::MatrixTimesScalar | Op::MatrixTimesVector | Op::MatrixTimesMatrix => { inst.expect(5)?; parse_expr_op!(crate::BinaryOperator::Multiply, BINARY)?; } Op::Transpose => { inst.expect(4)?; let result_type_id = self.next()?; let result_id = self.next()?; let matrix_id = self.next()?; let matrix_lexp = self.lookup_expression.lookup(matrix_id)?; let matrix_handle = get_expr_handle!(matrix_id, matrix_lexp); let expr = crate::Expression::Math { fun: crate::MathFunction::Transpose, arg: matrix_handle, arg1: None, arg2: None, arg3: None, }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); } Op::Dot => { inst.expect(5)?; let result_type_id = self.next()?; let result_id = self.next()?; let left_id = self.next()?; let right_id = self.next()?; let left_lexp = self.lookup_expression.lookup(left_id)?; let left_handle = get_expr_handle!(left_id, left_lexp); let right_lexp = self.lookup_expression.lookup(right_id)?; let right_handle = get_expr_handle!(right_id, right_lexp); let expr = crate::Expression::Math { fun: crate::MathFunction::Dot, arg: left_handle, arg1: Some(right_handle), arg2: None, arg3: None, }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); } Op::BitFieldInsert => { inst.expect(7)?; let start = self.data_offset; let span = self.span_from_with_op(start); let result_type_id = self.next()?; let result_id = self.next()?; let base_id = self.next()?; let insert_id = self.next()?; let offset_id = self.next()?; let count_id = self.next()?; let base_lexp = self.lookup_expression.lookup(base_id)?; let base_handle = get_expr_handle!(base_id, base_lexp); let insert_lexp = self.lookup_expression.lookup(insert_id)?; let insert_handle = get_expr_handle!(insert_id, insert_lexp); let offset_lexp = self.lookup_expression.lookup(offset_id)?; let offset_handle = get_expr_handle!(offset_id, offset_lexp); let offset_lookup_ty = self.lookup_type.lookup(offset_lexp.type_id)?; let count_lexp = self.lookup_expression.lookup(count_id)?; let count_handle = get_expr_handle!(count_id, count_lexp); let count_lookup_ty = self.lookup_type.lookup(count_lexp.type_id)?; let offset_kind = ctx.module.types[offset_lookup_ty.handle] .inner .scalar_kind() .unwrap(); let count_kind = ctx.module.types[count_lookup_ty.handle] .inner .scalar_kind() .unwrap(); let offset_cast_handle = if offset_kind != crate::ScalarKind::Uint { ctx.expressions.append( crate::Expression::As { expr: offset_handle, kind: crate::ScalarKind::Uint, convert: None, }, span, ) } else { offset_handle }; let count_cast_handle = if count_kind != crate::ScalarKind::Uint { ctx.expressions.append( crate::Expression::As { expr: count_handle, kind: crate::ScalarKind::Uint, convert: None, }, span, ) } else { count_handle }; let expr = crate::Expression::Math { fun: crate::MathFunction::InsertBits, arg: base_handle, arg1: Some(insert_handle), arg2: Some(offset_cast_handle), arg3: Some(count_cast_handle), }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); } Op::BitFieldSExtract | Op::BitFieldUExtract => { inst.expect(6)?; let result_type_id = self.next()?; let result_id = self.next()?; let base_id = self.next()?; let offset_id = self.next()?; let count_id = self.next()?; let base_lexp = self.lookup_expression.lookup(base_id)?; let base_handle = get_expr_handle!(base_id, base_lexp); let offset_lexp = self.lookup_expression.lookup(offset_id)?; let offset_handle = get_expr_handle!(offset_id, offset_lexp); let offset_lookup_ty = self.lookup_type.lookup(offset_lexp.type_id)?; let count_lexp = self.lookup_expression.lookup(count_id)?; let count_handle = get_expr_handle!(count_id, count_lexp); let count_lookup_ty = self.lookup_type.lookup(count_lexp.type_id)?; let offset_kind = ctx.module.types[offset_lookup_ty.handle] .inner .scalar_kind() .unwrap(); let count_kind = ctx.module.types[count_lookup_ty.handle] .inner .scalar_kind() .unwrap(); let offset_cast_handle = if offset_kind != crate::ScalarKind::Uint { ctx.expressions.append( crate::Expression::As { expr: offset_handle, kind: crate::ScalarKind::Uint, convert: None, }, span, ) } else { offset_handle }; let count_cast_handle = if count_kind != crate::ScalarKind::Uint { ctx.expressions.append( crate::Expression::As { expr: count_handle, kind: crate::ScalarKind::Uint, convert: None, }, span, ) } else { count_handle }; let expr = crate::Expression::Math { fun: crate::MathFunction::ExtractBits, arg: base_handle, arg1: Some(offset_cast_handle), arg2: Some(count_cast_handle), arg3: None, }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); } Op::BitReverse | Op::BitCount => { inst.expect(4)?; let result_type_id = self.next()?; let result_id = self.next()?; let base_id = self.next()?; let base_lexp = self.lookup_expression.lookup(base_id)?; let base_handle = get_expr_handle!(base_id, base_lexp); let expr = crate::Expression::Math { fun: match inst.op { Op::BitReverse => crate::MathFunction::ReverseBits, Op::BitCount => crate::MathFunction::CountOneBits, _ => unreachable!(), }, arg: base_handle, arg1: None, arg2: None, arg3: None, }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); } Op::OuterProduct => { inst.expect(5)?; let result_type_id = self.next()?; let result_id = self.next()?; let left_id = self.next()?; let right_id = self.next()?; let left_lexp = self.lookup_expression.lookup(left_id)?; let left_handle = get_expr_handle!(left_id, left_lexp); let right_lexp = self.lookup_expression.lookup(right_id)?; let right_handle = get_expr_handle!(right_id, right_lexp); let expr = crate::Expression::Math { fun: crate::MathFunction::Outer, arg: left_handle, arg1: Some(right_handle), arg2: None, arg3: None, }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); } // Bitwise instructions Op::Not => { inst.expect(4)?; self.parse_expr_unary_op_sign_adjusted( ctx, &mut emitter, &mut block, block_id, body_idx, crate::UnaryOperator::BitwiseNot, )?; } Op::ShiftRightLogical => { inst.expect(5)?; //TODO: convert input and result to unsigned parse_expr_op!(crate::BinaryOperator::ShiftRight, SHIFT)?; } Op::ShiftRightArithmetic => { inst.expect(5)?; //TODO: convert input and result to signed parse_expr_op!(crate::BinaryOperator::ShiftRight, SHIFT)?; } Op::ShiftLeftLogical => { inst.expect(5)?; parse_expr_op!(crate::BinaryOperator::ShiftLeft, SHIFT)?; } // Sampling Op::Image => { inst.expect(4)?; self.parse_image_uncouple(block_id)?; } Op::SampledImage => { inst.expect(5)?; self.parse_image_couple()?; } Op::ImageWrite => { let extra = inst.expect_at_least(4)?; let stmt = self.parse_image_write(extra, ctx, &mut emitter, &mut block, body_idx)?; block.extend(emitter.finish(ctx.expressions)); block.push(stmt, span); emitter.start(ctx.expressions); } Op::ImageFetch | Op::ImageRead => { let extra = inst.expect_at_least(5)?; self.parse_image_load( extra, ctx, &mut emitter, &mut block, block_id, body_idx, )?; } Op::ImageSampleImplicitLod | Op::ImageSampleExplicitLod => { let extra = inst.expect_at_least(5)?; let options = image::SamplingOptions { compare: false, project: false, gather: false, }; self.parse_image_sample( extra, options, ctx, &mut emitter, &mut block, block_id, body_idx, )?; } Op::ImageSampleProjImplicitLod | Op::ImageSampleProjExplicitLod => { let extra = inst.expect_at_least(5)?; let options = image::SamplingOptions { compare: false, project: true, gather: false, }; self.parse_image_sample( extra, options, ctx, &mut emitter, &mut block, block_id, body_idx, )?; } Op::ImageSampleDrefImplicitLod | Op::ImageSampleDrefExplicitLod => { let extra = inst.expect_at_least(6)?; let options = image::SamplingOptions { compare: true, project: false, gather: false, }; self.parse_image_sample( extra, options, ctx, &mut emitter, &mut block, block_id, body_idx, )?; } Op::ImageSampleProjDrefImplicitLod | Op::ImageSampleProjDrefExplicitLod => { let extra = inst.expect_at_least(6)?; let options = image::SamplingOptions { compare: true, project: true, gather: false, }; self.parse_image_sample( extra, options, ctx, &mut emitter, &mut block, block_id, body_idx, )?; } Op::ImageGather => { let extra = inst.expect_at_least(6)?; let options = image::SamplingOptions { compare: false, project: false, gather: true, }; self.parse_image_sample( extra, options, ctx, &mut emitter, &mut block, block_id, body_idx, )?; } Op::ImageDrefGather => { let extra = inst.expect_at_least(6)?; let options = image::SamplingOptions { compare: true, project: false, gather: true, }; self.parse_image_sample( extra, options, ctx, &mut emitter, &mut block, block_id, body_idx, )?; } Op::ImageQuerySize => { inst.expect(4)?; self.parse_image_query_size( false, ctx, &mut emitter, &mut block, block_id, body_idx, )?; } Op::ImageQuerySizeLod => { inst.expect(5)?; self.parse_image_query_size( true, ctx, &mut emitter, &mut block, block_id, body_idx, )?; } Op::ImageQueryLevels => { inst.expect(4)?; self.parse_image_query_other(crate::ImageQuery::NumLevels, ctx, block_id)?; } Op::ImageQuerySamples => { inst.expect(4)?; self.parse_image_query_other(crate::ImageQuery::NumSamples, ctx, block_id)?; } // other ops Op::Select => { inst.expect(6)?; let result_type_id = self.next()?; let result_id = self.next()?; let condition = self.next()?; let o1_id = self.next()?; let o2_id = self.next()?; let cond_lexp = self.lookup_expression.lookup(condition)?; let cond_handle = get_expr_handle!(condition, cond_lexp); let o1_lexp = self.lookup_expression.lookup(o1_id)?; let o1_handle = get_expr_handle!(o1_id, o1_lexp); let o2_lexp = self.lookup_expression.lookup(o2_id)?; let o2_handle = get_expr_handle!(o2_id, o2_lexp); let expr = crate::Expression::Select { condition: cond_handle, accept: o1_handle, reject: o2_handle, }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); } Op::VectorShuffle => { inst.expect_at_least(5)?; let result_type_id = self.next()?; let result_id = self.next()?; let v1_id = self.next()?; let v2_id = self.next()?; let v1_lexp = self.lookup_expression.lookup(v1_id)?; let v1_lty = self.lookup_type.lookup(v1_lexp.type_id)?; let v1_handle = get_expr_handle!(v1_id, v1_lexp); let n1 = match ctx.module.types[v1_lty.handle].inner { crate::TypeInner::Vector { size, .. } => size as u32, _ => return Err(Error::InvalidInnerType(v1_lexp.type_id)), }; let v2_lexp = self.lookup_expression.lookup(v2_id)?; let v2_lty = self.lookup_type.lookup(v2_lexp.type_id)?; let v2_handle = get_expr_handle!(v2_id, v2_lexp); let n2 = match ctx.module.types[v2_lty.handle].inner { crate::TypeInner::Vector { size, .. } => size as u32, _ => return Err(Error::InvalidInnerType(v2_lexp.type_id)), }; self.temp_bytes.clear(); let mut max_component = 0; for _ in 5..inst.wc as usize { let mut index = self.next()?; if index == u32::MAX { // treat Undefined as X index = 0; } max_component = max_component.max(index); self.temp_bytes.push(index as u8); } // Check for swizzle first. let expr = if max_component < n1 { use crate::SwizzleComponent as Sc; let size = match self.temp_bytes.len() { 2 => crate::VectorSize::Bi, 3 => crate::VectorSize::Tri, _ => crate::VectorSize::Quad, }; let mut pattern = [Sc::X; 4]; for (pat, index) in pattern.iter_mut().zip(self.temp_bytes.drain(..)) { *pat = match index { 0 => Sc::X, 1 => Sc::Y, 2 => Sc::Z, _ => Sc::W, }; } crate::Expression::Swizzle { size, vector: v1_handle, pattern, } } else { // Fall back to access + compose let mut components = Vec::with_capacity(self.temp_bytes.len()); for index in self.temp_bytes.drain(..).map(|i| i as u32) { let expr = if index < n1 { crate::Expression::AccessIndex { base: v1_handle, index, } } else if index < n1 + n2 { crate::Expression::AccessIndex { base: v2_handle, index: index - n1, } } else { return Err(Error::InvalidAccessIndex(index)); }; components.push(ctx.expressions.append(expr, span)); } crate::Expression::Compose { ty: self.lookup_type.lookup(result_type_id)?.handle, components, } }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); } Op::Bitcast | Op::ConvertSToF | Op::ConvertUToF | Op::ConvertFToU | Op::ConvertFToS | Op::FConvert | Op::UConvert | Op::SConvert => { inst.expect(4)?; let result_type_id = self.next()?; let result_id = self.next()?; let value_id = self.next()?; let value_lexp = self.lookup_expression.lookup(value_id)?; let ty_lookup = self.lookup_type.lookup(result_type_id)?; let scalar = match ctx.module.types[ty_lookup.handle].inner { crate::TypeInner::Scalar(scalar) | crate::TypeInner::Vector { scalar, .. } | crate::TypeInner::Matrix { scalar, .. } => scalar, _ => return Err(Error::InvalidAsType(ty_lookup.handle)), }; let expr = crate::Expression::As { expr: get_expr_handle!(value_id, value_lexp), kind: scalar.kind, convert: if scalar.kind == crate::ScalarKind::Bool { Some(crate::BOOL_WIDTH) } else if inst.op == Op::Bitcast { None } else { Some(scalar.width) }, }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); } Op::FunctionCall => { inst.expect_at_least(4)?; let result_type_id = self.next()?; let result_id = self.next()?; let func_id = self.next()?; let mut arguments = Vec::with_capacity(inst.wc as usize - 4); for _ in 0..arguments.capacity() { let arg_id = self.next()?; let lexp = self.lookup_expression.lookup(arg_id)?; arguments.push(get_expr_handle!(arg_id, lexp)); } block.extend(emitter.finish(ctx.expressions)); // We just need an unique handle here, nothing more. let function = self.add_call(ctx.function_id, func_id); let result = if self.lookup_void_type == Some(result_type_id) { None } else { let expr_handle = ctx .expressions .append(crate::Expression::CallResult(function), span); self.lookup_expression.insert( result_id, LookupExpression { handle: expr_handle, type_id: result_type_id, block_id, }, ); Some(expr_handle) }; block.push( crate::Statement::Call { function, arguments, result, }, span, ); emitter.start(ctx.expressions); } Op::ExtInst => { use crate::MathFunction as Mf; use spirv::GlslStd450Op as Glo; let base_wc = 5; inst.expect_at_least(base_wc)?; let result_type_id = self.next()?; let result_id = self.next()?; let set_id = self.next()?; if Some(set_id) == self.ext_non_semantic_id { for _ in 0..inst.wc - 4 { self.next()?; } continue; } else if Some(set_id) != self.ext_glsl_id { return Err(Error::UnsupportedExtInstSet(set_id)); } let inst_id = self.next()?; let gl_op = Glo::from_u32(inst_id).ok_or(Error::UnsupportedExtInst(inst_id))?; let fun = match gl_op { Glo::Round => Mf::Round, Glo::RoundEven => Mf::Round, Glo::Trunc => Mf::Trunc, Glo::FAbs | Glo::SAbs => Mf::Abs, Glo::FSign | Glo::SSign => Mf::Sign, Glo::Floor => Mf::Floor, Glo::Ceil => Mf::Ceil, Glo::Fract => Mf::Fract, Glo::Sin => Mf::Sin, Glo::Cos => Mf::Cos, Glo::Tan => Mf::Tan, Glo::Asin => Mf::Asin, Glo::Acos => Mf::Acos, Glo::Atan => Mf::Atan, Glo::Sinh => Mf::Sinh, Glo::Cosh => Mf::Cosh, Glo::Tanh => Mf::Tanh, Glo::Atan2 => Mf::Atan2, Glo::Asinh => Mf::Asinh, Glo::Acosh => Mf::Acosh, Glo::Atanh => Mf::Atanh, Glo::Radians => Mf::Radians, Glo::Degrees => Mf::Degrees, Glo::Pow => Mf::Pow, Glo::Exp => Mf::Exp, Glo::Log => Mf::Log, Glo::Exp2 => Mf::Exp2, Glo::Log2 => Mf::Log2, Glo::Sqrt => Mf::Sqrt, Glo::InverseSqrt => Mf::InverseSqrt, Glo::MatrixInverse => Mf::Inverse, Glo::Determinant => Mf::Determinant, Glo::ModfStruct => Mf::Modf, Glo::FMin | Glo::UMin | Glo::SMin | Glo::NMin => Mf::Min, Glo::FMax | Glo::UMax | Glo::SMax | Glo::NMax => Mf::Max, Glo::FClamp | Glo::UClamp | Glo::SClamp | Glo::NClamp => Mf::Clamp, Glo::FMix => Mf::Mix, Glo::Step => Mf::Step, Glo::SmoothStep => Mf::SmoothStep, Glo::Fma => Mf::Fma, Glo::FrexpStruct => Mf::Frexp, Glo::Ldexp => Mf::Ldexp, Glo::Length => Mf::Length, Glo::Distance => Mf::Distance, Glo::Cross => Mf::Cross, Glo::Normalize => Mf::Normalize, Glo::FaceForward => Mf::FaceForward, Glo::Reflect => Mf::Reflect, Glo::Refract => Mf::Refract, Glo::PackUnorm4x8 => Mf::Pack4x8unorm, Glo::PackSnorm4x8 => Mf::Pack4x8snorm, Glo::PackHalf2x16 => Mf::Pack2x16float, Glo::PackUnorm2x16 => Mf::Pack2x16unorm, Glo::PackSnorm2x16 => Mf::Pack2x16snorm, Glo::UnpackUnorm4x8 => Mf::Unpack4x8unorm, Glo::UnpackSnorm4x8 => Mf::Unpack4x8snorm, Glo::UnpackHalf2x16 => Mf::Unpack2x16float, Glo::UnpackUnorm2x16 => Mf::Unpack2x16unorm, Glo::UnpackSnorm2x16 => Mf::Unpack2x16snorm, Glo::FindILsb => Mf::FirstTrailingBit, Glo::FindUMsb | Glo::FindSMsb => Mf::FirstLeadingBit, // TODO: https://github.com/gfx-rs/naga/issues/2526 Glo::Modf | Glo::Frexp => return Err(Error::UnsupportedExtInst(inst_id)), Glo::IMix | Glo::PackDouble2x32 | Glo::UnpackDouble2x32 | Glo::InterpolateAtCentroid | Glo::InterpolateAtSample | Glo::InterpolateAtOffset => { return Err(Error::UnsupportedExtInst(inst_id)) } }; let arg_count = fun.argument_count(); inst.expect(base_wc + arg_count as u16)?; let arg = { let arg_id = self.next()?; let lexp = self.lookup_expression.lookup(arg_id)?; get_expr_handle!(arg_id, lexp) }; let arg1 = if arg_count > 1 { let arg_id = self.next()?; let lexp = self.lookup_expression.lookup(arg_id)?; Some(get_expr_handle!(arg_id, lexp)) } else { None }; let arg2 = if arg_count > 2 { let arg_id = self.next()?; let lexp = self.lookup_expression.lookup(arg_id)?; Some(get_expr_handle!(arg_id, lexp)) } else { None }; let arg3 = if arg_count > 3 { let arg_id = self.next()?; let lexp = self.lookup_expression.lookup(arg_id)?; Some(get_expr_handle!(arg_id, lexp)) } else { None }; let expr = crate::Expression::Math { fun, arg, arg1, arg2, arg3, }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); } // Relational and Logical Instructions Op::LogicalNot => { inst.expect(4)?; parse_expr_op!(crate::UnaryOperator::LogicalNot, UNARY)?; } Op::LogicalOr => { inst.expect(5)?; parse_expr_op!(crate::BinaryOperator::LogicalOr, BINARY)?; } Op::LogicalAnd => { inst.expect(5)?; parse_expr_op!(crate::BinaryOperator::LogicalAnd, BINARY)?; } Op::SGreaterThan | Op::SGreaterThanEqual | Op::SLessThan | Op::SLessThanEqual => { inst.expect(5)?; self.parse_expr_int_comparison( ctx, &mut emitter, &mut block, block_id, body_idx, map_binary_operator(inst.op)?, crate::ScalarKind::Sint, )?; } Op::UGreaterThan | Op::UGreaterThanEqual | Op::ULessThan | Op::ULessThanEqual => { inst.expect(5)?; self.parse_expr_int_comparison( ctx, &mut emitter, &mut block, block_id, body_idx, map_binary_operator(inst.op)?, crate::ScalarKind::Uint, )?; } Op::FOrdEqual | Op::FUnordEqual | Op::FOrdNotEqual | Op::FUnordNotEqual | Op::FOrdLessThan | Op::FUnordLessThan | Op::FOrdGreaterThan | Op::FUnordGreaterThan | Op::FOrdLessThanEqual | Op::FUnordLessThanEqual | Op::FOrdGreaterThanEqual | Op::FUnordGreaterThanEqual | Op::LogicalEqual | Op::LogicalNotEqual => { inst.expect(5)?; let operator = map_binary_operator(inst.op)?; parse_expr_op!(operator, BINARY)?; } Op::Any | Op::All | Op::IsNan | Op::IsInf | Op::IsFinite | Op::IsNormal => { inst.expect(4)?; let result_type_id = self.next()?; let result_id = self.next()?; let arg_id = self.next()?; let arg_lexp = self.lookup_expression.lookup(arg_id)?; let arg_handle = get_expr_handle!(arg_id, arg_lexp); let expr = crate::Expression::Relational { fun: map_relational_fun(inst.op)?, argument: arg_handle, }; self.lookup_expression.insert( result_id, LookupExpression { handle: ctx.expressions.append(expr, span), type_id: result_type_id, block_id, }, ); } Op::Kill => { inst.expect(1)?; break Some(crate::Statement::Kill); } Op::Unreachable => { inst.expect(1)?; break None; } Op::Return => { inst.expect(1)?; break Some(crate::Statement::Return { value: None }); } Op::ReturnValue => { inst.expect(2)?; let value_id = self.next()?; let value_lexp = self.lookup_expression.lookup(value_id)?; let value_handle = get_expr_handle!(value_id, value_lexp); break Some(crate::Statement::Return { value: Some(value_handle), }); } Op::Branch => { inst.expect(2)?; let target_id = self.next()?; // If this is a branch to a merge or continue block, then // that ends the current body. // // Why can we count on finding an entry here when it's // needed? SPIR-V requires dominators to appear before // blocks they dominate, so we will have visited a // structured control construct's header block before // anything that could exit it. if let Some(info) = ctx.mergers.get(&target_id) { block.extend(emitter.finish(ctx.expressions)); ctx.blocks.insert(block_id, block); let body = &mut ctx.bodies[body_idx]; body.data.push(BodyFragment::BlockId(block_id)); merger(body, info); return Ok(()); } // If `target_id` has no entry in `ctx.body_for_label`, then // this must be the only branch to it: // // - We've already established that it's not anybody's merge // block. // // - It can't be a switch case. Only switch header blocks // and other switch cases can branch to a switch case. // Switch header blocks must dominate all their cases, so // they must appear in the file before them, and when we // see `Op::Switch` we populate `ctx.body_for_label` for // every switch case. // // Thus, `target_id` must be a simple extension of the // current block, which we dominate, so we know we'll // encounter it later in the file. ctx.body_for_label.entry(target_id).or_insert(body_idx); break None; } Op::BranchConditional => { inst.expect_at_least(4)?; let condition = { let condition_id = self.next()?; let lexp = self.lookup_expression.lookup(condition_id)?; get_expr_handle!(condition_id, lexp) }; // HACK(eddyb) Naga doesn't seem to have this helper, // so it's declared on the fly here for convenience. #[derive(Copy, Clone)] struct BranchTarget { label_id: spirv::Word, merge_info: Option, } let branch_target = |label_id| BranchTarget { label_id, merge_info: ctx.mergers.get(&label_id).copied(), }; let true_target = branch_target(self.next()?); let false_target = branch_target(self.next()?); // Consume branch weights for _ in 4..inst.wc { let _ = self.next()?; } // Handle `OpBranchConditional`s used at the end of a loop // body's "continuing" section as a "conditional backedge", // i.e. a `do`-`while` condition, or `break if` in WGSL. // HACK(eddyb) this has to go to the parent *twice*, because // `OpLoopMerge` left the "continuing" section nested in the // loop body in terms of `parent`, but not `BodyFragment`. let parent_body_idx = ctx.bodies[body_idx].parent; let parent_parent_body_idx = ctx.bodies[parent_body_idx].parent; match ctx.bodies[parent_parent_body_idx].data[..] { // The `OpLoopMerge`'s `continuing` block and the loop's // backedge block may not be the same, but they'll both // belong to the same body. [.., BodyFragment::Loop { body: loop_body_idx, continuing: loop_continuing_idx, break_if: ref mut break_if_slot @ None, }] if body_idx == loop_continuing_idx => { // Try both orderings of break-vs-backedge, because // SPIR-V is symmetrical here, unlike WGSL `break if`. let break_if_cond = [true, false].into_iter().find_map(|true_breaks| { let (break_candidate, backedge_candidate) = if true_breaks { (true_target, false_target) } else { (false_target, true_target) }; if break_candidate.merge_info != Some(MergeBlockInformation::LoopMerge) { return None; } // HACK(eddyb) since Naga doesn't explicitly track // backedges, this is checking for the outcome of // `OpLoopMerge` below (even if it looks weird). let backedge_candidate_is_backedge = backedge_candidate.merge_info.is_none() && ctx.body_for_label.get(&backedge_candidate.label_id) == Some(&loop_body_idx); if !backedge_candidate_is_backedge { return None; } Some(if true_breaks { condition } else { ctx.expressions.append( crate::Expression::Unary { op: crate::UnaryOperator::LogicalNot, expr: condition, }, span, ) }) }); if let Some(break_if_cond) = break_if_cond { *break_if_slot = Some(break_if_cond); // This `OpBranchConditional` ends the "continuing" // section of the loop body as normal, with the // `break if` condition having been stashed above. break None; } } _ => {} } block.extend(emitter.finish(ctx.expressions)); ctx.blocks.insert(block_id, block); let body = &mut ctx.bodies[body_idx]; body.data.push(BodyFragment::BlockId(block_id)); let same_target = true_target.label_id == false_target.label_id; // Start a body block for the `accept` branch. let accept = ctx.bodies.len(); let mut accept_block = Body::with_parent(body_idx); // If the `OpBranchConditional` target is somebody else's // merge or continue block, then put a `Break` or `Continue` // statement in this new body block. if let Some(info) = true_target.merge_info { merger( match same_target { true => &mut ctx.bodies[body_idx], false => &mut accept_block, }, &info, ) } else { // Note the body index for the block we're branching to. let prev = ctx.body_for_label.insert( true_target.label_id, match same_target { true => body_idx, false => accept, }, ); debug_assert!(prev.is_none()); } if same_target { return Ok(()); } ctx.bodies.push(accept_block); // Handle the `reject` branch just like the `accept` block. let reject = ctx.bodies.len(); let mut reject_block = Body::with_parent(body_idx); if let Some(info) = false_target.merge_info { merger(&mut reject_block, &info) } else { let prev = ctx.body_for_label.insert(false_target.label_id, reject); debug_assert!(prev.is_none()); } ctx.bodies.push(reject_block); let body = &mut ctx.bodies[body_idx]; body.data.push(BodyFragment::If { condition, accept, reject, }); return Ok(()); } Op::Switch => { inst.expect_at_least(3)?; let selector = self.next()?; let default_id = self.next()?; // If the previous instruction was a `OpSelectionMerge` then we must // promote the `MergeBlockInformation` to a `SwitchMerge` if let Some(merge) = selection_merge_block { ctx.mergers .insert(merge, MergeBlockInformation::SwitchMerge); } let default = ctx.bodies.len(); ctx.bodies.push(Body::with_parent(body_idx)); ctx.body_for_label.entry(default_id).or_insert(default); let selector_lexp = &self.lookup_expression[&selector]; let selector_lty = self.lookup_type.lookup(selector_lexp.type_id)?; let selector_handle = get_expr_handle!(selector, selector_lexp); let selector = match ctx.module.types[selector_lty.handle].inner { crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Uint, width: _, }) => { // IR expects a signed integer, so do a bitcast ctx.expressions.append( crate::Expression::As { kind: crate::ScalarKind::Sint, expr: selector_handle, convert: None, }, span, ) } crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Sint, width: _, }) => selector_handle, ref other => unimplemented!("Unexpected selector {:?}", other), }; // Clear past switch cases to prevent them from entering this one self.switch_cases.clear(); for _ in 0..(inst.wc - 3) / 2 { let literal = self.next()?; let target = self.next()?; let case_body_idx = ctx.bodies.len(); // Check if any previous case already used this target block id, if so // group them together to reorder them later so that no weird // fallthrough cases happen. if let Some(&mut (_, ref mut literals)) = self.switch_cases.get_mut(&target) { literals.push(literal as i32); continue; } let mut body = Body::with_parent(body_idx); if let Some(info) = ctx.mergers.get(&target) { merger(&mut body, info); } ctx.bodies.push(body); ctx.body_for_label.entry(target).or_insert(case_body_idx); // Register this target block id as already having been processed and // the respective body index assigned and the first case value self.switch_cases .insert(target, (case_body_idx, vec![literal as i32])); } // Loop through the collected target blocks creating a new case for each // literal pointing to it, only one case will have the true body and all the // others will be empty fallthrough so that they all execute the same body // without duplicating code. // // Since `switch_cases` is an indexmap the order of insertion is preserved // this is needed because spir-v defines fallthrough order in the switch // instruction. let mut cases = Vec::with_capacity((inst.wc as usize - 3) / 2); for &(case_body_idx, ref literals) in self.switch_cases.values() { let value = literals[0]; for &literal in literals.iter().skip(1) { let empty_body_idx = ctx.bodies.len(); let body = Body::with_parent(body_idx); ctx.bodies.push(body); cases.push((literal, empty_body_idx)); } cases.push((value, case_body_idx)); } block.extend(emitter.finish(ctx.expressions)); let body = &mut ctx.bodies[body_idx]; ctx.blocks.insert(block_id, block); // Make sure the vector has space for at least two more allocations body.data.reserve(2); body.data.push(BodyFragment::BlockId(block_id)); body.data.push(BodyFragment::Switch { selector, cases, default, }); return Ok(()); } Op::SelectionMerge => { inst.expect(3)?; let merge_block_id = self.next()?; // TODO: Selection Control Mask let _selection_control = self.next()?; // Indicate that the merge block is a continuation of the // current `Body`. ctx.body_for_label.entry(merge_block_id).or_insert(body_idx); // Let subsequent branches to the merge block know that // they've reached the end of the selection construct. ctx.mergers .insert(merge_block_id, MergeBlockInformation::SelectionMerge); selection_merge_block = Some(merge_block_id); } Op::LoopMerge => { inst.expect_at_least(4)?; let merge_block_id = self.next()?; let continuing = self.next()?; // TODO: Loop Control Parameters for _ in 0..inst.wc - 3 { self.next()?; } // Indicate that the merge block is a continuation of the // current `Body`. ctx.body_for_label.entry(merge_block_id).or_insert(body_idx); // Let subsequent branches to the merge block know that // they're `Break` statements. ctx.mergers .insert(merge_block_id, MergeBlockInformation::LoopMerge); let loop_body_idx = ctx.bodies.len(); ctx.bodies.push(Body::with_parent(body_idx)); let continue_idx = ctx.bodies.len(); // The continue block inherits the scope of the loop body ctx.bodies.push(Body::with_parent(loop_body_idx)); ctx.body_for_label.entry(continuing).or_insert(continue_idx); // Let subsequent branches to the continue block know that // they're `Continue` statements. ctx.mergers .insert(continuing, MergeBlockInformation::LoopContinue); // The loop header always belongs to the loop body ctx.body_for_label.insert(block_id, loop_body_idx); let parent_body = &mut ctx.bodies[body_idx]; parent_body.data.push(BodyFragment::Loop { body: loop_body_idx, continuing: continue_idx, break_if: None, }); body_idx = loop_body_idx; } Op::DPdxCoarse => { parse_expr_op!( crate::DerivativeAxis::X, crate::DerivativeControl::Coarse, DERIVATIVE )?; } Op::DPdyCoarse => { parse_expr_op!( crate::DerivativeAxis::Y, crate::DerivativeControl::Coarse, DERIVATIVE )?; } Op::FwidthCoarse => { parse_expr_op!( crate::DerivativeAxis::Width, crate::DerivativeControl::Coarse, DERIVATIVE )?; } Op::DPdxFine => { parse_expr_op!( crate::DerivativeAxis::X, crate::DerivativeControl::Fine, DERIVATIVE )?; } Op::DPdyFine => { parse_expr_op!( crate::DerivativeAxis::Y, crate::DerivativeControl::Fine, DERIVATIVE )?; } Op::FwidthFine => { parse_expr_op!( crate::DerivativeAxis::Width, crate::DerivativeControl::Fine, DERIVATIVE )?; } Op::DPdx => { parse_expr_op!( crate::DerivativeAxis::X, crate::DerivativeControl::None, DERIVATIVE )?; } Op::DPdy => { parse_expr_op!( crate::DerivativeAxis::Y, crate::DerivativeControl::None, DERIVATIVE )?; } Op::Fwidth => { parse_expr_op!( crate::DerivativeAxis::Width, crate::DerivativeControl::None, DERIVATIVE )?; } Op::ArrayLength => { inst.expect(5)?; let result_type_id = self.next()?; let result_id = self.next()?; let structure_id = self.next()?; let member_index = self.next()?; // We're assuming that the validation pass, if it's run, will catch if the // wrong types or parameters are supplied here. let structure_ptr = self.lookup_expression.lookup(structure_id)?; let structure_handle = get_expr_handle!(structure_id, structure_ptr); let member_ptr = ctx.expressions.append( crate::Expression::AccessIndex { base: structure_handle, index: member_index, }, span, ); let length = ctx .expressions .append(crate::Expression::ArrayLength(member_ptr), span); self.lookup_expression.insert( result_id, LookupExpression { handle: length, type_id: result_type_id, block_id, }, ); } Op::CopyMemory => { inst.expect_at_least(3)?; let target_id = self.next()?; let source_id = self.next()?; let _memory_access = if inst.wc != 3 { inst.expect(4)?; spirv::MemoryAccess::from_bits(self.next()?) .ok_or(Error::InvalidParameter(Op::CopyMemory))? } else { spirv::MemoryAccess::NONE }; // TODO: check if the source and target types are the same? let target = self.lookup_expression.lookup(target_id)?; let target_handle = get_expr_handle!(target_id, target); let source = self.lookup_expression.lookup(source_id)?; let source_handle = get_expr_handle!(source_id, source); // This operation is practically the same as loading and then storing, I think. let value_expr = ctx.expressions.append( crate::Expression::Load { pointer: source_handle, }, span, ); block.extend(emitter.finish(ctx.expressions)); block.push( crate::Statement::Store { pointer: target_handle, value: value_expr, }, span, ); emitter.start(ctx.expressions); } Op::ControlBarrier => { inst.expect(4)?; let exec_scope_id = self.next()?; let _mem_scope_raw = self.next()?; let semantics_id = self.next()?; let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; let semantics_const = self.lookup_constant.lookup(semantics_id)?; let exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; let semantics = resolve_constant(ctx.gctx(), &semantics_const.inner) .ok_or(Error::InvalidBarrierMemorySemantics(semantics_id))?; if exec_scope == spirv::Scope::Workgroup as u32 || exec_scope == spirv::Scope::Subgroup as u32 { let mut flags = crate::Barrier::empty(); flags.set( crate::Barrier::STORAGE, semantics & spirv::MemorySemantics::UNIFORM_MEMORY.bits() != 0, ); flags.set( crate::Barrier::WORK_GROUP, semantics & (spirv::MemorySemantics::WORKGROUP_MEMORY).bits() != 0, ); flags.set( crate::Barrier::SUB_GROUP, semantics & spirv::MemorySemantics::SUBGROUP_MEMORY.bits() != 0, ); flags.set( crate::Barrier::TEXTURE, semantics & spirv::MemorySemantics::IMAGE_MEMORY.bits() != 0, ); block.extend(emitter.finish(ctx.expressions)); block.push(crate::Statement::ControlBarrier(flags), span); emitter.start(ctx.expressions); } else { log::warn!("Unsupported barrier execution scope: {exec_scope}"); } } Op::MemoryBarrier => { inst.expect(3)?; let mem_scope_id = self.next()?; let semantics_id = self.next()?; let mem_scope_const = self.lookup_constant.lookup(mem_scope_id)?; let semantics_const = self.lookup_constant.lookup(semantics_id)?; let mem_scope = resolve_constant(ctx.gctx(), &mem_scope_const.inner) .ok_or(Error::InvalidBarrierScope(mem_scope_id))?; let semantics = resolve_constant(ctx.gctx(), &semantics_const.inner) .ok_or(Error::InvalidBarrierMemorySemantics(semantics_id))?; let mut flags = if mem_scope == spirv::Scope::Device as u32 { crate::Barrier::STORAGE } else if mem_scope == spirv::Scope::Workgroup as u32 { crate::Barrier::WORK_GROUP } else if mem_scope == spirv::Scope::Subgroup as u32 { crate::Barrier::SUB_GROUP } else { crate::Barrier::empty() }; flags.set( crate::Barrier::STORAGE, semantics & spirv::MemorySemantics::UNIFORM_MEMORY.bits() != 0, ); flags.set( crate::Barrier::WORK_GROUP, semantics & (spirv::MemorySemantics::WORKGROUP_MEMORY).bits() != 0, ); flags.set( crate::Barrier::SUB_GROUP, semantics & spirv::MemorySemantics::SUBGROUP_MEMORY.bits() != 0, ); flags.set( crate::Barrier::TEXTURE, semantics & spirv::MemorySemantics::IMAGE_MEMORY.bits() != 0, ); block.extend(emitter.finish(ctx.expressions)); block.push(crate::Statement::MemoryBarrier(flags), span); emitter.start(ctx.expressions); } Op::CopyObject => { inst.expect(4)?; let result_type_id = self.next()?; let result_id = self.next()?; let operand_id = self.next()?; let lookup = self.lookup_expression.lookup(operand_id)?; let handle = get_expr_handle!(operand_id, lookup); self.lookup_expression.insert( result_id, LookupExpression { handle, type_id: result_type_id, block_id, }, ); } Op::GroupNonUniformBallot => { inst.expect(5)?; block.extend(emitter.finish(ctx.expressions)); let result_type_id = self.next()?; let result_id = self.next()?; let exec_scope_id = self.next()?; let predicate_id = self.next()?; let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; let _exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) .filter(|exec_scope| *exec_scope == spirv::Scope::Subgroup as u32) .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; let predicate = if self .lookup_constant .lookup(predicate_id) .ok() .filter(|predicate_const| match predicate_const.inner { Constant::Constant(constant) => matches!( ctx.gctx().global_expressions[ctx.gctx().constants[constant].init], crate::Expression::Literal(crate::Literal::Bool(true)), ), Constant::Override(_) => false, }) .is_some() { None } else { let predicate_lookup = self.lookup_expression.lookup(predicate_id)?; let predicate_handle = get_expr_handle!(predicate_id, predicate_lookup); Some(predicate_handle) }; let result_handle = ctx .expressions .append(crate::Expression::SubgroupBallotResult, span); self.lookup_expression.insert( result_id, LookupExpression { handle: result_handle, type_id: result_type_id, block_id, }, ); block.push( crate::Statement::SubgroupBallot { result: result_handle, predicate, }, span, ); emitter.start(ctx.expressions); } Op::GroupNonUniformAll | Op::GroupNonUniformAny | Op::GroupNonUniformIAdd | Op::GroupNonUniformFAdd | Op::GroupNonUniformIMul | Op::GroupNonUniformFMul | Op::GroupNonUniformSMax | Op::GroupNonUniformUMax | Op::GroupNonUniformFMax | Op::GroupNonUniformSMin | Op::GroupNonUniformUMin | Op::GroupNonUniformFMin | Op::GroupNonUniformBitwiseAnd | Op::GroupNonUniformBitwiseOr | Op::GroupNonUniformBitwiseXor | Op::GroupNonUniformLogicalAnd | Op::GroupNonUniformLogicalOr | Op::GroupNonUniformLogicalXor => { block.extend(emitter.finish(ctx.expressions)); inst.expect( if matches!(inst.op, Op::GroupNonUniformAll | Op::GroupNonUniformAny) { 5 } else { 6 }, )?; let result_type_id = self.next()?; let result_id = self.next()?; let exec_scope_id = self.next()?; let collective_op_id = match inst.op { Op::GroupNonUniformAll | Op::GroupNonUniformAny => { crate::CollectiveOperation::Reduce } _ => { let group_op_id = self.next()?; match spirv::GroupOperation::from_u32(group_op_id) { Some(spirv::GroupOperation::Reduce) => { crate::CollectiveOperation::Reduce } Some(spirv::GroupOperation::InclusiveScan) => { crate::CollectiveOperation::InclusiveScan } Some(spirv::GroupOperation::ExclusiveScan) => { crate::CollectiveOperation::ExclusiveScan } _ => return Err(Error::UnsupportedGroupOperation(group_op_id)), } } }; let argument_id = self.next()?; let argument_lookup = self.lookup_expression.lookup(argument_id)?; let argument_handle = get_expr_handle!(argument_id, argument_lookup); let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; let _exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) .filter(|exec_scope| *exec_scope == spirv::Scope::Subgroup as u32) .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; let op_id = match inst.op { Op::GroupNonUniformAll => crate::SubgroupOperation::All, Op::GroupNonUniformAny => crate::SubgroupOperation::Any, Op::GroupNonUniformIAdd | Op::GroupNonUniformFAdd => { crate::SubgroupOperation::Add } Op::GroupNonUniformIMul | Op::GroupNonUniformFMul => { crate::SubgroupOperation::Mul } Op::GroupNonUniformSMax | Op::GroupNonUniformUMax | Op::GroupNonUniformFMax => crate::SubgroupOperation::Max, Op::GroupNonUniformSMin | Op::GroupNonUniformUMin | Op::GroupNonUniformFMin => crate::SubgroupOperation::Min, Op::GroupNonUniformBitwiseAnd | Op::GroupNonUniformLogicalAnd => { crate::SubgroupOperation::And } Op::GroupNonUniformBitwiseOr | Op::GroupNonUniformLogicalOr => { crate::SubgroupOperation::Or } Op::GroupNonUniformBitwiseXor | Op::GroupNonUniformLogicalXor => { crate::SubgroupOperation::Xor } _ => unreachable!(), }; let result_type = self.lookup_type.lookup(result_type_id)?; let result_handle = ctx.expressions.append( crate::Expression::SubgroupOperationResult { ty: result_type.handle, }, span, ); self.lookup_expression.insert( result_id, LookupExpression { handle: result_handle, type_id: result_type_id, block_id, }, ); block.push( crate::Statement::SubgroupCollectiveOperation { result: result_handle, op: op_id, collective_op: collective_op_id, argument: argument_handle, }, span, ); emitter.start(ctx.expressions); } Op::GroupNonUniformBroadcastFirst | Op::GroupNonUniformBroadcast | Op::GroupNonUniformShuffle | Op::GroupNonUniformShuffleDown | Op::GroupNonUniformShuffleUp | Op::GroupNonUniformShuffleXor | Op::GroupNonUniformQuadBroadcast => { inst.expect(if matches!(inst.op, Op::GroupNonUniformBroadcastFirst) { 5 } else { 6 })?; block.extend(emitter.finish(ctx.expressions)); let result_type_id = self.next()?; let result_id = self.next()?; let exec_scope_id = self.next()?; let argument_id = self.next()?; let argument_lookup = self.lookup_expression.lookup(argument_id)?; let argument_handle = get_expr_handle!(argument_id, argument_lookup); let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; let _exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) .filter(|exec_scope| *exec_scope == spirv::Scope::Subgroup as u32) .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; let mode = if matches!(inst.op, Op::GroupNonUniformBroadcastFirst) { crate::GatherMode::BroadcastFirst } else { let index_id = self.next()?; let index_lookup = self.lookup_expression.lookup(index_id)?; let index_handle = get_expr_handle!(index_id, index_lookup); match inst.op { Op::GroupNonUniformBroadcast => { crate::GatherMode::Broadcast(index_handle) } Op::GroupNonUniformShuffle => crate::GatherMode::Shuffle(index_handle), Op::GroupNonUniformShuffleDown => { crate::GatherMode::ShuffleDown(index_handle) } Op::GroupNonUniformShuffleUp => { crate::GatherMode::ShuffleUp(index_handle) } Op::GroupNonUniformShuffleXor => { crate::GatherMode::ShuffleXor(index_handle) } Op::GroupNonUniformQuadBroadcast => { crate::GatherMode::QuadBroadcast(index_handle) } _ => unreachable!(), } }; let result_type = self.lookup_type.lookup(result_type_id)?; let result_handle = ctx.expressions.append( crate::Expression::SubgroupOperationResult { ty: result_type.handle, }, span, ); self.lookup_expression.insert( result_id, LookupExpression { handle: result_handle, type_id: result_type_id, block_id, }, ); block.push( crate::Statement::SubgroupGather { result: result_handle, mode, argument: argument_handle, }, span, ); emitter.start(ctx.expressions); } Op::GroupNonUniformQuadSwap => { inst.expect(6)?; block.extend(emitter.finish(ctx.expressions)); let result_type_id = self.next()?; let result_id = self.next()?; let exec_scope_id = self.next()?; let argument_id = self.next()?; let direction_id = self.next()?; let argument_lookup = self.lookup_expression.lookup(argument_id)?; let argument_handle = get_expr_handle!(argument_id, argument_lookup); let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; let _exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) .filter(|exec_scope| *exec_scope == spirv::Scope::Subgroup as u32) .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; let direction_const = self.lookup_constant.lookup(direction_id)?; let direction_const = resolve_constant(ctx.gctx(), &direction_const.inner) .ok_or(Error::InvalidOperand)?; let direction = match direction_const { 0 => crate::Direction::X, 1 => crate::Direction::Y, 2 => crate::Direction::Diagonal, _ => unreachable!(), }; let result_type = self.lookup_type.lookup(result_type_id)?; let result_handle = ctx.expressions.append( crate::Expression::SubgroupOperationResult { ty: result_type.handle, }, span, ); self.lookup_expression.insert( result_id, LookupExpression { handle: result_handle, type_id: result_type_id, block_id, }, ); block.push( crate::Statement::SubgroupGather { mode: crate::GatherMode::QuadSwap(direction), result: result_handle, argument: argument_handle, }, span, ); emitter.start(ctx.expressions); } Op::AtomicLoad => { inst.expect(6)?; let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let pointer_id = self.next()?; let _scope_id = self.next()?; let _memory_semantics_id = self.next()?; let span = self.span_from_with_op(start); log::trace!("\t\t\tlooking up expr {pointer_id:?}"); let p_lexp_handle = get_expr_handle!(pointer_id, self.lookup_expression.lookup(pointer_id)?); // Create an expression for our result let expr = crate::Expression::Load { pointer: p_lexp_handle, }; let handle = ctx.expressions.append(expr, span); self.lookup_expression.insert( result_id, LookupExpression { handle, type_id: result_type_id, block_id, }, ); // Store any associated global variables so we can upgrade their types later self.record_atomic_access(ctx, p_lexp_handle)?; } Op::AtomicStore => { inst.expect(5)?; let start = self.data_offset; let pointer_id = self.next()?; let _scope_id = self.next()?; let _memory_semantics_id = self.next()?; let value_id = self.next()?; let span = self.span_from_with_op(start); log::trace!("\t\t\tlooking up pointer expr {pointer_id:?}"); let p_lexp_handle = get_expr_handle!(pointer_id, self.lookup_expression.lookup(pointer_id)?); log::trace!("\t\t\tlooking up value expr {pointer_id:?}"); let v_lexp_handle = get_expr_handle!(value_id, self.lookup_expression.lookup(value_id)?); block.extend(emitter.finish(ctx.expressions)); // Create a statement for the op itself let stmt = crate::Statement::Store { pointer: p_lexp_handle, value: v_lexp_handle, }; block.push(stmt, span); emitter.start(ctx.expressions); // Store any associated global variables so we can upgrade their types later self.record_atomic_access(ctx, p_lexp_handle)?; } Op::AtomicIIncrement | Op::AtomicIDecrement => { inst.expect(6)?; let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let pointer_id = self.next()?; let _scope_id = self.next()?; let _memory_semantics_id = self.next()?; let span = self.span_from_with_op(start); let (p_exp_h, p_base_ty_h) = self.get_exp_and_base_ty_handles( pointer_id, ctx, &mut emitter, &mut block, body_idx, )?; block.extend(emitter.finish(ctx.expressions)); // Create an expression for our result let r_lexp_handle = { let expr = crate::Expression::AtomicResult { ty: p_base_ty_h, comparison: false, }; let handle = ctx.expressions.append(expr, span); self.lookup_expression.insert( result_id, LookupExpression { handle, type_id: result_type_id, block_id, }, ); handle }; emitter.start(ctx.expressions); // Create a literal "1" to use as our value let one_lexp_handle = make_index_literal( ctx, 1, &mut block, &mut emitter, p_base_ty_h, result_type_id, span, )?; // Create a statement for the op itself let stmt = crate::Statement::Atomic { pointer: p_exp_h, fun: match inst.op { Op::AtomicIIncrement => crate::AtomicFunction::Add, _ => crate::AtomicFunction::Subtract, }, value: one_lexp_handle, result: Some(r_lexp_handle), }; block.push(stmt, span); // Store any associated global variables so we can upgrade their types later self.record_atomic_access(ctx, p_exp_h)?; } Op::AtomicCompareExchange => { inst.expect(9)?; let start = self.data_offset; let span = self.span_from_with_op(start); let result_type_id = self.next()?; let result_id = self.next()?; let pointer_id = self.next()?; let _memory_scope_id = self.next()?; let _equal_memory_semantics_id = self.next()?; let _unequal_memory_semantics_id = self.next()?; let value_id = self.next()?; let comparator_id = self.next()?; let (p_exp_h, p_base_ty_h) = self.get_exp_and_base_ty_handles( pointer_id, ctx, &mut emitter, &mut block, body_idx, )?; log::trace!("\t\t\tlooking up value expr {value_id:?}"); let v_lexp_handle = get_expr_handle!(value_id, self.lookup_expression.lookup(value_id)?); log::trace!("\t\t\tlooking up comparator expr {value_id:?}"); let c_lexp_handle = get_expr_handle!( comparator_id, self.lookup_expression.lookup(comparator_id)? ); // We know from the SPIR-V spec that the result type must be an integer // scalar, and we'll need the type itself to get a handle to the atomic // result struct. let crate::TypeInner::Scalar(scalar) = ctx.module.types[p_base_ty_h].inner else { return Err( crate::front::atomic_upgrade::Error::CompareExchangeNonScalarBaseType .into(), ); }; // Get a handle to the atomic result struct type. let atomic_result_struct_ty_h = ctx.module.generate_predeclared_type( crate::PredeclaredType::AtomicCompareExchangeWeakResult(scalar), ); block.extend(emitter.finish(ctx.expressions)); // Create an expression for our atomic result let atomic_lexp_handle = { let expr = crate::Expression::AtomicResult { ty: atomic_result_struct_ty_h, comparison: true, }; ctx.expressions.append(expr, span) }; // Create an dot accessor to extract the value from the // result struct __atomic_compare_exchange_result and use that // as the expression for the result_id { let expr = crate::Expression::AccessIndex { base: atomic_lexp_handle, index: 0, }; let handle = ctx.expressions.append(expr, span); // Use this dot accessor as the result id's expression let _ = self.lookup_expression.insert( result_id, LookupExpression { handle, type_id: result_type_id, block_id, }, ); } emitter.start(ctx.expressions); // Create a statement for the op itself let stmt = crate::Statement::Atomic { pointer: p_exp_h, fun: crate::AtomicFunction::Exchange { compare: Some(c_lexp_handle), }, value: v_lexp_handle, result: Some(atomic_lexp_handle), }; block.push(stmt, span); // Store any associated global variables so we can upgrade their types later self.record_atomic_access(ctx, p_exp_h)?; } Op::AtomicExchange | Op::AtomicIAdd | Op::AtomicISub | Op::AtomicSMin | Op::AtomicUMin | Op::AtomicSMax | Op::AtomicUMax | Op::AtomicAnd | Op::AtomicOr | Op::AtomicXor | Op::AtomicFAddEXT => self.parse_atomic_expr_with_value( inst, &mut emitter, ctx, &mut block, block_id, body_idx, match inst.op { Op::AtomicExchange => crate::AtomicFunction::Exchange { compare: None }, Op::AtomicIAdd | Op::AtomicFAddEXT => crate::AtomicFunction::Add, Op::AtomicISub => crate::AtomicFunction::Subtract, Op::AtomicSMin => crate::AtomicFunction::Min, Op::AtomicUMin => crate::AtomicFunction::Min, Op::AtomicSMax => crate::AtomicFunction::Max, Op::AtomicUMax => crate::AtomicFunction::Max, Op::AtomicAnd => crate::AtomicFunction::And, Op::AtomicOr => crate::AtomicFunction::InclusiveOr, Op::AtomicXor => crate::AtomicFunction::ExclusiveOr, _ => unreachable!(), }, )?, _ => { return Err(Error::UnsupportedInstruction(self.state, inst.op)); } } }; block.extend(emitter.finish(ctx.expressions)); if let Some(stmt) = terminator { block.push(stmt, crate::Span::default()); } // Save this block fragment in `block_ctx.blocks`, and mark it to be // incorporated into the current body at `Statement` assembly time. ctx.blocks.insert(block_id, block); let body = &mut ctx.bodies[body_idx]; body.data.push(BodyFragment::BlockId(block_id)); Ok(()) } } fn make_index_literal( ctx: &mut BlockContext, index: u32, block: &mut crate::Block, emitter: &mut crate::proc::Emitter, index_type: Handle, index_type_id: spirv::Word, span: crate::Span, ) -> Result, Error> { block.extend(emitter.finish(ctx.expressions)); let literal = match ctx.module.types[index_type].inner.scalar_kind() { Some(crate::ScalarKind::Uint) => crate::Literal::U32(index), Some(crate::ScalarKind::Sint) => crate::Literal::I32(index as i32), _ => return Err(Error::InvalidIndexType(index_type_id)), }; let expr = ctx .expressions .append(crate::Expression::Literal(literal), span); emitter.start(ctx.expressions); Ok(expr) } ================================================ FILE: naga/src/front/spv/null.rs ================================================ use alloc::vec; use super::Error; use crate::arena::{Arena, Handle}; /// Create a default value for an output built-in. pub fn generate_default_built_in( built_in: Option, ty: Handle, global_expressions: &mut Arena, span: crate::Span, ) -> Result, Error> { let expr = match built_in { Some(crate::BuiltIn::Position { .. }) => { let zero = global_expressions .append(crate::Expression::Literal(crate::Literal::F32(0.0)), span); let one = global_expressions .append(crate::Expression::Literal(crate::Literal::F32(1.0)), span); crate::Expression::Compose { ty, components: vec![zero, zero, zero, one], } } Some(crate::BuiltIn::PointSize) => crate::Expression::Literal(crate::Literal::F32(1.0)), Some(crate::BuiltIn::FragDepth) => crate::Expression::Literal(crate::Literal::F32(0.0)), Some(crate::BuiltIn::SampleMask) => { crate::Expression::Literal(crate::Literal::U32(u32::MAX)) } // Note: `crate::BuiltIn::ClipDistance` is intentionally left for the default path _ => crate::Expression::ZeroValue(ty), }; Ok(global_expressions.append(expr, span)) } ================================================ FILE: naga/src/front/type_gen.rs ================================================ /*! Type generators. */ use alloc::{string::ToString, vec}; use crate::{arena::Handle, span::Span}; impl crate::Module { /// Populate this module's [`SpecialTypes::ray_desc`] type. /// /// [`SpecialTypes::ray_desc`] is the type of the [`descriptor`] operand of /// an [`Initialize`] [`RayQuery`] statement. In WGSL, it is a struct type /// referred to as `RayDesc`. /// /// Backends consume values of this type to drive platform APIs, so if you /// change any its fields, you must update the backends to match. Look for /// backend code dealing with [`RayQueryFunction::Initialize`]. /// /// [`SpecialTypes::ray_desc`]: crate::SpecialTypes::ray_desc /// [`descriptor`]: crate::RayQueryFunction::Initialize::descriptor /// [`Initialize`]: crate::RayQueryFunction::Initialize /// [`RayQuery`]: crate::Statement::RayQuery /// [`RayQueryFunction::Initialize`]: crate::RayQueryFunction::Initialize pub fn generate_ray_desc_type(&mut self) -> Handle { if let Some(handle) = self.special_types.ray_desc { return handle; } let ty_flag = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::U32), }, Span::UNDEFINED, ); let ty_scalar = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::F32), }, Span::UNDEFINED, ); let ty_vector = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Vector { size: crate::VectorSize::Tri, scalar: crate::Scalar::F32, }, }, Span::UNDEFINED, ); let handle = self.types.insert( crate::Type { name: Some("RayDesc".to_string()), inner: crate::TypeInner::Struct { members: vec![ crate::StructMember { name: Some("flags".to_string()), ty: ty_flag, binding: None, offset: 0, }, crate::StructMember { name: Some("cull_mask".to_string()), ty: ty_flag, binding: None, offset: 4, }, crate::StructMember { name: Some("tmin".to_string()), ty: ty_scalar, binding: None, offset: 8, }, crate::StructMember { name: Some("tmax".to_string()), ty: ty_scalar, binding: None, offset: 12, }, crate::StructMember { name: Some("origin".to_string()), ty: ty_vector, binding: None, offset: 16, }, crate::StructMember { name: Some("dir".to_string()), ty: ty_vector, binding: None, offset: 32, }, ], span: 48, }, }, Span::UNDEFINED, ); self.special_types.ray_desc = Some(handle); handle } /// Make sure the types for the vertex return are in the module's type pub fn generate_vertex_return_type(&mut self) -> Handle { if let Some(handle) = self.special_types.ray_vertex_return { return handle; } let ty_vec3f = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Vector { size: crate::VectorSize::Tri, scalar: crate::Scalar::F32, }, }, Span::UNDEFINED, ); let array = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Array { base: ty_vec3f, size: crate::ArraySize::Constant(core::num::NonZeroU32::new(3).unwrap()), stride: 16, }, }, Span::UNDEFINED, ); self.special_types.ray_vertex_return = Some(array); array } /// Populate this module's [`SpecialTypes::ray_intersection`] type. /// /// [`SpecialTypes::ray_intersection`] is the type of a /// `RayQueryGetIntersection` expression. In WGSL, it is a struct type /// referred to as `RayIntersection`. /// /// Backends construct values of this type based on platform APIs, so if you /// change any its fields, you must update the backends to match. Look for /// the backend's handling for [`Expression::RayQueryGetIntersection`]. /// /// [`SpecialTypes::ray_intersection`]: crate::SpecialTypes::ray_intersection /// [`Expression::RayQueryGetIntersection`]: crate::Expression::RayQueryGetIntersection pub fn generate_ray_intersection_type(&mut self) -> Handle { if let Some(handle) = self.special_types.ray_intersection { return handle; } let ty_flag = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::U32), }, Span::UNDEFINED, ); let ty_scalar = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::F32), }, Span::UNDEFINED, ); let ty_barycentrics = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Vector { size: crate::VectorSize::Bi, scalar: crate::Scalar::F32, }, }, Span::UNDEFINED, ); let ty_bool = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::BOOL), }, Span::UNDEFINED, ); let ty_transform = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Matrix { columns: crate::VectorSize::Quad, rows: crate::VectorSize::Tri, scalar: crate::Scalar::F32, }, }, Span::UNDEFINED, ); let handle = self.types.insert( crate::Type { name: Some("RayIntersection".to_string()), inner: crate::TypeInner::Struct { members: vec![ crate::StructMember { name: Some("kind".to_string()), ty: ty_flag, binding: None, offset: 0, }, crate::StructMember { name: Some("t".to_string()), ty: ty_scalar, binding: None, offset: 4, }, crate::StructMember { name: Some("instance_custom_data".to_string()), ty: ty_flag, binding: None, offset: 8, }, crate::StructMember { name: Some("instance_index".to_string()), ty: ty_flag, binding: None, offset: 12, }, crate::StructMember { name: Some("sbt_record_offset".to_string()), ty: ty_flag, binding: None, offset: 16, }, crate::StructMember { name: Some("geometry_index".to_string()), ty: ty_flag, binding: None, offset: 20, }, crate::StructMember { name: Some("primitive_index".to_string()), ty: ty_flag, binding: None, offset: 24, }, crate::StructMember { name: Some("barycentrics".to_string()), ty: ty_barycentrics, binding: None, offset: 28, }, crate::StructMember { name: Some("front_face".to_string()), ty: ty_bool, binding: None, offset: 36, }, crate::StructMember { name: Some("object_to_world".to_string()), ty: ty_transform, binding: None, offset: 48, }, crate::StructMember { name: Some("world_to_object".to_string()), ty: ty_transform, binding: None, offset: 112, }, ], span: 176, }, }, Span::UNDEFINED, ); self.special_types.ray_intersection = Some(handle); handle } /// Generate [`SpecialTypes::external_texture_params`] and /// [`SpecialTypes::external_texture_transfer_function`]. /// /// Other than the WGSL backend, every backend that supports external /// textures does so by lowering them to a set of ordinary textures and /// some parameters saying how to sample from them. These types are used /// for said parameters. Note that they are not used by the IR, but /// generated purely as a convenience for the backends. /// /// [`SpecialTypes::external_texture_params`]: crate::ir::SpecialTypes::external_texture_params /// [`SpecialTypes::external_texture_transfer_function`]: crate::ir::SpecialTypes::external_texture_transfer_function pub fn generate_external_texture_types(&mut self) { if self.special_types.external_texture_params.is_some() { return; } let ty_f32 = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::F32), }, Span::UNDEFINED, ); let ty_u32 = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::U32), }, Span::UNDEFINED, ); let ty_vec2u = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Vector { size: crate::VectorSize::Bi, scalar: crate::Scalar::U32, }, }, Span::UNDEFINED, ); let ty_mat3x2f = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Matrix { columns: crate::VectorSize::Tri, rows: crate::VectorSize::Bi, scalar: crate::Scalar::F32, }, }, Span::UNDEFINED, ); let ty_mat3x3f = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Matrix { columns: crate::VectorSize::Tri, rows: crate::VectorSize::Tri, scalar: crate::Scalar::F32, }, }, Span::UNDEFINED, ); let ty_mat4x4f = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Matrix { columns: crate::VectorSize::Quad, rows: crate::VectorSize::Quad, scalar: crate::Scalar::F32, }, }, Span::UNDEFINED, ); let transfer_fn_handle = self.types.insert( crate::Type { name: Some("NagaExternalTextureTransferFn".to_string()), inner: crate::TypeInner::Struct { members: vec![ crate::StructMember { name: Some("a".to_string()), ty: ty_f32, binding: None, offset: 0, }, crate::StructMember { name: Some("b".to_string()), ty: ty_f32, binding: None, offset: 4, }, crate::StructMember { name: Some("g".to_string()), ty: ty_f32, binding: None, offset: 8, }, crate::StructMember { name: Some("k".to_string()), ty: ty_f32, binding: None, offset: 12, }, ], span: 16, }, }, Span::UNDEFINED, ); self.special_types.external_texture_transfer_function = Some(transfer_fn_handle); let params_handle = self.types.insert( crate::Type { name: Some("NagaExternalTextureParams".to_string()), inner: crate::TypeInner::Struct { members: vec![ crate::StructMember { name: Some("yuv_conversion_matrix".to_string()), ty: ty_mat4x4f, binding: None, offset: 0, }, crate::StructMember { name: Some("gamut_conversion_matrix".to_string()), ty: ty_mat3x3f, binding: None, offset: 64, }, crate::StructMember { name: Some("src_tf".to_string()), ty: transfer_fn_handle, binding: None, offset: 112, }, crate::StructMember { name: Some("dst_tf".to_string()), ty: transfer_fn_handle, binding: None, offset: 128, }, crate::StructMember { name: Some("sample_transform".to_string()), ty: ty_mat3x2f, binding: None, offset: 144, }, crate::StructMember { name: Some("load_transform".to_string()), ty: ty_mat3x2f, binding: None, offset: 168, }, crate::StructMember { name: Some("size".to_string()), ty: ty_vec2u, binding: None, offset: 192, }, crate::StructMember { name: Some("num_planes".to_string()), ty: ty_u32, binding: None, offset: 200, }, ], span: 208, }, }, Span::UNDEFINED, ); self.special_types.external_texture_params = Some(params_handle); } /// Populate this module's [`SpecialTypes::predeclared_types`] type and return the handle. /// /// [`SpecialTypes::predeclared_types`]: crate::SpecialTypes::predeclared_types pub fn generate_predeclared_type( &mut self, special_type: crate::PredeclaredType, ) -> Handle { if let Some(value) = self.special_types.predeclared_types.get(&special_type) { return *value; } let name = special_type.struct_name(); let ty = match special_type { crate::PredeclaredType::AtomicCompareExchangeWeakResult(scalar) => { let bool_ty = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar::BOOL), }, Span::UNDEFINED, ); let scalar_ty = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(scalar), }, Span::UNDEFINED, ); crate::Type { name: Some(name), inner: crate::TypeInner::Struct { members: vec![ crate::StructMember { name: Some("old_value".to_string()), ty: scalar_ty, binding: None, offset: 0, }, crate::StructMember { name: Some("exchanged".to_string()), ty: bool_ty, binding: None, offset: scalar.width as u32, }, ], span: scalar.width as u32 * 2, }, } } crate::PredeclaredType::ModfResult { size, scalar } => { let float_ty = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(scalar), }, Span::UNDEFINED, ); let (member_ty, second_offset) = if let Some(size) = size { let vec_ty = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Vector { size, scalar }, }, Span::UNDEFINED, ); (vec_ty, size as u32 * scalar.width as u32) } else { (float_ty, scalar.width as u32) }; crate::Type { name: Some(name), inner: crate::TypeInner::Struct { members: vec![ crate::StructMember { name: Some("fract".to_string()), ty: member_ty, binding: None, offset: 0, }, crate::StructMember { name: Some("whole".to_string()), ty: member_ty, binding: None, offset: second_offset, }, ], span: second_offset * 2, }, } } crate::PredeclaredType::FrexpResult { size, scalar } => { let float_ty = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(scalar), }, Span::UNDEFINED, ); let int_ty = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Sint, width: scalar.width, }), }, Span::UNDEFINED, ); let (fract_member_ty, exp_member_ty, second_offset) = if let Some(size) = size { let vec_float_ty = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Vector { size, scalar }, }, Span::UNDEFINED, ); let vec_int_ty = self.types.insert( crate::Type { name: None, inner: crate::TypeInner::Vector { size, scalar: crate::Scalar { kind: crate::ScalarKind::Sint, width: scalar.width, }, }, }, Span::UNDEFINED, ); (vec_float_ty, vec_int_ty, size as u32 * scalar.width as u32) } else { (float_ty, int_ty, scalar.width as u32) }; crate::Type { name: Some(name), inner: crate::TypeInner::Struct { members: vec![ crate::StructMember { name: Some("fract".to_string()), ty: fract_member_ty, binding: None, offset: 0, }, crate::StructMember { name: Some("exp".to_string()), ty: exp_member_ty, binding: None, offset: second_offset, }, ], span: second_offset * 2, }, } } }; let handle = self.types.insert(ty, Span::UNDEFINED); self.special_types .predeclared_types .insert(special_type, handle); handle } } ================================================ FILE: naga/src/front/wgsl/error.rs ================================================ //! Formatting WGSL front end error messages. use crate::common::wgsl::TryToWgsl; use crate::diagnostic_filter::ConflictingDiagnosticRuleError; use crate::error::replace_control_chars; use crate::proc::{Alignment, ConstantEvaluatorError, ResolveError}; use crate::{Scalar, SourceLocation, Span}; use super::parse::directive::enable_extension::{EnableExtension, UnimplementedEnableExtension}; use super::parse::directive::language_extension::{ LanguageExtension, UnimplementedLanguageExtension, }; use super::parse::lexer::Token; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::files::SimpleFile; use codespan_reporting::term; use thiserror::Error; use alloc::{ borrow::Cow, boxed::Box, format, string::{String, ToString}, vec, vec::Vec, }; use core::ops::Range; #[derive(Clone, Debug)] pub struct ParseError { message: String, // The first span should be the primary span, and the other ones should be complementary. labels: Vec<(Span, Cow<'static, str>)>, notes: Vec, } impl ParseError { pub fn labels(&self) -> impl ExactSizeIterator + '_ { self.labels .iter() .map(|&(span, ref msg)| (span, msg.as_ref())) } pub fn message(&self) -> &str { &self.message } fn diagnostic(&self) -> Diagnostic<()> { let diagnostic = Diagnostic::error() .with_message(self.message.to_string()) .with_labels( self.labels .iter() .filter_map(|label| label.0.to_range().map(|range| (label, range))) .map(|(label, range)| { Label::primary((), range).with_message(label.1.to_string()) }) .collect(), ) .with_notes( self.notes .iter() .map(|note| format!("note: {note}")) .collect(), ); diagnostic } /// Emits a summary of the error to standard error stream. #[cfg(feature = "stderr")] pub fn emit_to_stderr(&self, source: &str) { self.emit_to_stderr_with_path(source, "wgsl") } /// Emits a summary of the error to standard error stream. #[cfg(feature = "stderr")] pub fn emit_to_stderr_with_path

(&self, source: &str, path: P) where P: AsRef, { let path = path.as_ref().display().to_string(); let files = SimpleFile::new(path, replace_control_chars(source)); let config = term::Config::default(); cfg_if::cfg_if! { if #[cfg(feature = "termcolor")] { let writer = term::termcolor::StandardStream::stderr(term::termcolor::ColorChoice::Auto); term::emit_to_write_style(&mut writer.lock(), &config, &files, &self.diagnostic()) .expect("cannot write error"); } else { let writer = std::io::stderr(); term::emit_to_io_write(&mut writer.lock(), &config, &files, &self.diagnostic()) .expect("cannot write error"); } } } /// Emits a summary of the error to a string. pub fn emit_to_string(&self, source: &str) -> String { self.emit_to_string_with_path(source, "wgsl") } /// Emits a summary of the error to a string. pub fn emit_to_string_with_path

(&self, source: &str, path: P) -> String where P: AsRef, { let path = path.as_ref().display().to_string(); let files = SimpleFile::new(path, replace_control_chars(source)); let config = term::Config::default(); let mut writer = crate::error::DiagnosticBuffer::new(); writer .emit_to_self(&config, &files, &self.diagnostic()) .expect("cannot write error"); writer.into_string() } /// Returns a [`SourceLocation`] for the first label in the error message. pub fn location(&self, source: &str) -> Option { self.labels.first().map(|label| label.0.location(source)) } } impl core::fmt::Display for ParseError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", self.message) } } impl core::error::Error for ParseError {} #[derive(Copy, Clone, Debug, PartialEq)] pub enum ExpectedToken<'a> { Token(Token<'a>), Identifier, AfterIdentListComma, AfterIdentListArg, /// LHS expression (identifier component_or_swizzle_specifier?, (`lhs_expression`) component_or_swizzle_specifier?, &`lhs_expression`, *`lhs_expression`) LhsExpression, /// Expected: constant, parenthesized expression, identifier PrimaryExpression, /// Expected: assignment, increment/decrement expression Assignment, /// Expected: 'case', 'default', '}' SwitchItem, /// Expected: ',', ')' WorkgroupSizeSeparator, /// Expected: 'struct', 'let', 'var', 'type', ';', 'fn', eof GlobalItem, /// Access of `var`, `let`, `const`. Variable, /// Access of a function Function, /// The `diagnostic` identifier of the `@diagnostic(…)` attribute. DiagnosticAttribute, /// statement Statement, /// for loop init statement (variable_or_value_statement, variable_updating_statement, func_call_statement) ForInit, /// for loop update statement (variable_updating_statement, func_call_statement) ForUpdate, } #[derive(Clone, Copy, Debug, Error, PartialEq)] pub enum NumberError { #[error("invalid numeric literal format")] Invalid, #[error("numeric literal not representable by target type")] NotRepresentable, } #[derive(Copy, Clone, Debug, PartialEq)] pub enum InvalidAssignmentType { Other, Swizzle, ImmutableBinding(Span), } #[derive(Clone, Debug)] pub(crate) enum Error<'a> { Unexpected(Span, ExpectedToken<'a>), UnexpectedComponents(Span), UnexpectedOperationInConstContext(Span), BadNumber(Span, NumberError), BadMatrixScalarKind(Span, Scalar), BadAccessor(Span), BadTexture(Span), BadTypeCast { span: Span, from_type: String, to_type: String, }, NotStorageTexture(Span), BadTextureSampleType { span: Span, scalar: Scalar, }, BadIncrDecrReferenceType(Span), InvalidResolve(ResolveError), /// A break if appeared outside of a continuing block InvalidBreakIf(Span), InvalidGatherComponent(Span), InvalidConstructorComponentType(Span, i32), InvalidIdentifierUnderscore(Span), ReservedIdentifierPrefix(Span), UnknownAddressSpace(Span), InvalidLocalVariableAddressSpace(Span), UnknownRayFlag(Span), RepeatedAttribute(Span), UnknownAttribute(Span), UnknownBuiltin(Span), UnknownAccess(Span), UnknownIdent(Span, &'a str), UnknownScalarType(Span), UnknownStorageFormat(Span), UnknownConservativeDepth(Span), UnknownEnableExtension(Span, &'a str), UnknownLanguageExtension(Span, &'a str), UnknownDiagnosticRuleName(Span), SizeAttributeTooLow(Span, u32), AlignAttributeTooLow(Span, Alignment), NonPowerOfTwoAlignAttribute(Span), InconsistentBinding(Span), TypeNotConstructible(Span), TypeNotInferable(Span), InitializationTypeMismatch { name: Span, expected: String, got: String, }, DeclMissingTypeAndInit(Span), MissingAttribute(&'static str, Span), InvalidAddrOfOperand(Span), InvalidAtomicPointer(Span), InvalidAtomicOperandType(Span), InvalidRayQueryPointer(Span), NotPointer(Span), NotReference(&'static str, Span), InvalidAssignment { span: Span, ty: InvalidAssignmentType, }, ReservedKeyword(Span), /// Redefinition of an identifier (used for both module-scope and local redefinitions). Redefinition { /// Span of the identifier in the previous definition. previous: Span, /// Span of the identifier in the new definition. current: Span, }, /// A declaration refers to itself directly. RecursiveDeclaration { /// The location of the name of the declaration. ident: Span, /// The point at which it is used. usage: Span, }, /// A declaration refers to itself indirectly, through one or more other /// definitions. CyclicDeclaration { /// The location of the name of some declaration in the cycle. ident: Span, /// The edges of the cycle of references. /// /// Each `(decl, reference)` pair indicates that the declaration whose /// name is `decl` has an identifier at `reference` whose definition is /// the next declaration in the cycle. The last pair's `reference` is /// the same identifier as `ident`, above. path: Box<[(Span, Span)]>, }, InvalidSwitchSelector { span: Span, }, InvalidSwitchCase { span: Span, }, SwitchCaseTypeMismatch { span: Span, }, CalledEntryPoint(Span), CalledLocalDecl(Span), WrongArgumentCount { span: Span, expected: Range, found: u32, }, /// No overload of this function accepts this many arguments. TooManyArguments { /// The name of the function being called. function: String, /// The function name in the call expression. call_span: Span, /// The first argument that is unacceptable. arg_span: Span, /// Maximum number of arguments accepted by any overload of /// this function. max_arguments: u32, }, /// A value passed to a builtin function has a type that is not /// accepted by any overload of the function. WrongArgumentType { /// The name of the function being called. function: String, /// The function name in the call expression. call_span: Span, /// The first argument whose type is unacceptable. arg_span: Span, /// The index of the first argument whose type is unacceptable. arg_index: u32, /// That argument's actual type. arg_ty: String, /// The set of argument types that would have been accepted for /// this argument, given the prior arguments. allowed: Vec, }, /// A value passed to a builtin function has a type that is not /// accepted, given the earlier arguments' types. InconsistentArgumentType { /// The name of the function being called. function: String, /// The function name in the call expression. call_span: Span, /// The first unacceptable argument. arg_span: Span, /// The index of the first unacceptable argument. arg_index: u32, /// The actual type of the first unacceptable argument. arg_ty: String, /// The prior argument whose type made the `arg_span` argument /// unacceptable. inconsistent_span: Span, /// The index of the `inconsistent_span` argument. inconsistent_index: u32, /// The type of the `inconsistent_span` argument. inconsistent_ty: String, /// The types that would have been accepted instead of the /// first unacceptable argument. allowed: Vec, }, FunctionReturnsVoid(Span), FunctionMustUseUnused(Span), FunctionMustUseReturnsVoid(Span, Span), InvalidWorkGroupUniformLoad(Span), Internal(&'static str), ExpectedConstExprConcreteIntegerScalar(Span), ExpectedNonNegative(Span), ExpectedPositiveArrayLength(Span), MissingWorkgroupSize(Span), ConstantEvaluatorError(Box, Span), AutoConversion(Box), AutoConversionLeafScalar(Box), ConcretizationFailed(Box), ExceededLimitForNestedBraces { span: Span, limit: u8, }, PipelineConstantIDValue(Span), NotBool(Span), ConstAssertFailed(Span), DirectiveAfterFirstGlobalDecl { directive_span: Span, }, EnableExtensionNotYetImplemented { kind: UnimplementedEnableExtension, span: Span, }, EnableExtensionNotEnabled { kind: EnableExtension, span: Span, }, EnableExtensionNotSupported { kind: EnableExtension, span: Span, }, LanguageExtensionNotYetImplemented { kind: UnimplementedLanguageExtension, span: Span, }, DiagnosticInvalidSeverity { severity_control_name_span: Span, }, DiagnosticDuplicateTriggeringRule(ConflictingDiagnosticRuleError), DiagnosticAttributeNotYetImplementedAtParseSite { site_name_plural: &'static str, spans: Vec, }, DiagnosticAttributeNotSupported { on_what: DiagnosticAttributeNotSupportedPosition, spans: Vec, }, SelectUnexpectedArgumentType { arg_span: Span, arg_type: String, }, SelectRejectAndAcceptHaveNoCommonType { reject_span: Span, reject_type: String, accept_span: Span, accept_type: String, }, ExpectedGlobalVariable { name_span: Span, }, StructMemberTooLarge { member_name_span: Span, }, TypeTooLarge { span: Span, }, UnderspecifiedCooperativeMatrix, InvalidCooperativeLoadType(Span), UnsupportedCooperativeScalar(Span), UnexpectedIdentForEnumerant(Span), UnexpectedExprForEnumerant(Span), UnusedArgsForTemplate(Vec), UnexpectedTemplate(Span), MissingTemplateArg { span: Span, description: &'static str, }, UnexpectedExprForTypeExpression(Span), MissingIncomingPayload(Span), } impl From for Error<'_> { fn from(value: ConflictingDiagnosticRuleError) -> Self { Self::DiagnosticDuplicateTriggeringRule(value) } } /// Used for diagnostic refinement in [`Error::DiagnosticAttributeNotSupported`]. #[derive(Clone, Copy, Debug)] pub(crate) enum DiagnosticAttributeNotSupportedPosition { SemicolonInModulePosition, Other { display_plural: &'static str }, } impl From<&'static str> for DiagnosticAttributeNotSupportedPosition { fn from(display_plural: &'static str) -> Self { Self::Other { display_plural } } } #[derive(Clone, Debug)] pub(crate) struct AutoConversionError { pub dest_span: Span, pub dest_type: String, pub source_span: Span, pub source_type: String, } #[derive(Clone, Debug)] pub(crate) struct AutoConversionLeafScalarError { pub dest_span: Span, pub dest_scalar: String, pub source_span: Span, pub source_type: String, } #[derive(Clone, Debug)] pub(crate) struct ConcretizationFailedError { pub expr_span: Span, pub expr_type: String, pub concretization_preferences: Vec<(String, ConstantEvaluatorError)>, } impl<'a> Error<'a> { #[cold] #[inline(never)] pub(crate) fn as_parse_error(&self, source: &'a str) -> ParseError { match *self { Error::Unexpected(unexpected_span, expected) => { let expected_str = match expected { ExpectedToken::Token(token) => match token { Token::Separator(c) => format!("`{c}`"), Token::Paren(c) => format!("`{c}`"), Token::Attribute => "@".to_string(), Token::Number(_) => "number".to_string(), Token::Word(s) => s.to_string(), Token::Operation(c) => format!("operation (`{c}`)"), Token::LogicalOperation(c) => format!("logical operation (`{c}`)"), Token::ShiftOperation(c) => format!("bitshift (`{c}{c}`)"), Token::AssignmentOperation(c) if c == '<' || c == '>' => { format!("bitshift (`{c}{c}=`)") } Token::AssignmentOperation(c) => format!("operation (`{c}=`)"), Token::IncrementOperation => "increment operation".to_string(), Token::DecrementOperation => "decrement operation".to_string(), Token::Arrow => "->".to_string(), Token::TemplateArgsStart => "template args start".to_string(), Token::TemplateArgsEnd => "template args end".to_string(), Token::Unknown(c) => format!("unknown (`{c}`)"), Token::Trivia => "trivia".to_string(), Token::DocComment(s) => format!("doc comment ('{s}')"), Token::ModuleDocComment(s) => format!("module doc comment ('{s}')"), Token::End => "end".to_string(), }, ExpectedToken::Identifier => "identifier".to_string(), ExpectedToken::LhsExpression => "LHS expression (identifier component_or_swizzle_specifier?, (`lhs_expression`) component_or_swizzle_specifier?, &`lhs_expression`, *`lhs_expression`)".to_string(), ExpectedToken::PrimaryExpression => "expression".to_string(), ExpectedToken::Assignment => "assignment or increment/decrement".to_string(), ExpectedToken::SwitchItem => concat!( "switch item (`case` or `default`) or a closing curly bracket ", "to signify the end of the switch statement (`}`)" ) .to_string(), ExpectedToken::WorkgroupSizeSeparator => { "workgroup size separator (`,`) or a closing parenthesis".to_string() } ExpectedToken::GlobalItem => concat!( "global item (`struct`, `const`, `var`, `alias`, ", "`fn`, `diagnostic`, `enable`, `requires`, `;`) ", "or the end of the file" ) .to_string(), ExpectedToken::Variable => "variable access".to_string(), ExpectedToken::Function => "function name".to_string(), ExpectedToken::AfterIdentListArg => { "next argument, trailing comma, or end of list (`,` or `;`)".to_string() } ExpectedToken::AfterIdentListComma => { "next argument or end of list (`;`)".to_string() } ExpectedToken::DiagnosticAttribute => { "the `diagnostic` attribute identifier".to_string() } ExpectedToken::Statement => "statement".to_string(), ExpectedToken::ForInit => "for loop initializer statement (`var`/`let`/`const` declaration, assignment, `i++`/`i--` statement, function call)".to_string(), ExpectedToken::ForUpdate => "for loop update statement (assignment, `i++`/`i--` statement, function call)".to_string(), }; ParseError { message: format!( "expected {}, found {:?}", expected_str, &source[unexpected_span], ), labels: vec![(unexpected_span, format!("expected {expected_str}").into())], notes: vec![], } } Error::UnexpectedComponents(bad_span) => ParseError { message: "unexpected components".to_string(), labels: vec![(bad_span, "unexpected components".into())], notes: vec![], }, Error::UnexpectedOperationInConstContext(span) => ParseError { message: "this operation is not supported in a const context".to_string(), labels: vec![(span, "operation not supported here".into())], notes: vec![], }, Error::BadNumber(bad_span, ref err) => ParseError { message: format!("{}: `{}`", err, &source[bad_span],), labels: vec![(bad_span, err.to_string().into())], notes: vec![], }, Error::BadMatrixScalarKind(span, scalar) => ParseError { message: format!( "matrix scalar type must be floating-point, but found `{}`", scalar.to_wgsl_for_diagnostics() ), labels: vec![(span, "must be floating-point (e.g. `f32`)".into())], notes: vec![], }, Error::BadAccessor(accessor_span) => ParseError { message: format!("invalid field accessor `{}`", &source[accessor_span],), labels: vec![(accessor_span, "invalid accessor".into())], notes: vec![], }, Error::UnknownIdent(ident_span, ident) => ParseError { message: format!("no definition in scope for identifier: `{ident}`"), labels: vec![(ident_span, "unknown identifier".into())], notes: vec![], }, Error::UnknownScalarType(bad_span) => ParseError { message: format!("unknown scalar type: `{}`", &source[bad_span]), labels: vec![(bad_span, "unknown scalar type".into())], notes: vec!["Valid scalar types are f32, f64, i32, u32, bool".into()], }, Error::NotStorageTexture(bad_span) => ParseError { message: "textureStore can only be applied to storage textures".to_string(), labels: vec![(bad_span, "not a storage texture".into())], notes: vec![], }, Error::BadTextureSampleType { span, scalar } => ParseError { message: format!( "texture sample type must be one of f32, i32 or u32, but found {}", scalar.to_wgsl_for_diagnostics() ), labels: vec![(span, "must be one of f32, i32 or u32".into())], notes: vec![], }, Error::BadIncrDecrReferenceType(span) => ParseError { message: concat!( "increment/decrement operation requires ", "reference type to be one of i32 or u32" ) .to_string(), labels: vec![(span, "must be a reference type of i32 or u32".into())], notes: vec![], }, Error::BadTexture(bad_span) => ParseError { message: format!( "expected an image, but found `{}` which is not an image", &source[bad_span] ), labels: vec![(bad_span, "not an image".into())], notes: vec![], }, Error::BadTypeCast { span, ref from_type, ref to_type, } => { let msg = format!("cannot cast a {from_type} to a {to_type}"); ParseError { message: msg.clone(), labels: vec![(span, msg.into())], notes: vec![], } } Error::InvalidResolve(ref resolve_error) => ParseError { message: resolve_error.to_string(), labels: vec![], notes: vec![], }, Error::InvalidBreakIf(bad_span) => ParseError { message: "A break if is only allowed in a continuing block".to_string(), labels: vec![(bad_span, "not in a continuing block".into())], notes: vec![], }, Error::InvalidGatherComponent(bad_span) => ParseError { message: format!( "textureGather component `{}` doesn't exist, must be 0, 1, 2, or 3", &source[bad_span] ), labels: vec![(bad_span, "invalid component".into())], notes: vec![], }, Error::InvalidConstructorComponentType(bad_span, component) => ParseError { message: format!("invalid type for constructor component at index [{component}]"), labels: vec![(bad_span, "invalid component type".into())], notes: vec![], }, Error::InvalidIdentifierUnderscore(bad_span) => ParseError { message: "Identifier can't be `_`".to_string(), labels: vec![(bad_span, "invalid identifier".into())], notes: vec![ "Use phony assignment instead (`_ =` notice the absence of `let` or `var`)" .to_string(), ], }, Error::ReservedIdentifierPrefix(bad_span) => ParseError { message: format!( "Identifier starts with a reserved prefix: `{}`", &source[bad_span] ), labels: vec![(bad_span, "invalid identifier".into())], notes: vec![], }, Error::UnknownAddressSpace(bad_span) => ParseError { message: format!("unknown address space: `{}`", &source[bad_span]), labels: vec![(bad_span, "unknown address space".into())], notes: vec![], }, Error::InvalidLocalVariableAddressSpace(bad_span) => ParseError { message: format!("invalid address space for local variable: `{}`", &source[bad_span]), labels: vec![(bad_span, "local variables can only use 'function' address space".into())], notes: vec![], }, Error::UnknownRayFlag(bad_span) => ParseError { message: format!("unknown ray flag: `{}`", &source[bad_span]), labels: vec![(bad_span, "unknown ray flag".into())], notes: vec![], }, Error::RepeatedAttribute(bad_span) => ParseError { message: format!("repeated attribute: `{}`", &source[bad_span]), labels: vec![(bad_span, "repeated attribute".into())], notes: vec![], }, Error::UnknownAttribute(bad_span) => ParseError { message: format!("unknown attribute: `{}`", &source[bad_span]), labels: vec![(bad_span, "unknown attribute".into())], notes: vec![], }, Error::UnknownBuiltin(bad_span) => ParseError { message: format!("unknown builtin: `{}`", &source[bad_span]), labels: vec![(bad_span, "unknown builtin".into())], notes: vec![], }, Error::UnknownAccess(bad_span) => ParseError { message: format!("unknown access: `{}`", &source[bad_span]), labels: vec![(bad_span, "unknown access".into())], notes: vec![], }, Error::UnknownStorageFormat(bad_span) => ParseError { message: format!("unknown storage format: `{}`", &source[bad_span]), labels: vec![(bad_span, "unknown storage format".into())], notes: vec![], }, Error::UnknownConservativeDepth(bad_span) => ParseError { message: format!("unknown conservative depth: `{}`", &source[bad_span]), labels: vec![(bad_span, "unknown conservative depth".into())], notes: vec![], }, Error::UnknownEnableExtension(span, word) => ParseError { message: format!("unknown enable-extension `{word}`"), labels: vec![(span, "".into())], notes: vec![ "See available extensions at ." .into(), ], }, Error::UnknownLanguageExtension(span, name) => ParseError { message: format!("unknown language extension `{name}`"), labels: vec![(span, "".into())], notes: vec![concat!( "See available extensions at ", "." ) .into()], }, Error::UnknownDiagnosticRuleName(span) => ParseError { message: format!("unknown `diagnostic(…)` rule name `{}`", &source[span]), labels: vec![(span, "not a valid diagnostic rule name".into())], notes: vec![concat!( "See available trigger rules at ", "." ) .into()], }, Error::SizeAttributeTooLow(bad_span, min_size) => ParseError { message: format!("struct member size must be at least {min_size}"), labels: vec![(bad_span, format!("must be at least {min_size}").into())], notes: vec![], }, Error::AlignAttributeTooLow(bad_span, min_align) => ParseError { message: format!("struct member alignment must be at least {min_align}"), labels: vec![(bad_span, format!("must be at least {min_align}").into())], notes: vec![], }, Error::NonPowerOfTwoAlignAttribute(bad_span) => ParseError { message: "struct member alignment must be a power of 2".to_string(), labels: vec![(bad_span, "must be a power of 2".into())], notes: vec![], }, Error::InconsistentBinding(span) => ParseError { message: "input/output binding is not consistent".to_string(), labels: vec![(span, "input/output binding is not consistent".into())], notes: vec![], }, Error::TypeNotConstructible(span) => ParseError { message: format!("type `{}` is not constructible", &source[span]), labels: vec![(span, "type is not constructible".into())], notes: vec![], }, Error::TypeNotInferable(span) => ParseError { message: "type can't be inferred".to_string(), labels: vec![(span, "type can't be inferred".into())], notes: vec![], }, Error::InitializationTypeMismatch { name, ref expected, ref got, } => ParseError { message: format!( "the type of `{}` is expected to be `{}`, but got `{}`", &source[name], expected, got, ), labels: vec![(name, format!("definition of `{}`", &source[name]).into())], notes: vec![], }, Error::DeclMissingTypeAndInit(name_span) => ParseError { message: format!( "declaration of `{}` needs a type specifier or initializer", &source[name_span] ), labels: vec![(name_span, "needs a type specifier or initializer".into())], notes: vec![], }, Error::MissingAttribute(name, name_span) => ParseError { message: format!( "variable `{}` needs a '{}' attribute", &source[name_span], name ), labels: vec![( name_span, format!("definition of `{}`", &source[name_span]).into(), )], notes: vec![], }, Error::InvalidAddrOfOperand(span) => ParseError { message: "cannot take the address of a vector component".to_string(), labels: vec![(span, "invalid operand for address-of".into())], notes: vec![], }, Error::InvalidAtomicPointer(span) => ParseError { message: "atomic operation is done on a pointer to a non-atomic".to_string(), labels: vec![(span, "atomic pointer is invalid".into())], notes: vec![], }, Error::InvalidAtomicOperandType(span) => ParseError { message: "atomic operand type is inconsistent with the operation".to_string(), labels: vec![(span, "atomic operand type is invalid".into())], notes: vec![], }, Error::InvalidRayQueryPointer(span) => ParseError { message: "ray query operation is done on a pointer to a non-ray-query".to_string(), labels: vec![(span, "ray query pointer is invalid".into())], notes: vec![], }, Error::NotPointer(span) => ParseError { message: "the operand of the `*` operator must be a pointer".to_string(), labels: vec![(span, "expression is not a pointer".into())], notes: vec![], }, Error::NotReference(what, span) => ParseError { message: format!("{what} must be a reference"), labels: vec![(span, "expression is not a reference".into())], notes: vec![], }, Error::InvalidAssignment { span, ty } => { let (extra_label, notes) = match ty { InvalidAssignmentType::Swizzle => ( None, vec![ "WGSL does not support assignments to swizzles".into(), "consider assigning each component individually".into(), ], ), InvalidAssignmentType::ImmutableBinding(binding_span) => ( Some((binding_span, "this is an immutable binding".into())), vec![format!( "consider declaring `{}` with `var` instead of `let`", &source[binding_span] )], ), InvalidAssignmentType::Other => (None, vec![]), }; ParseError { message: "invalid left-hand side of assignment".into(), labels: core::iter::once((span, "cannot assign to this expression".into())) .chain(extra_label) .collect(), notes, } } Error::ReservedKeyword(name_span) => ParseError { message: format!("name `{}` is a reserved keyword", &source[name_span]), labels: vec![( name_span, format!("definition of `{}`", &source[name_span]).into(), )], notes: vec![], }, Error::Redefinition { previous, current } => ParseError { message: format!("redefinition of `{}`", &source[current]), labels: vec![ ( current, format!("redefinition of `{}`", &source[current]).into(), ), ( previous, format!("previous definition of `{}`", &source[previous]).into(), ), ], notes: vec![], }, Error::RecursiveDeclaration { ident, usage } => ParseError { message: format!("declaration of `{}` is recursive", &source[ident]), labels: vec![(ident, "".into()), (usage, "uses itself here".into())], notes: vec![], }, Error::CyclicDeclaration { ident, ref path } => ParseError { message: format!("declaration of `{}` is cyclic", &source[ident]), labels: path .iter() .enumerate() .flat_map(|(i, &(ident, usage))| { [ (ident, "".into()), ( usage, if i == path.len() - 1 { "ending the cycle".into() } else { format!("uses `{}`", &source[ident]).into() }, ), ] }) .collect(), notes: vec![], }, Error::InvalidSwitchSelector { span } => ParseError { message: "invalid `switch` selector".to_string(), labels: vec![( span, "`switch` selector must be a scalar integer" .into(), )], notes: vec![], }, Error::InvalidSwitchCase { span } => ParseError { message: "invalid `switch` case selector value".to_string(), labels: vec![( span, "`switch` case selector must be a scalar integer const expression" .into(), )], notes: vec![], }, Error::SwitchCaseTypeMismatch { span } => ParseError { message: "invalid `switch` case selector value".to_string(), labels: vec![( span, "`switch` case selector must have the same type as the `switch` selector expression" .into(), )], notes: vec![], }, Error::CalledEntryPoint(span) => ParseError { message: "entry point cannot be called".to_string(), labels: vec![(span, "entry point cannot be called".into())], notes: vec![], }, Error::CalledLocalDecl(span) => ParseError { message: "local declaration cannot be called".to_string(), labels: vec![(span, "local declaration cannot be called".into())], notes: vec![], }, Error::WrongArgumentCount { span, ref expected, found, } => ParseError { message: format!( "wrong number of arguments: expected {}, found {}", if expected.len() < 2 { format!("{}", expected.start) } else { format!("{}..{}", expected.start, expected.end) }, found ), labels: vec![(span, "wrong number of arguments".into())], notes: vec![], }, Error::TooManyArguments { ref function, call_span, arg_span, max_arguments, } => ParseError { message: format!("too many arguments passed to `{function}`"), labels: vec![ (call_span, "".into()), (arg_span, format!("unexpected argument #{}", max_arguments + 1).into()) ], notes: vec![ format!("The `{function}` function accepts at most {max_arguments} argument(s)") ], }, Error::WrongArgumentType { ref function, call_span, arg_span, arg_index, ref arg_ty, ref allowed, } => { let message = format!( "wrong type passed as argument #{} to `{function}`", arg_index + 1, ); let labels = vec![ (call_span, "".into()), (arg_span, format!("argument #{} has type `{arg_ty}`", arg_index + 1).into()) ]; let mut notes = vec![]; notes.push(format!("`{function}` accepts the following types for argument #{}:", arg_index + 1)); notes.extend(allowed.iter().map(|ty| format!("allowed type: {ty}"))); ParseError { message, labels, notes } }, Error::InconsistentArgumentType { ref function, call_span, arg_span, arg_index, ref arg_ty, inconsistent_span, inconsistent_index, ref inconsistent_ty, ref allowed } => { let message = format!( "inconsistent type passed as argument #{} to `{function}`", arg_index + 1, ); let labels = vec![ (call_span, "".into()), (arg_span, format!("argument #{} has type {arg_ty}", arg_index + 1).into()), (inconsistent_span, format!( "this argument has type {inconsistent_ty}, which constrains subsequent arguments" ).into()), ]; let mut notes = vec![ format!("Because argument #{} has type {inconsistent_ty}, only the following types", inconsistent_index + 1), format!("(or types that automatically convert to them) are accepted for argument #{}:", arg_index + 1), ]; notes.extend(allowed.iter().map(|ty| format!("allowed type: {ty}"))); ParseError { message, labels, notes } } Error::FunctionReturnsVoid(span) => ParseError { message: "function does not return any value".to_string(), labels: vec![(span, "".into())], notes: vec![ "perhaps you meant to call the function in a separate statement?".into(), ], }, Error::FunctionMustUseUnused(call) => ParseError { message: "unused return value from function annotated with @must_use".into(), labels: vec![(call, "".into())], notes: vec![ format!( "function '{}' is declared with `@must_use` attribute", &source[call], ), "use a phony assignment or declare a value using the function call as the initializer".into(), ], }, Error::FunctionMustUseReturnsVoid(attr, signature) => ParseError { message: "function annotated with @must_use but does not return any value".into(), labels: vec![ (attr, "".into()), (signature, "".into()), ], notes: vec![ "declare a return type or remove the attribute".into(), ], }, Error::InvalidWorkGroupUniformLoad(span) => ParseError { message: "incorrect type passed to workgroupUniformLoad".into(), labels: vec![(span, "".into())], notes: vec!["passed type must be a workgroup pointer".into()], }, Error::Internal(message) => ParseError { message: "internal WGSL front end error".to_string(), labels: vec![], notes: vec![message.into()], }, Error::ExpectedConstExprConcreteIntegerScalar(span) => ParseError { message: concat!( "must be a const-expression that ", "resolves to a concrete integer scalar (`u32` or `i32`)" ) .to_string(), labels: vec![(span, "must resolve to `u32` or `i32`".into())], notes: vec![], }, Error::ExpectedNonNegative(span) => ParseError { message: "must be non-negative (>= 0)".to_string(), labels: vec![(span, "must be non-negative".into())], notes: vec![], }, Error::ExpectedPositiveArrayLength(span) => ParseError { message: "array element count must be positive (> 0)".to_string(), labels: vec![(span, "must be positive".into())], notes: vec![], }, Error::ConstantEvaluatorError(ref e, span) => ParseError { message: e.to_string(), labels: vec![(span, "see msg".into())], notes: vec![], }, Error::MissingWorkgroupSize(span) => ParseError { message: "workgroup size is missing on compute shader entry point".to_string(), labels: vec![( span, "must be paired with a `@workgroup_size` attribute".into(), )], notes: vec![], }, Error::AutoConversion(ref error) => { // destructuring ensures all fields are handled let AutoConversionError { dest_span, ref dest_type, source_span, ref source_type, } = **error; ParseError { message: format!( "automatic conversions cannot convert `{source_type}` to `{dest_type}`" ), labels: vec![ ( dest_span, format!("a value of type {dest_type} is required here").into(), ), ( source_span, format!("this expression has type {source_type}").into(), ), ], notes: vec![], } } Error::AutoConversionLeafScalar(ref error) => { let AutoConversionLeafScalarError { dest_span, ref dest_scalar, source_span, ref source_type, } = **error; ParseError { message: format!( "automatic conversions cannot convert elements of `{source_type}` to `{dest_scalar}`" ), labels: vec![ ( dest_span, format!( "a value with elements of type {dest_scalar} is required here" ) .into(), ), ( source_span, format!("this expression has type {source_type}").into(), ), ], notes: vec![], } } Error::ConcretizationFailed(ref error) => { let ConcretizationFailedError { expr_span, ref expr_type, ref concretization_preferences, } = **error; ParseError { message: "failed to convert expression to a concrete type".to_string(), labels: vec![( expr_span, format!("this expression has type {expr_type}").into(), )], notes: concretization_preferences .iter() .map(|&(ref scalar, ref err)| format!("the expression couldn't be converted to have {scalar} scalar type: {err}") ) .collect(), } } Error::ExceededLimitForNestedBraces { span, limit } => ParseError { message: "brace nesting limit reached".into(), labels: vec![(span, "limit reached at this brace".into())], notes: vec![format!("nesting limit is currently set to {limit}")], }, Error::PipelineConstantIDValue(span) => ParseError { message: "pipeline constant ID must be between 0 and 65535 inclusive".to_string(), labels: vec![(span, "must be between 0 and 65535 inclusive".into())], notes: vec![], }, Error::NotBool(span) => ParseError { message: "must be a const-expression that resolves to a `bool`".to_string(), labels: vec![(span, "must resolve to `bool`".into())], notes: vec![], }, Error::ConstAssertFailed(span) => ParseError { message: "`const_assert` failure".to_string(), labels: vec![(span, "evaluates to `false`".into())], notes: vec![], }, Error::DirectiveAfterFirstGlobalDecl { directive_span } => ParseError { message: "expected global declaration, but found a global directive".into(), labels: vec![( directive_span, "written after first global declaration".into(), )], notes: vec![concat!( "global directives are only allowed before global declarations; ", "maybe hoist this closer to the top of the shader module?" ) .into()], }, Error::EnableExtensionNotYetImplemented { kind, span } => ParseError { message: format!( "the `{}` enable-extension is not yet supported", EnableExtension::Unimplemented(kind).to_ident() ), labels: vec![( span, concat!( "this enable-extension specifies standard functionality ", "which is not yet implemented in Naga" ) .into(), )], notes: vec![format!( concat!( "Let Naga maintainers know that you ran into this at ", ", ", "so they can prioritize it!" ), kind.tracking_issue_num() )], }, Error::EnableExtensionNotEnabled { kind, span } => ParseError { message: format!("the `{}` enable extension is not enabled", kind.to_ident()), labels: vec![( span, format!( concat!( "the `{}` \"Enable Extension\" is needed for this functionality, ", "but it is not currently enabled." ), kind.to_ident() ) .into(), )], notes: if let EnableExtension::Unimplemented(kind) = kind { vec![format!( concat!( "This \"Enable Extension\" is not yet implemented. ", "Let Naga maintainers know that you ran into this at ", ", ", "so they can prioritize it!" ), kind.tracking_issue_num() )] } else { vec![ format!( "You can enable this extension by adding `enable {};` at the top of the shader, before any other items.", kind.to_ident() ), ] }, }, Error::EnableExtensionNotSupported { kind, span } => ParseError { message: format!( "the `{}` extension is not supported in the current environment", kind.to_ident() ), labels: vec![( span, "unsupported enable-extension".into(), )], notes: vec![], }, Error::LanguageExtensionNotYetImplemented { kind, span } => ParseError { message: format!( "the `{}` language extension is not yet supported", LanguageExtension::Unimplemented(kind).to_ident() ), labels: vec![(span, "".into())], notes: vec![format!( concat!( "Let Naga maintainers know that you ran into this at ", ", ", "so they can prioritize it!" ), kind.tracking_issue_num() )], }, Error::DiagnosticInvalidSeverity { severity_control_name_span, } => ParseError { message: "invalid `diagnostic(…)` severity".into(), labels: vec![( severity_control_name_span, "not a valid severity level".into(), )], notes: vec![concat!( "See available severities at ", "." ) .into()], }, Error::DiagnosticDuplicateTriggeringRule(ConflictingDiagnosticRuleError { triggering_rule_spans, }) => { let [first_span, second_span] = triggering_rule_spans; ParseError { message: "found conflicting `diagnostic(…)` rule(s)".into(), labels: vec![ (first_span, "first rule".into()), (second_span, "second rule".into()), ], notes: vec![ concat!( "Multiple `diagnostic(…)` rules with the same rule name ", "conflict unless they are directives and the severity is the same.", ) .into(), "You should delete the rule you don't want.".into(), ], } } Error::DiagnosticAttributeNotYetImplementedAtParseSite { site_name_plural, ref spans, } => ParseError { message: "`@diagnostic(…)` attribute(s) not yet implemented".into(), labels: { let mut spans = spans.iter().cloned(); let first = spans .next() .map(|span| { ( span, format!("can't use this on {site_name_plural} (yet)").into(), ) }) .expect("internal error: diag. attr. rejection on empty map"); core::iter::once(first) .chain(spans.map(|span| (span, "".into()))) .collect() }, notes: vec![format!(concat!( "Let Naga maintainers know that you ran into this at ", ", ", "so they can prioritize it!" ))], }, Error::DiagnosticAttributeNotSupported { on_what, ref spans } => { // In this case the user may have intended to create a global diagnostic filter directive, // so display a note to them suggesting the correct syntax. let intended_diagnostic_directive = match on_what { DiagnosticAttributeNotSupportedPosition::SemicolonInModulePosition => true, DiagnosticAttributeNotSupportedPosition::Other { .. } => false, }; let on_what_plural = match on_what { DiagnosticAttributeNotSupportedPosition::SemicolonInModulePosition => { "semicolons" } DiagnosticAttributeNotSupportedPosition::Other { display_plural } => { display_plural } }; ParseError { message: format!( "`@diagnostic(…)` attribute(s) on {on_what_plural} are not supported", ), labels: spans .iter() .cloned() .map(|span| (span, "".into())) .collect(), notes: vec![ concat!( "`@diagnostic(…)` attributes are only permitted on `fn`s, ", "some statements, and `switch`/`loop` bodies." ) .into(), { if intended_diagnostic_directive { concat!( "If you meant to declare a diagnostic filter that ", "applies to the entire module, move this line to ", "the top of the file and remove the `@` symbol." ) .into() } else { concat!( "These attributes are well-formed, ", "you likely just need to move them." ) .into() } }, ], } } Error::SelectUnexpectedArgumentType { arg_span, ref arg_type } => ParseError { message: "unexpected argument type for `select` call".into(), labels: vec![(arg_span, format!("this value of type {arg_type}").into())], notes: vec!["expected a scalar or a `vecN` of scalars".into()], }, Error::SelectRejectAndAcceptHaveNoCommonType { reject_span, ref reject_type, accept_span, ref accept_type, } => ParseError { message: "type mismatch for reject and accept values in `select` call".into(), labels: vec![ (reject_span, format!("reject value of type {reject_type}").into()), (accept_span, format!("accept value of type {accept_type}").into()), ], notes: vec![], }, Error::ExpectedGlobalVariable { name_span } => ParseError { message: "expected global variable".to_string(), labels: vec![(name_span, "variable used here".into())], notes: vec![], }, Error::StructMemberTooLarge { member_name_span } => ParseError { message: "struct member is too large".into(), labels: vec![(member_name_span, "this member exceeds the maximum size".into())], notes: vec![format!( "the maximum size is {} bytes", crate::valid::MAX_TYPE_SIZE )], }, Error::TypeTooLarge { span } => ParseError { message: "type is too large".into(), labels: vec![(span, "this type exceeds the maximum size".into())], notes: vec![format!( "the maximum size is {} bytes", crate::valid::MAX_TYPE_SIZE )], }, Error::UnderspecifiedCooperativeMatrix => ParseError { message: "cooperative matrix constructor is underspecified".into(), labels: vec![], notes: vec![format!("must be F32")], }, Error::InvalidCooperativeLoadType(span) => ParseError { message: "cooperative load should have a generic type for coop_mat".into(), labels: vec![(span, "type needs the coop_mat<...>".into())], notes: vec![format!("must be a valid cooperative type")], }, Error::UnsupportedCooperativeScalar(span) => ParseError { message: "cooperative scalar type is not supported".into(), labels: vec![(span, "type needs the scalar type specified".into())], notes: vec![format!("must be F32")], }, Error::UnexpectedIdentForEnumerant(ident_span) => ParseError { message: format!( "identifier `{}` resolves to a declaration", &source[ident_span] ), labels: vec![(ident_span, "needs to resolve to a predeclared enumerant".into())], notes: vec![], }, Error::UnexpectedExprForEnumerant(expr_span) => ParseError { message: "unexpected expression".to_string(), labels: vec![(expr_span, "needs to be an identifier resolving to a predeclared enumerant".into())], notes: vec![], }, Error::UnusedArgsForTemplate(ref expr_spans) => ParseError { message: "unused expressions for template".to_string(), labels: expr_spans.iter().cloned().map(|span| -> (_, _){ (span, "unused".into()) }).collect(), notes: vec![], }, Error::UnexpectedTemplate(span) => ParseError { message: "unexpected template".to_string(), labels: vec![(span, "expected identifier".into())], notes: vec![], }, Error::MissingTemplateArg { span, description: arg, } => ParseError { message: format!( "`{}` needs a template argument specified: {arg}", &source[span] ), labels: vec![(span, "is missing a template argument".into())], notes: vec![], }, Error::UnexpectedExprForTypeExpression(expr_span) => ParseError { message: "unexpected expression".to_string(), labels: vec![(expr_span, "needs to be an identifier resolving to a type declaration (alias or struct) or predeclared type(-generator)".into())], notes: vec![], }, Error::MissingIncomingPayload(span) => ParseError { message: "incoming payload is missing on a `closest_hit`, `any_hit` or `miss` shader entry point".to_string(), labels: vec![( span, "must be paired with a `@incoming_payload` attribute".into(), )], notes: vec![], }, } } } ================================================ FILE: naga/src/front/wgsl/index.rs ================================================ use alloc::{boxed::Box, vec, vec::Vec}; use super::{Error, Result}; use crate::front::wgsl::parse::ast; use crate::{FastHashMap, Handle, Span}; /// A `GlobalDecl` list in which each definition occurs before all its uses. pub struct Index<'a> { dependency_order: Vec>>, } impl<'a> Index<'a> { /// Generate an `Index` for the given translation unit. /// /// Perform a topological sort on `tu`'s global declarations, placing /// referents before the definitions that refer to them. /// /// Return an error if the graph of references between declarations contains /// any cycles. pub fn generate(tu: &ast::TranslationUnit<'a>) -> Result<'a, Self> { // Produce a map from global definitions' names to their `Handle`s. // While doing so, reject conflicting definitions. let mut globals = FastHashMap::with_capacity_and_hasher(tu.decls.len(), Default::default()); for (handle, decl) in tu.decls.iter() { if let Some(ident) = decl_ident(decl) { let name = ident.name; if let Some(old) = globals.insert(name, handle) { return Err(Box::new(Error::Redefinition { previous: decl_ident(&tu.decls[old]) .expect("decl should have ident for redefinition") .span, current: ident.span, })); } } } let len = tu.decls.len(); let solver = DependencySolver { globals: &globals, module: tu, visited: vec![false; len], temp_visited: vec![false; len], path: Vec::new(), out: Vec::with_capacity(len), }; let dependency_order = solver.solve()?; Ok(Self { dependency_order }) } /// Iterate over `GlobalDecl`s, visiting each definition before all its uses. /// /// Produce handles for all of the `GlobalDecl`s of the `TranslationUnit` /// passed to `Index::generate`, ordered so that a given declaration is /// produced before any other declaration that uses it. pub fn visit_ordered(&self) -> impl Iterator>> + '_ { self.dependency_order.iter().copied() } } /// An edge from a reference to its referent in the current depth-first /// traversal. /// /// This is like `ast::Dependency`, except that we've determined which /// `GlobalDecl` it refers to. struct ResolvedDependency<'a> { /// The referent of some identifier used in the current declaration. decl: Handle>, /// Where that use occurs within the current declaration. usage: Span, } /// Local state for ordering a `TranslationUnit`'s module-scope declarations. /// /// Values of this type are used temporarily by `Index::generate` /// to perform a depth-first sort on the declarations. /// Technically, what we want is a topological sort, but a depth-first sort /// has one key benefit - it's much more efficient in storing /// the path of each node for error generation. struct DependencySolver<'source, 'temp> { /// A map from module-scope definitions' names to their handles. globals: &'temp FastHashMap<&'source str, Handle>>, /// The translation unit whose declarations we're ordering. module: &'temp ast::TranslationUnit<'source>, /// For each handle, whether we have pushed it onto `out` yet. visited: Vec, /// For each handle, whether it is an predecessor in the current depth-first /// traversal. This is used to detect cycles in the reference graph. temp_visited: Vec, /// The current path in our depth-first traversal. Used for generating /// error messages for non-trivial reference cycles. path: Vec>, /// The list of declaration handles, with declarations before uses. out: Vec>>, } impl<'a> DependencySolver<'a, '_> { /// Produce the sorted list of declaration handles, and check for cycles. fn solve(mut self) -> Result<'a, Vec>>> { for (id, _) in self.module.decls.iter() { if self.visited[id.index()] { continue; } self.dfs(id)?; } Ok(self.out) } /// Ensure that all declarations used by `id` have been added to the /// ordering, and then append `id` itself. fn dfs(&mut self, id: Handle>) -> Result<'a, ()> { let decl = &self.module.decls[id]; let id_usize = id.index(); self.temp_visited[id_usize] = true; for dep in decl.dependencies.iter() { if let Some(&dep_id) = self.globals.get(dep.ident) { self.path.push(ResolvedDependency { decl: dep_id, usage: dep.usage, }); let dep_id_usize = dep_id.index(); if self.temp_visited[dep_id_usize] { // Found a cycle. return if dep_id == id { // A declaration refers to itself directly. Err(Box::new(Error::RecursiveDeclaration { ident: decl_ident(decl).expect("decl should have ident").span, usage: dep.usage, })) } else { // A declaration refers to itself indirectly, through // one or more other definitions. Report the entire path // of references. let start_at = self .path .iter() .rev() .enumerate() .find_map(|(i, dep)| (dep.decl == dep_id).then_some(i)) .unwrap_or(0); Err(Box::new(Error::CyclicDeclaration { ident: decl_ident(&self.module.decls[dep_id]) .expect("decl should have ident") .span, path: self.path[start_at..] .iter() .map(|curr_dep| { let curr_id = curr_dep.decl; let curr_decl = &self.module.decls[curr_id]; ( decl_ident(curr_decl).expect("decl should have ident").span, curr_dep.usage, ) }) .collect(), })) }; } else if !self.visited[dep_id_usize] { self.dfs(dep_id)?; } // Remove this edge from the current path. self.path.pop(); } // Ignore unresolved identifiers; they may be predeclared objects. } // Remove this node from the current path. self.temp_visited[id_usize] = false; // Now everything this declaration uses has been visited, and is already // present in `out`. That means we can append this one to the // ordering, and mark it as visited. self.out.push(id); self.visited[id_usize] = true; Ok(()) } } const fn decl_ident<'a>(decl: &ast::GlobalDecl<'a>) -> Option> { match decl.kind { ast::GlobalDeclKind::Fn(ref f) => Some(f.name), ast::GlobalDeclKind::Var(ref v) => Some(v.name), ast::GlobalDeclKind::Const(ref c) => Some(c.name), ast::GlobalDeclKind::Override(ref o) => Some(o.name), ast::GlobalDeclKind::Struct(ref s) => Some(s.name), ast::GlobalDeclKind::Type(ref t) => Some(t.name), ast::GlobalDeclKind::ConstAssert(_) => None, } } ================================================ FILE: naga/src/front/wgsl/lower/construction.rs ================================================ use alloc::{boxed::Box, vec, vec::Vec}; use core::num::NonZeroU32; use crate::common::wgsl::{TryToWgsl, TypeContext}; use crate::front::wgsl::lower::{ExpressionContext, Lowerer}; use crate::front::wgsl::parse::ast; use crate::front::wgsl::{Error, Result}; use crate::{Handle, Span}; /// A [`constructor built-in function`]. /// /// WGSL has two types of such functions: /// /// - Those that fully specify the type being constructed, like /// `vec3(x,y,z)`, which obviously constructs a `vec3`. /// /// - Those that leave the component type of the composite being constructed /// implicit, to be inferred from the argument types, like `vec3(x,y,z)`, /// which constructs a `vec3` where `T` is the type of `x`, `y`, and `z`. /// /// This enum represents both cases. The `PartialFoo` variants /// represent the second case, where the component type is implicit. /// /// [`constructor built-in function`]: https://gpuweb.github.io/gpuweb/wgsl/#constructor-builtin-function pub enum Constructor { /// A vector construction whose component type is inferred from the /// argument: `vec3(1.0)`. PartialVector { size: crate::VectorSize }, /// A matrix construction whose component type is inferred from the /// argument: `mat2x2(1,2,3,4)`. PartialMatrix { columns: crate::VectorSize, rows: crate::VectorSize, }, /// An array whose component type and size are inferred from the arguments: /// `array(3,4,5)`. PartialArray, /// A known Naga type. /// /// When we match on this type, we need to see the `TypeInner` here, but at /// the point that we build this value we'll still need mutable access to /// the module later. To avoid borrowing from the module, the type parameter /// `T` is `Handle` initially. Then we use `borrow_inner` to produce a /// version holding a tuple `(Handle, &TypeInner)`. Type(T), } impl Constructor> { /// Return an equivalent `Constructor` value that includes borrowed /// `TypeInner` values alongside any type handles. /// /// The returned form is more convenient to match on, since the patterns /// can actually see what the handle refers to. fn borrow_inner( self, module: &crate::Module, ) -> Constructor<(Handle, &crate::TypeInner)> { match self { Constructor::PartialVector { size } => Constructor::PartialVector { size }, Constructor::PartialMatrix { columns, rows } => { Constructor::PartialMatrix { columns, rows } } Constructor::PartialArray => Constructor::PartialArray, Constructor::Type(handle) => Constructor::Type((handle, &module.types[handle].inner)), } } } enum Components<'a> { None, One { component: Handle, span: Span, ty_inner: &'a crate::TypeInner, }, Many { components: Vec>, spans: Vec, }, } impl Components<'_> { fn into_components_vec(self) -> Vec> { match self { Self::None => vec![], Self::One { component, .. } => vec![component], Self::Many { components, .. } => components, } } } impl<'source> Lowerer<'source, '_> { /// Generate Naga IR for a type constructor expression. /// /// The `constructor` value represents the head of the constructor /// expression, which is at least a hint of which type is being built; if /// it's one of the `Partial` variants, we need to consider the argument /// types as well. /// /// This is used for [`Call`] expressions, once we've determined that /// the "callable" (in WGSL spec terms) is actually a type. /// /// [`Call`]: ast::Expression::Call pub fn construct( &mut self, span: Span, constructor: Constructor>, ty_span: Span, components: &[Handle>], ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Handle> { use crate::proc::TypeResolution as Tr; let components = match *components { [] => Components::None, [component] => { let span = ctx.ast_expressions.get_span(component); let component = self.expression_for_abstract(component, ctx)?; let ty_inner = super::resolve_inner!(ctx, component); Components::One { component, span, ty_inner, } } ref ast_components @ [_, _, ..] => { let components = ast_components .iter() .map(|&expr| self.expression_for_abstract(expr, ctx)) .collect::>()?; let spans = ast_components .iter() .map(|&expr| ctx.ast_expressions.get_span(expr)) .collect(); for &component in &components { ctx.grow_types(component)?; } Components::Many { components, spans } } }; // Even though we computed `constructor` above, wait until now to borrow // a reference to the `TypeInner`, so that the component-handling code // above can have mutable access to the type arena. let constructor = constructor.borrow_inner(ctx.module); let expr; match (components, constructor) { // Zero-value constructor with explicit type. (Components::None, Constructor::Type((result_ty, inner))) if inner.is_constructible(&ctx.module.types) => { expr = crate::Expression::ZeroValue(result_ty); } // Zero-value constructor, vector with type inference (Components::None, Constructor::PartialVector { size }) => { // vec2(), vec3(), vec4() return vectors of abstractInts; the same // is not true of the similar constructors for matrices or arrays. // See https://www.w3.org/TR/WGSL/#vec2-builtin et seq. let result_ty = ctx.module.types.insert( crate::Type { name: None, inner: crate::TypeInner::Vector { size, scalar: crate::Scalar::ABSTRACT_INT, }, }, span, ); expr = crate::Expression::ZeroValue(result_ty); } // Zero-value constructor, matrix or array with type inference (Components::None, Constructor::PartialMatrix { .. } | Constructor::PartialArray) => { // We have no arguments from which to infer the result type, so // partial constructors aren't acceptable here. return Err(Box::new(Error::TypeNotInferable(ty_span))); } // Scalar constructor & conversion (scalar -> scalar) ( Components::One { component, ty_inner: &crate::TypeInner::Scalar { .. }, .. }, Constructor::Type((_, &crate::TypeInner::Scalar(scalar))), ) => { expr = crate::Expression::As { expr: component, kind: scalar.kind, convert: Some(scalar.width), }; } // Vector conversion (vector -> vector) ( Components::One { component, ty_inner: &crate::TypeInner::Vector { size: src_size, .. }, .. }, Constructor::Type(( _, &crate::TypeInner::Vector { size: dst_size, scalar: dst_scalar, }, )), ) if dst_size == src_size => { expr = crate::Expression::As { expr: component, kind: dst_scalar.kind, convert: Some(dst_scalar.width), }; } // Vector conversion (vector -> vector) - partial ( Components::One { component, ty_inner: &crate::TypeInner::Vector { size: src_size, .. }, .. }, Constructor::PartialVector { size: dst_size }, ) if dst_size == src_size => { // This is a trivial conversion: the sizes match, and a Partial // constructor doesn't specify a scalar type, so nothing can // possibly happen. return Ok(component); } // Matrix conversion (matrix -> matrix) ( Components::One { component, ty_inner: &crate::TypeInner::Matrix { columns: src_columns, rows: src_rows, .. }, .. }, Constructor::Type(( _, &crate::TypeInner::Matrix { columns: dst_columns, rows: dst_rows, scalar: dst_scalar, }, )), ) if dst_columns == src_columns && dst_rows == src_rows => { expr = crate::Expression::As { expr: component, kind: dst_scalar.kind, convert: Some(dst_scalar.width), }; } // Matrix conversion (matrix -> matrix) - partial ( Components::One { component, ty_inner: &crate::TypeInner::Matrix { columns: src_columns, rows: src_rows, .. }, .. }, Constructor::PartialMatrix { columns: dst_columns, rows: dst_rows, }, ) if dst_columns == src_columns && dst_rows == src_rows => { // This is a trivial conversion: the sizes match, and a Partial // constructor doesn't specify a scalar type, so nothing can // possibly happen. return Ok(component); } // Vector constructor (splat) - infer type ( Components::One { component, ty_inner: &crate::TypeInner::Scalar { .. }, .. }, Constructor::PartialVector { size }, ) => { expr = crate::Expression::Splat { size, value: component, }; } // Vector constructor (splat) ( Components::One { mut component, ty_inner: &crate::TypeInner::Scalar(component_scalar), span, }, Constructor::Type(( type_handle, &crate::TypeInner::Vector { size, scalar: vec_scalar, }, )), ) => { // Splat only allows automatic conversions of the component's scalar. if !component_scalar.automatically_converts_to(vec_scalar) { let component_ty = &ctx.typifier()[component]; let arg_ty = ctx.type_resolution_to_string(component_ty); return Err(Box::new(Error::WrongArgumentType { function: ctx.type_to_string(type_handle), call_span: ty_span, arg_span: span, arg_index: 0, arg_ty, allowed: vec![vec_scalar.to_wgsl_for_diagnostics()], })); } ctx.convert_slice_to_common_leaf_scalar( core::slice::from_mut(&mut component), vec_scalar, )?; expr = crate::Expression::Splat { size, value: component, }; } // Vector constructor (by elements), partial ( Components::Many { mut components, spans, }, Constructor::PartialVector { size }, ) => { let consensus_scalar = ctx .automatic_conversion_consensus(None, &components) .map_err(|index| { Error::InvalidConstructorComponentType(spans[index], index as i32) })?; ctx.convert_slice_to_common_leaf_scalar(&mut components, consensus_scalar)?; let inner = consensus_scalar.to_inner_vector(size); let ty = ctx.ensure_type_exists(inner); expr = crate::Expression::Compose { ty, components }; } // Vector constructor (by elements), full type given ( Components::Many { mut components, .. }, Constructor::Type((ty, &crate::TypeInner::Vector { scalar, .. })), ) => { ctx.try_automatic_conversions_for_vector(&mut components, scalar, ty_span)?; expr = crate::Expression::Compose { ty, components }; } // Matrix constructor (by elements), partial ( Components::Many { mut components, spans, }, Constructor::PartialMatrix { columns, rows }, ) if components.len() == columns as usize * rows as usize => { let consensus_scalar = ctx .automatic_conversion_consensus( Some(crate::Scalar::ABSTRACT_FLOAT), &components, ) .map_err(|index| { Error::InvalidConstructorComponentType(spans[index], index as i32) })?; ctx.convert_slice_to_common_leaf_scalar(&mut components, consensus_scalar)?; let vec_ty = ctx.ensure_type_exists(consensus_scalar.to_inner_vector(rows)); let components = components .chunks(rows as usize) .map(|vec_components| { ctx.append_expression( crate::Expression::Compose { ty: vec_ty, components: Vec::from(vec_components), }, Default::default(), ) }) .collect::>>()?; let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix { columns, rows, scalar: consensus_scalar, }); expr = crate::Expression::Compose { ty, components }; } // Matrix constructor (by elements), type given ( Components::Many { mut components, .. }, Constructor::Type(( _, &crate::TypeInner::Matrix { columns, rows, scalar, }, )), ) if components.len() == columns as usize * rows as usize => { let element = Tr::Value(crate::TypeInner::Scalar(scalar)); ctx.try_automatic_conversions_slice(&mut components, &element, ty_span)?; let vec_ty = ctx.ensure_type_exists(scalar.to_inner_vector(rows)); let components = components .chunks(rows as usize) .map(|vec_components| { ctx.append_expression( crate::Expression::Compose { ty: vec_ty, components: Vec::from(vec_components), }, Default::default(), ) }) .collect::>>()?; let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix { columns, rows, scalar, }); expr = crate::Expression::Compose { ty, components }; } // Matrix constructor (by columns), partial ( Components::Many { mut components, spans, }, Constructor::PartialMatrix { columns, rows }, ) if components.len() == columns as usize => { let consensus_scalar = ctx .automatic_conversion_consensus( Some(crate::Scalar::ABSTRACT_FLOAT), &components, ) .map_err(|index| { Error::InvalidConstructorComponentType(spans[index], index as i32) })?; let component_ty = crate::TypeInner::Vector { size: rows, scalar: consensus_scalar, }; ctx.try_automatic_conversions_slice( &mut components, &Tr::Value(component_ty), ty_span, )?; let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix { columns, rows, scalar: consensus_scalar, }); expr = crate::Expression::Compose { ty, components }; } // Matrix constructor (by columns), type given ( Components::Many { mut components, .. }, Constructor::Type(( ty, &crate::TypeInner::Matrix { columns, rows, scalar, }, )), ) if components.len() == columns as usize => { let component_ty = crate::TypeInner::Vector { size: rows, scalar }; ctx.try_automatic_conversions_slice( &mut components, &Tr::Value(component_ty), ty_span, )?; expr = crate::Expression::Compose { ty, components }; } // Array constructor - infer type (components, Constructor::PartialArray) => { let mut components = components.into_components_vec(); if let Ok(consensus_scalar) = ctx.automatic_conversion_consensus(None, &components) { // Note that this will *not* necessarily convert all the // components to the same type! The `automatic_conversion_consensus` // method only considers the parameters' leaf scalar // types; the parameters themselves could be any mix of // vectors, matrices, and scalars. // // But *if* it is possible for this array construction // expression to be well-typed at all, then all the // parameters must have the same type constructors (vec, // matrix, scalar) applied to their leaf scalars, so // reconciling their scalars is always the right thing to // do. And if this array construction is not well-typed, // these conversions will not make it so, and we can let // validation catch the error. ctx.convert_slice_to_common_leaf_scalar(&mut components, consensus_scalar)?; } else { // There's no consensus scalar. Emit the `Compose` // expression anyway, and let validation catch the problem. } let base = ctx.register_type(components[0])?; let inner = crate::TypeInner::Array { base, size: crate::ArraySize::Constant( NonZeroU32::new(u32::try_from(components.len()).unwrap()).unwrap(), ), stride: { ctx.layouter.update(ctx.module.to_ctx()).unwrap(); ctx.layouter[base].to_stride() }, }; let ty = ctx.ensure_type_exists(inner); expr = crate::Expression::Compose { ty, components }; } // Array constructor, explicit type. ( components, Constructor::Type((ty, inner @ &crate::TypeInner::Array { base, .. })), ) if inner.is_constructible(&ctx.module.types) => { let mut components = components.into_components_vec(); ctx.try_automatic_conversions_slice(&mut components, &Tr::Handle(base), ty_span)?; expr = crate::Expression::Compose { ty, components }; } // Struct constructor ( components, Constructor::Type((ty, inner @ &crate::TypeInner::Struct { ref members, .. })), ) if inner.is_constructible(&ctx.module.types) => { let mut components = components.into_components_vec(); let struct_ty_span = ctx.module.types.get_span(ty); // Make a vector of the members' type handles in advance, to // avoid borrowing `members` from `ctx` while we generate // new code. let members: Vec> = members.iter().map(|m| m.ty).collect(); for (component, &ty) in components.iter_mut().zip(&members) { *component = ctx.try_automatic_conversions(*component, &Tr::Handle(ty), struct_ty_span)?; } expr = crate::Expression::Compose { ty, components }; } // ERRORS // Bad conversion (type cast) ( Components::One { span, component, .. }, Constructor::Type(( ty, &(crate::TypeInner::Scalar { .. } | crate::TypeInner::Vector { .. } | crate::TypeInner::Matrix { .. }), )), ) => { let component_ty = &ctx.typifier()[component]; let from_type = ctx.type_resolution_to_string(component_ty); return Err(Box::new(Error::BadTypeCast { span, from_type, to_type: ctx.type_to_string(ty), })); } // Too many parameters for scalar constructor ( Components::Many { spans, .. }, Constructor::Type((_, &crate::TypeInner::Scalar { .. })), ) => { let span = spans[1].until(spans.last().unwrap()); return Err(Box::new(Error::UnexpectedComponents(span))); } // Other types can't be constructed _ => return Err(Box::new(Error::TypeNotConstructible(ty_span))), } let expr = ctx.append_expression(expr, span)?; Ok(expr) } } ================================================ FILE: naga/src/front/wgsl/lower/conversion.rs ================================================ //! WGSL's automatic conversions for abstract types. use alloc::{boxed::Box, string::String, vec::Vec}; use crate::common::wgsl::{TryToWgsl, TypeContext}; use crate::front::wgsl::error::{ AutoConversionError, AutoConversionLeafScalarError, ConcretizationFailedError, }; use crate::front::wgsl::Result; use crate::{Handle, Span}; impl<'source> super::ExpressionContext<'source, '_, '_> { /// Try to use WGSL's automatic conversions to convert `expr` to `goal_ty`. /// /// If no conversions are necessary, return `expr` unchanged. /// /// If automatic conversions cannot convert `expr` to `goal_ty`, return an /// [`AutoConversion`] error. /// /// Although the Load Rule is one of the automatic conversions, this /// function assumes it has already been applied if appropriate, as /// indicated by the fact that the Rust type of `expr` is not `Typed<_>`. /// /// [`AutoConversion`]: super::Error::AutoConversion pub fn try_automatic_conversions( &mut self, expr: Handle, goal_ty: &crate::proc::TypeResolution, goal_span: Span, ) -> Result<'source, Handle> { let expr_span = self.get_expression_span(expr); // Keep the TypeResolution so we can get type names for // structs in error messages. let expr_resolution = super::resolve!(self, expr); let types = &self.module.types; let expr_inner = expr_resolution.inner_with(types); let goal_inner = goal_ty.inner_with(types); // We can only convert abstract types, so if `expr` is not abstract do not even // attempt conversion. This allows the validator to catch type errors correctly // rather than them being misreported as type conversion errors. // If the type is an array (of an array, etc) then we must check whether the // type of the innermost array's base type is abstract. if !expr_inner.is_abstract(types) { return Ok(expr); } // If `expr` already has the requested type, we're done. if self.module.compare_types(expr_resolution, goal_ty) { return Ok(expr); } let (_expr_scalar, goal_scalar) = match expr_inner.automatically_converts_to(goal_inner, types) { Some(scalars) => scalars, None => { let source_type = self.type_resolution_to_string(expr_resolution); let dest_type = self.type_resolution_to_string(goal_ty); return Err(Box::new(super::Error::AutoConversion(Box::new( AutoConversionError { dest_span: goal_span, dest_type, source_span: expr_span, source_type, }, )))); } }; self.convert_leaf_scalar(expr, expr_span, goal_scalar) } /// Try to convert `expr`'s leaf scalar to `goal_scalar` using automatic conversions. /// /// If no conversions are necessary, return `expr` unchanged. /// /// If automatic conversions cannot convert `expr` to `goal_scalar`, return /// an [`AutoConversionLeafScalar`] error. /// /// Although the Load Rule is one of the automatic conversions, this /// function assumes it has already been applied if appropriate, as /// indicated by the fact that the Rust type of `expr` is not `Typed<_>`. /// /// [`AutoConversionLeafScalar`]: super::Error::AutoConversionLeafScalar pub fn try_automatic_conversion_for_leaf_scalar( &mut self, expr: Handle, goal_scalar: crate::Scalar, goal_span: Span, ) -> Result<'source, Handle> { let expr_span = self.get_expression_span(expr); let expr_resolution = super::resolve!(self, expr); let types = &self.module.types; let expr_inner = expr_resolution.inner_with(types); let make_error = || { let source_type = self.type_resolution_to_string(expr_resolution); super::Error::AutoConversionLeafScalar(Box::new(AutoConversionLeafScalarError { dest_span: goal_span, dest_scalar: goal_scalar.to_wgsl_for_diagnostics(), source_span: expr_span, source_type, })) }; let expr_scalar = match expr_inner.automatically_convertible_scalar(&self.module.types) { Some(scalar) => scalar, None => return Err(Box::new(make_error())), }; if expr_scalar == goal_scalar { return Ok(expr); } if !expr_scalar.automatically_converts_to(goal_scalar) { return Err(Box::new(make_error())); } assert!(expr_scalar.is_abstract()); self.convert_leaf_scalar(expr, expr_span, goal_scalar) } fn convert_leaf_scalar( &mut self, expr: Handle, expr_span: Span, goal_scalar: crate::Scalar, ) -> Result<'source, Handle> { let expr_inner = super::resolve_inner!(self, expr); if let crate::TypeInner::Array { .. } = *expr_inner { self.as_const_evaluator() .cast_array(expr, goal_scalar, expr_span) .map_err(|err| { Box::new(super::Error::ConstantEvaluatorError(err.into(), expr_span)) }) } else { let cast = crate::Expression::As { expr, kind: goal_scalar.kind, convert: Some(goal_scalar.width), }; self.append_expression(cast, expr_span) } } /// Try to convert `exprs` to `goal_ty` using WGSL's automatic conversions. pub fn try_automatic_conversions_slice( &mut self, exprs: &mut [Handle], goal_ty: &crate::proc::TypeResolution, goal_span: Span, ) -> Result<'source, ()> { for expr in exprs.iter_mut() { *expr = self.try_automatic_conversions(*expr, goal_ty, goal_span)?; } Ok(()) } /// Apply WGSL's automatic conversions to a vector constructor's arguments. /// /// When calling a vector constructor like `vec3(...)`, the parameters /// can be a mix of scalars and vectors, with the latter being spread out to /// contribute each of their components as a component of the new value. /// When the element type is explicit, as with `` in the example above, /// WGSL's automatic conversions should convert abstract scalar and vector /// parameters to the constructor's required scalar type. pub fn try_automatic_conversions_for_vector( &mut self, exprs: &mut [Handle], goal_scalar: crate::Scalar, goal_span: Span, ) -> Result<'source, ()> { use crate::proc::TypeResolution as Tr; use crate::TypeInner as Ti; let goal_scalar_res = Tr::Value(Ti::Scalar(goal_scalar)); for (i, expr) in exprs.iter_mut().enumerate() { // Keep the TypeResolution so we can get full type names // in error messages. let expr_resolution = super::resolve!(self, *expr); let types = &self.module.types; let expr_inner = expr_resolution.inner_with(types); match *expr_inner { Ti::Scalar(_) => { *expr = self.try_automatic_conversions(*expr, &goal_scalar_res, goal_span)?; } Ti::Vector { size, scalar: _ } => { let goal_vector_res = Tr::Value(Ti::Vector { size, scalar: goal_scalar, }); *expr = self.try_automatic_conversions(*expr, &goal_vector_res, goal_span)?; } _ => { let span = self.get_expression_span(*expr); return Err(Box::new(super::Error::InvalidConstructorComponentType( span, i as i32, ))); } } } Ok(()) } /// Convert `expr` to the leaf scalar type `scalar`. pub fn convert_to_leaf_scalar( &mut self, expr: &mut Handle, goal: crate::Scalar, ) -> Result<'source, ()> { let inner = super::resolve_inner!(self, *expr); // Do nothing if `inner` doesn't even have leaf scalars; // it's a type error that validation will catch. if inner.scalar() != Some(goal) { let cast = crate::Expression::As { expr: *expr, kind: goal.kind, convert: Some(goal.width), }; let expr_span = self.get_expression_span(*expr); *expr = self.append_expression(cast, expr_span)?; } Ok(()) } /// Convert all expressions in `exprs` to a common scalar type. /// /// Note that the caller is responsible for making sure these /// conversions are actually justified. This function simply /// generates `As` expressions, regardless of whether they are /// permitted WGSL automatic conversions. Callers intending to /// implement automatic conversions need to determine for /// themselves whether the casts we we generate are justified, /// perhaps by calling `TypeInner::automatically_converts_to` or /// `Scalar::automatic_conversion_combine`. pub fn convert_slice_to_common_leaf_scalar( &mut self, exprs: &mut [Handle], goal: crate::Scalar, ) -> Result<'source, ()> { for expr in exprs.iter_mut() { self.convert_to_leaf_scalar(expr, goal)?; } Ok(()) } /// Return an expression for the concretized value of `expr`. /// /// If `expr` is already concrete, return it unchanged. pub fn concretize( &mut self, expr: Handle, ) -> Result<'source, Handle> { let inner = super::resolve_inner!(self, expr); if let Some(scalar) = inner.automatically_convertible_scalar(&self.module.types) { use crate::ScalarKind as Sk; let concretization_preferences = match scalar.kind { // already concrete Sk::Sint | Sk::Uint | Sk::Float | Sk::Bool => return Ok(expr), Sk::AbstractInt => { [crate::Scalar::I32, crate::Scalar::U32, crate::Scalar::F32].as_slice() } Sk::AbstractFloat => [crate::Scalar::F32].as_slice(), }; let expr_span = self.get_expression_span(expr); let mut errors = Vec::new(); for concrete_scalar in concretization_preferences { match self .as_const_evaluator() .cast_array(expr, *concrete_scalar, expr_span) { Ok(expr) => return Ok(expr), Err(err) => { errors.push((concrete_scalar.to_wgsl_for_diagnostics(), err)); } } } if !errors.is_empty() { // A `TypeResolution` includes the type's full name, if // it has one. Also, avoid holding the borrow of `inner` // across the call to `cast_array`. let expr_type = &self.typifier()[expr]; return Err(Box::new(super::Error::ConcretizationFailed(Box::new( ConcretizationFailedError { expr_span, expr_type: self.type_resolution_to_string(expr_type), concretization_preferences: errors, }, )))); } } Ok(expr) } /// Find the consensus scalar of `components` under WGSL's automatic /// conversions. /// /// If `components` can all be converted to any common scalar via /// WGSL's automatic conversions, return the best such scalar. /// /// The `components` slice must not be empty. All elements' types must /// have been resolved. /// /// If `components` are definitely not acceptable as arguments to such /// constructors, return `Err(i)`, where `i` is the index in /// `components` of some problematic argument. /// /// If `base` is `Some(scalar)`, the consensus scalar must also be /// compatible with that `scalar`. This is used to restrict matrix /// initializers to floating-point types. /// /// This function doesn't fully type-check the arguments - it only /// considers their leaf scalar types. This means it may return `Ok` /// even when the Naga validator will reject the resulting /// construction expression later. pub fn automatic_conversion_consensus<'handle, I>( &self, base: Option, components: I, ) -> core::result::Result where I: IntoIterator>, I::IntoIter: Clone, // for debugging { let types = &self.module.types; let components_iter = components.into_iter(); log::debug!( "wgsl automatic_conversion_consensus: {}", components_iter .clone() .map(|&expr| { let res = &self.typifier()[expr]; self.type_resolution_to_string(res) }) .collect::>() .join(", ") ); let mut components_iter = components_iter .map(|&c| self.typifier()[c].inner_with(types).scalar()) .enumerate(); let base = base .or_else(|| components_iter.next().unwrap().1) .ok_or(0usize)?; let best = components_iter.try_fold(base, |best, (i, scalar)| { scalar .and_then(|scalar| best.automatic_conversion_combine(scalar)) .ok_or(i) })?; log::debug!(" consensus: {}", best.to_wgsl_for_diagnostics()); Ok(best) } } impl crate::TypeInner { fn automatically_convertible_scalar( &self, types: &crate::UniqueArena, ) -> Option { use crate::TypeInner as Ti; match *self { Ti::Scalar(scalar) | Ti::Vector { scalar, .. } | Ti::Matrix { scalar, .. } => { Some(scalar) } Ti::CooperativeMatrix { .. } => None, Ti::Array { base, .. } => types[base].inner.automatically_convertible_scalar(types), Ti::Atomic(_) | Ti::Pointer { .. } | Ti::ValuePointer { .. } | Ti::Struct { .. } | Ti::Image { .. } | Ti::Sampler { .. } | Ti::AccelerationStructure { .. } | Ti::RayQuery { .. } | Ti::BindingArray { .. } => None, } } /// Return the leaf scalar type of `pointer`. /// /// `pointer` must be a `TypeInner` representing a pointer type. pub fn pointer_automatically_convertible_scalar( &self, types: &crate::UniqueArena, ) -> Option { use crate::TypeInner as Ti; match *self { Ti::Scalar(scalar) | Ti::Vector { scalar, .. } | Ti::Matrix { scalar, .. } => { Some(scalar) } Ti::CooperativeMatrix { .. } => None, Ti::Atomic(_) => None, Ti::Pointer { base, .. } | Ti::Array { base, .. } => { types[base].inner.automatically_convertible_scalar(types) } Ti::ValuePointer { scalar, .. } => Some(scalar), Ti::Struct { .. } | Ti::Image { .. } | Ti::Sampler { .. } | Ti::AccelerationStructure { .. } | Ti::RayQuery { .. } | Ti::BindingArray { .. } => None, } } } impl crate::Scalar { /// Find the common type of `self` and `other` under WGSL's /// automatic conversions. /// /// If there are any scalars to which WGSL's automatic conversions /// will convert both `self` and `other`, return the best such /// scalar. Otherwise, return `None`. pub const fn automatic_conversion_combine(self, other: Self) -> Option { use crate::ScalarKind as Sk; match (self.kind, other.kind) { // When the kinds match... (Sk::AbstractFloat, Sk::AbstractFloat) | (Sk::AbstractInt, Sk::AbstractInt) | (Sk::Sint, Sk::Sint) | (Sk::Uint, Sk::Uint) | (Sk::Float, Sk::Float) | (Sk::Bool, Sk::Bool) => { if self.width == other.width { // ... either no conversion is necessary ... Some(self) } else { // ... or no conversion is possible. // We never convert concrete to concrete, and // abstract types should have only one size. None } } // AbstractInt converts to AbstractFloat. (Sk::AbstractFloat, Sk::AbstractInt) => Some(self), (Sk::AbstractInt, Sk::AbstractFloat) => Some(other), // AbstractFloat converts to Float. (Sk::AbstractFloat, Sk::Float) => Some(other), (Sk::Float, Sk::AbstractFloat) => Some(self), // AbstractInt converts to concrete integer or float. (Sk::AbstractInt, Sk::Uint | Sk::Sint | Sk::Float) => Some(other), (Sk::Uint | Sk::Sint | Sk::Float, Sk::AbstractInt) => Some(self), // AbstractFloat can't be reconciled with concrete integer types. (Sk::AbstractFloat, Sk::Uint | Sk::Sint) | (Sk::Uint | Sk::Sint, Sk::AbstractFloat) => { None } // Nothing can be reconciled with `bool`. (Sk::Bool, _) | (_, Sk::Bool) => None, // Different concrete types cannot be reconciled. (Sk::Sint | Sk::Uint | Sk::Float, Sk::Sint | Sk::Uint | Sk::Float) => None, } } /// Return `true` if automatic conversions will covert `self` to `goal`. pub fn automatically_converts_to(self, goal: Self) -> bool { self.automatic_conversion_combine(goal) == Some(goal) } pub(in crate::front::wgsl) const fn concretize(self) -> Self { use crate::ScalarKind as Sk; match self.kind { Sk::Sint | Sk::Uint | Sk::Float | Sk::Bool => self, Sk::AbstractInt => Self::I32, Sk::AbstractFloat => Self::F32, } } } ================================================ FILE: naga/src/front/wgsl/lower/mod.rs ================================================ use alloc::{ borrow::ToOwned, boxed::Box, format, string::{String, ToString}, vec::Vec, }; use core::num::NonZeroU32; use crate::front::wgsl::error::{Error, ExpectedToken, InvalidAssignmentType}; use crate::front::wgsl::index::Index; use crate::front::wgsl::parse::directive::enable_extension::EnableExtensions; use crate::front::wgsl::parse::number::Number; use crate::front::wgsl::parse::{ast, conv}; use crate::front::wgsl::Result; use crate::front::Typifier; use crate::{ common::wgsl::{TryToWgsl, TypeContext}, compact::KeepUnused, }; use crate::{common::ForDebugWithTypes, proc::LayoutErrorInner}; use crate::{ir, proc}; use crate::{Arena, FastHashMap, FastIndexMap, Handle, Span}; use construction::Constructor; use template_list::TemplateListIter; mod construction; mod conversion; mod template_list; /// Resolves the inner type of a given expression. /// /// Expects a &mut [`ExpressionContext`] and a [`Handle`]. /// /// Returns a &[`ir::TypeInner`]. /// /// Ideally, we would simply have a function that takes a `&mut ExpressionContext` /// and returns a `&TypeResolution`. Unfortunately, this leads the borrow checker /// to conclude that the mutable borrow lasts for as long as we are using the /// `&TypeResolution`, so we can't use the `ExpressionContext` for anything else - /// like, say, resolving another operand's type. Using a macro that expands to /// two separate calls, only the first of which needs a `&mut`, /// lets the borrow checker see that the mutable borrow is over. macro_rules! resolve_inner { ($ctx:ident, $expr:expr) => {{ $ctx.grow_types($expr)?; $ctx.typifier()[$expr].inner_with(&$ctx.module.types) }}; } pub(super) use resolve_inner; /// Resolves the inner types of two given expressions. /// /// Expects a &mut [`ExpressionContext`] and two [`Handle`]s. /// /// Returns a tuple containing two &[`ir::TypeInner`]. /// /// See the documentation of [`resolve_inner!`] for why this macro is necessary. macro_rules! resolve_inner_binary { ($ctx:ident, $left:expr, $right:expr) => {{ $ctx.grow_types($left)?; $ctx.grow_types($right)?; ( $ctx.typifier()[$left].inner_with(&$ctx.module.types), $ctx.typifier()[$right].inner_with(&$ctx.module.types), ) }}; } /// Resolves the type of a given expression. /// /// Expects a &mut [`ExpressionContext`] and a [`Handle`]. /// /// Returns a &[`TypeResolution`]. /// /// See the documentation of [`resolve_inner!`] for why this macro is necessary. /// /// [`TypeResolution`]: proc::TypeResolution macro_rules! resolve { ($ctx:ident, $expr:expr) => {{ let expr = $expr; $ctx.grow_types(expr)?; &$ctx.typifier()[expr] }}; } pub(super) use resolve; /// State for constructing a `ir::Module`. pub struct GlobalContext<'source, 'temp, 'out> { enable_extensions: EnableExtensions, /// The `TranslationUnit`'s expressions arena. ast_expressions: &'temp Arena>, // Naga IR values. /// The map from the names of module-scope declarations to the Naga IR /// `Handle`s we have built for them, owned by `Lowerer::lower`. globals: &'temp mut FastHashMap<&'source str, LoweredGlobalDecl>, /// The module we're constructing. module: &'out mut ir::Module, const_typifier: &'temp mut Typifier, layouter: &'temp mut proc::Layouter, global_expression_kind_tracker: &'temp mut proc::ExpressionKindTracker, } impl<'source> GlobalContext<'source, '_, '_> { const fn as_const(&mut self) -> ExpressionContext<'source, '_, '_> { ExpressionContext { enable_extensions: self.enable_extensions, ast_expressions: self.ast_expressions, globals: self.globals, module: self.module, const_typifier: self.const_typifier, layouter: self.layouter, expr_type: ExpressionContextType::Constant(None), global_expression_kind_tracker: self.global_expression_kind_tracker, } } const fn as_override(&mut self) -> ExpressionContext<'source, '_, '_> { ExpressionContext { enable_extensions: self.enable_extensions, ast_expressions: self.ast_expressions, globals: self.globals, module: self.module, const_typifier: self.const_typifier, layouter: self.layouter, expr_type: ExpressionContextType::Override, global_expression_kind_tracker: self.global_expression_kind_tracker, } } fn ensure_type_exists( &mut self, name: Option, inner: ir::TypeInner, ) -> Handle { self.module .types .insert(ir::Type { inner, name }, Span::UNDEFINED) } } /// State for lowering a statement within a function. pub struct StatementContext<'source, 'temp, 'out> { enable_extensions: EnableExtensions, // WGSL AST values. /// A reference to [`TranslationUnit::expressions`] for the translation unit /// we're lowering. /// /// [`TranslationUnit::expressions`]: ast::TranslationUnit::expressions ast_expressions: &'temp Arena>, // Naga IR values. /// The map from the names of module-scope declarations to the Naga IR /// `Handle`s we have built for them, owned by `Lowerer::lower`. globals: &'temp mut FastHashMap<&'source str, LoweredGlobalDecl>, /// A map from each `ast::Local` handle to the Naga expression /// we've built for it: /// /// - WGSL function arguments become Naga [`FunctionArgument`] expressions. /// /// - WGSL `var` declarations become Naga [`LocalVariable`] expressions. /// /// - WGSL `let` declararations become arbitrary Naga expressions. /// /// This always borrows the `local_table` local variable in /// [`Lowerer::function`]. /// /// [`LocalVariable`]: ir::Expression::LocalVariable /// [`FunctionArgument`]: ir::Expression::FunctionArgument local_table: &'temp mut FastHashMap, Declared>>>, const_typifier: &'temp mut Typifier, typifier: &'temp mut Typifier, layouter: &'temp mut proc::Layouter, function: &'out mut ir::Function, /// Stores the names of expressions that are assigned in `let` statement /// Also stores the spans of the names, for use in errors. named_expressions: &'out mut FastIndexMap, (String, Span)>, module: &'out mut ir::Module, /// Which `Expression`s in `self.naga_expressions` are const expressions, in /// the WGSL sense. /// /// According to the WGSL spec, a const expression must not refer to any /// `let` declarations, even if those declarations' initializers are /// themselves const expressions. So this tracker is not simply concerned /// with the form of the expressions; it is also tracking whether WGSL says /// we should consider them to be const. See the use of `force_non_const` in /// the code for lowering `let` bindings. local_expression_kind_tracker: &'temp mut proc::ExpressionKindTracker, global_expression_kind_tracker: &'temp mut proc::ExpressionKindTracker, } impl<'a, 'temp> StatementContext<'a, 'temp, '_> { const fn as_const<'t>( &'t mut self, block: &'t mut ir::Block, emitter: &'t mut proc::Emitter, ) -> ExpressionContext<'a, 't, 't> where 'temp: 't, { ExpressionContext { enable_extensions: self.enable_extensions, globals: self.globals, ast_expressions: self.ast_expressions, const_typifier: self.const_typifier, layouter: self.layouter, global_expression_kind_tracker: self.global_expression_kind_tracker, module: self.module, expr_type: ExpressionContextType::Constant(Some(LocalExpressionContext { local_table: self.local_table, function: self.function, block, emitter, typifier: self.typifier, local_expression_kind_tracker: self.local_expression_kind_tracker, })), } } const fn as_expression<'t>( &'t mut self, block: &'t mut ir::Block, emitter: &'t mut proc::Emitter, ) -> ExpressionContext<'a, 't, 't> where 'temp: 't, { ExpressionContext { enable_extensions: self.enable_extensions, globals: self.globals, ast_expressions: self.ast_expressions, const_typifier: self.const_typifier, layouter: self.layouter, global_expression_kind_tracker: self.global_expression_kind_tracker, module: self.module, expr_type: ExpressionContextType::Runtime(LocalExpressionContext { local_table: self.local_table, function: self.function, block, emitter, typifier: self.typifier, local_expression_kind_tracker: self.local_expression_kind_tracker, }), } } #[allow(dead_code)] const fn as_global(&mut self) -> GlobalContext<'a, '_, '_> { GlobalContext { enable_extensions: self.enable_extensions, ast_expressions: self.ast_expressions, globals: self.globals, module: self.module, const_typifier: self.const_typifier, layouter: self.layouter, global_expression_kind_tracker: self.global_expression_kind_tracker, } } fn invalid_assignment_type(&self, expr: Handle) -> InvalidAssignmentType { if let Some(&(_, span)) = self.named_expressions.get(&expr) { InvalidAssignmentType::ImmutableBinding(span) } else { match self.function.expressions[expr] { ir::Expression::Swizzle { .. } => InvalidAssignmentType::Swizzle, ir::Expression::Access { base, .. } => self.invalid_assignment_type(base), ir::Expression::AccessIndex { base, .. } => self.invalid_assignment_type(base), _ => InvalidAssignmentType::Other, } } } } pub struct LocalExpressionContext<'temp, 'out> { /// A map from [`ast::Local`] handles to the Naga expressions we've built for them. /// /// This is always [`StatementContext::local_table`] for the /// enclosing statement; see that documentation for details. local_table: &'temp FastHashMap, Declared>>>, function: &'out mut ir::Function, block: &'temp mut ir::Block, emitter: &'temp mut proc::Emitter, typifier: &'temp mut Typifier, /// Which `Expression`s in `self.naga_expressions` are const expressions, in /// the WGSL sense. /// /// See [`StatementContext::local_expression_kind_tracker`] for details. local_expression_kind_tracker: &'temp mut proc::ExpressionKindTracker, } /// The type of Naga IR expression we are lowering an [`ast::Expression`] to. pub enum ExpressionContextType<'temp, 'out> { /// We are lowering to an arbitrary runtime expression, to be /// included in a function's body. /// /// The given [`LocalExpressionContext`] holds information about local /// variables, arguments, and other definitions available only to runtime /// expressions, not constant or override expressions. Runtime(LocalExpressionContext<'temp, 'out>), /// We are lowering to a constant expression, to be included in the module's /// constant expression arena. /// /// Everything global constant expressions are allowed to refer to is /// available in the [`ExpressionContext`], but local constant expressions can /// also refer to other Constant(Option>), /// We are lowering to an override expression, to be included in the module's /// constant expression arena. /// /// Everything override expressions are allowed to refer to is /// available in the [`ExpressionContext`], so this variant /// carries no further information. Override, } /// State for lowering an [`ast::Expression`] to Naga IR. /// /// [`ExpressionContext`]s come in two kinds, distinguished by /// the value of the [`expr_type`] field: /// /// - A [`Runtime`] context contributes [`naga::Expression`]s to a [`naga::Function`]'s /// runtime expression arena. /// /// - A [`Constant`] context contributes [`naga::Expression`]s to a [`naga::Module`]'s /// constant expression arena. /// /// [`ExpressionContext`]s are constructed in restricted ways: /// /// - To get a [`Runtime`] [`ExpressionContext`], call /// [`StatementContext::as_expression`]. /// /// - To get a [`Constant`] [`ExpressionContext`], call /// [`GlobalContext::as_const`]. /// /// - You can demote a [`Runtime`] context to a [`Constant`] context /// by calling [`as_const`], but there's no way to go in the other /// direction, producing a runtime context from a constant one. This /// is because runtime expressions can refer to constant /// expressions, via [`Expression::Constant`], but constant /// expressions can't refer to a function's expressions. /// /// Not to be confused with `wgsl::parse::ExpressionContext`, which is /// for parsing the `ast::Expression` in the first place. /// /// [`expr_type`]: ExpressionContext::expr_type /// [`Runtime`]: ExpressionContextType::Runtime /// [`naga::Expression`]: ir::Expression /// [`naga::Function`]: ir::Function /// [`Constant`]: ExpressionContextType::Constant /// [`naga::Module`]: ir::Module /// [`as_const`]: ExpressionContext::as_const /// [`Expression::Constant`]: ir::Expression::Constant pub struct ExpressionContext<'source, 'temp, 'out> { enable_extensions: EnableExtensions, // WGSL AST values. ast_expressions: &'temp Arena>, // Naga IR values. /// The map from the names of module-scope declarations to the Naga IR /// `Handle`s we have built for them, owned by `Lowerer::lower`. globals: &'temp mut FastHashMap<&'source str, LoweredGlobalDecl>, /// The IR [`Module`] we're constructing. /// /// [`Module`]: ir::Module module: &'out mut ir::Module, /// Type judgments for [`module::global_expressions`]. /// /// [`module::global_expressions`]: ir::Module::global_expressions const_typifier: &'temp mut Typifier, layouter: &'temp mut proc::Layouter, global_expression_kind_tracker: &'temp mut proc::ExpressionKindTracker, /// Whether we are lowering a constant expression or a general /// runtime expression, and the data needed in each case. expr_type: ExpressionContextType<'temp, 'out>, } impl TypeContext for ExpressionContext<'_, '_, '_> { fn lookup_type(&self, handle: Handle) -> &ir::Type { &self.module.types[handle] } fn type_name(&self, handle: Handle) -> &str { self.module.types[handle] .name .as_deref() .unwrap_or("{anonymous type}") } fn write_override( &self, handle: Handle, out: &mut W, ) -> core::fmt::Result { match self.module.overrides[handle].name { Some(ref name) => out.write_str(name), None => write!(out, "{{anonymous override {handle:?}}}"), } } fn write_unnamed_struct( &self, _: &ir::TypeInner, _: &mut W, ) -> core::fmt::Result { unreachable!("the WGSL front end should always know the type name"); } } impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> { const fn is_runtime(&self) -> bool { match self.expr_type { ExpressionContextType::Runtime(_) => true, ExpressionContextType::Constant(_) | ExpressionContextType::Override => false, } } #[allow(dead_code)] const fn as_const(&mut self) -> ExpressionContext<'source, '_, '_> { ExpressionContext { enable_extensions: self.enable_extensions, globals: self.globals, ast_expressions: self.ast_expressions, const_typifier: self.const_typifier, layouter: self.layouter, module: self.module, expr_type: ExpressionContextType::Constant(match self.expr_type { ExpressionContextType::Runtime(ref mut local_expression_context) | ExpressionContextType::Constant(Some(ref mut local_expression_context)) => { Some(LocalExpressionContext { local_table: local_expression_context.local_table, function: local_expression_context.function, block: local_expression_context.block, emitter: local_expression_context.emitter, typifier: local_expression_context.typifier, local_expression_kind_tracker: local_expression_context .local_expression_kind_tracker, }) } ExpressionContextType::Constant(None) | ExpressionContextType::Override => None, }), global_expression_kind_tracker: self.global_expression_kind_tracker, } } const fn as_global(&mut self) -> GlobalContext<'source, '_, '_> { GlobalContext { enable_extensions: self.enable_extensions, ast_expressions: self.ast_expressions, globals: self.globals, module: self.module, const_typifier: self.const_typifier, layouter: self.layouter, global_expression_kind_tracker: self.global_expression_kind_tracker, } } const fn as_const_evaluator(&mut self) -> proc::ConstantEvaluator<'_> { match self.expr_type { ExpressionContextType::Runtime(ref mut rctx) => { proc::ConstantEvaluator::for_wgsl_function( self.module, &mut rctx.function.expressions, rctx.local_expression_kind_tracker, self.layouter, rctx.emitter, rctx.block, false, ) } ExpressionContextType::Constant(Some(ref mut rctx)) => { proc::ConstantEvaluator::for_wgsl_function( self.module, &mut rctx.function.expressions, rctx.local_expression_kind_tracker, self.layouter, rctx.emitter, rctx.block, true, ) } ExpressionContextType::Constant(None) => proc::ConstantEvaluator::for_wgsl_module( self.module, self.global_expression_kind_tracker, self.layouter, false, ), ExpressionContextType::Override => proc::ConstantEvaluator::for_wgsl_module( self.module, self.global_expression_kind_tracker, self.layouter, true, ), } } /// Return a wrapper around `value` suitable for formatting. /// /// Return a wrapper around `value` that implements /// [`core::fmt::Display`] in a form suitable for use in /// diagnostic messages. const fn as_diagnostic_display( &self, value: T, ) -> crate::common::DiagnosticDisplay<(T, proc::GlobalCtx<'_>)> { let ctx = self.module.to_ctx(); crate::common::DiagnosticDisplay((value, ctx)) } fn append_expression( &mut self, expr: ir::Expression, span: Span, ) -> Result<'source, Handle> { let mut eval = self.as_const_evaluator(); eval.try_eval_and_append(expr, span) .map_err(|e| Box::new(Error::ConstantEvaluatorError(e.into(), span))) } fn get_const_val>( &self, handle: Handle, ) -> core::result::Result { match self.expr_type { ExpressionContextType::Runtime(ref ctx) => { if !ctx.local_expression_kind_tracker.is_const(handle) { return Err(proc::ConstValueError::NonConst); } self.module .to_ctx() .get_const_val_from(handle, &ctx.function.expressions) } ExpressionContextType::Constant(Some(ref ctx)) => { assert!(ctx.local_expression_kind_tracker.is_const(handle)); self.module .to_ctx() .get_const_val_from(handle, &ctx.function.expressions) } ExpressionContextType::Constant(None) => self.module.to_ctx().get_const_val(handle), ExpressionContextType::Override => Err(proc::ConstValueError::NonConst), } } /// Return `true` if `handle` is a constant expression. fn is_const(&self, handle: Handle) -> bool { use ExpressionContextType as Ect; match self.expr_type { Ect::Runtime(ref ctx) | Ect::Constant(Some(ref ctx)) => { ctx.local_expression_kind_tracker.is_const(handle) } Ect::Constant(None) | Ect::Override => { self.global_expression_kind_tracker.is_const(handle) } } } fn get_expression_span(&self, handle: Handle) -> Span { match self.expr_type { ExpressionContextType::Runtime(ref ctx) | ExpressionContextType::Constant(Some(ref ctx)) => { ctx.function.expressions.get_span(handle) } ExpressionContextType::Constant(None) | ExpressionContextType::Override => { self.module.global_expressions.get_span(handle) } } } const fn typifier(&self) -> &Typifier { match self.expr_type { ExpressionContextType::Runtime(ref ctx) | ExpressionContextType::Constant(Some(ref ctx)) => ctx.typifier, ExpressionContextType::Constant(None) | ExpressionContextType::Override => { self.const_typifier } } } fn get(&self, handle: Handle) -> &crate::Expression { match self.expr_type { ExpressionContextType::Runtime(ref ctx) | ExpressionContextType::Constant(Some(ref ctx)) => &ctx.function.expressions[handle], ExpressionContextType::Constant(None) | ExpressionContextType::Override => { &self.module.global_expressions[handle] } } } fn local( &mut self, local: &Handle, span: Span, ) -> Result<'source, Typed>> { match self.expr_type { ExpressionContextType::Runtime(ref ctx) => Ok(ctx.local_table[local].runtime()), ExpressionContextType::Constant(Some(ref ctx)) => ctx.local_table[local] .const_time() .ok_or(Box::new(Error::UnexpectedOperationInConstContext(span))), _ => Err(Box::new(Error::UnexpectedOperationInConstContext(span))), } } fn runtime_expression_ctx( &mut self, span: Span, ) -> Result<'source, &mut LocalExpressionContext<'temp, 'out>> { match self.expr_type { ExpressionContextType::Runtime(ref mut ctx) => Ok(ctx), ExpressionContextType::Constant(_) | ExpressionContextType::Override => { Err(Box::new(Error::UnexpectedOperationInConstContext(span))) } } } fn with_nested_runtime_expression_ctx<'a, F, T>( &mut self, span: Span, f: F, ) -> Result<'source, (T, crate::Block)> where for<'t> F: FnOnce(&mut ExpressionContext<'source, 't, 't>) -> Result<'source, T>, { let mut block = crate::Block::new(); let rctx = match self.expr_type { ExpressionContextType::Runtime(ref mut rctx) => Ok(rctx), ExpressionContextType::Constant(_) | ExpressionContextType::Override => { Err(Error::UnexpectedOperationInConstContext(span)) } }?; rctx.block .extend(rctx.emitter.finish(&rctx.function.expressions)); rctx.emitter.start(&rctx.function.expressions); let nested_rctx = LocalExpressionContext { local_table: rctx.local_table, function: rctx.function, block: &mut block, emitter: rctx.emitter, typifier: rctx.typifier, local_expression_kind_tracker: rctx.local_expression_kind_tracker, }; let mut nested_ctx = ExpressionContext { enable_extensions: self.enable_extensions, expr_type: ExpressionContextType::Runtime(nested_rctx), ast_expressions: self.ast_expressions, globals: self.globals, module: self.module, const_typifier: self.const_typifier, layouter: self.layouter, global_expression_kind_tracker: self.global_expression_kind_tracker, }; let ret = f(&mut nested_ctx)?; block.extend(rctx.emitter.finish(&rctx.function.expressions)); rctx.emitter.start(&rctx.function.expressions); Ok((ret, block)) } fn gather_component( &mut self, expr: Handle, component_span: Span, gather_span: Span, ) -> Result<'source, ir::SwizzleComponent> { match self.expr_type { ExpressionContextType::Runtime(ref rctx) => { if !rctx.local_expression_kind_tracker.is_const(expr) { return Err(Box::new(Error::ExpectedConstExprConcreteIntegerScalar( component_span, ))); } let index = self .module .to_ctx() .get_const_val_from::(expr, &rctx.function.expressions) .map_err(|err| match err { proc::ConstValueError::NonConst | proc::ConstValueError::InvalidType => { Error::ExpectedConstExprConcreteIntegerScalar(component_span) } proc::ConstValueError::Negative => { Error::ExpectedNonNegative(component_span) } })?; ir::SwizzleComponent::XYZW .get(index as usize) .copied() .ok_or(Box::new(Error::InvalidGatherComponent(component_span))) } // This means a `gather` operation appeared in a constant expression. // This error refers to the `gather` itself, not its "component" argument. ExpressionContextType::Constant(_) | ExpressionContextType::Override => Err(Box::new( Error::UnexpectedOperationInConstContext(gather_span), )), } } /// Determine the type of `handle`, and add it to the module's arena. /// /// If you just need a `TypeInner` for `handle`'s type, use the /// [`resolve_inner!`] macro instead. This function /// should only be used when the type of `handle` needs to appear /// in the module's final `Arena`, for example, if you're /// creating a [`LocalVariable`] whose type is inferred from its /// initializer. /// /// [`LocalVariable`]: ir::LocalVariable fn register_type( &mut self, handle: Handle, ) -> Result<'source, Handle> { self.grow_types(handle)?; // This is equivalent to calling ExpressionContext::typifier(), // except that this lets the borrow checker see that it's okay // to also borrow self.module.types mutably below. let typifier = match self.expr_type { ExpressionContextType::Runtime(ref ctx) | ExpressionContextType::Constant(Some(ref ctx)) => ctx.typifier, ExpressionContextType::Constant(None) | ExpressionContextType::Override => { &*self.const_typifier } }; Ok(typifier.register_type(handle, &mut self.module.types)) } /// Resolve the types of all expressions up through `handle`. /// /// Ensure that [`self.typifier`] has a [`TypeResolution`] for /// every expression in `self.function.expressions`. /// /// This does not add types to any arena. The [`Typifier`] /// documentation explains the steps we take to avoid filling /// arenas with intermediate types. /// /// This function takes `&mut self`, so it can't conveniently /// return a shared reference to the resulting `TypeResolution`: /// the shared reference would extend the mutable borrow, and you /// wouldn't be able to use `self` for anything else. Instead, you /// should use [`register_type`] or one of [`resolve!`], /// [`resolve_inner!`] or [`resolve_inner_binary!`]. /// /// [`self.typifier`]: ExpressionContext::typifier /// [`TypeResolution`]: proc::TypeResolution /// [`register_type`]: Self::register_type /// [`Typifier`]: Typifier fn grow_types(&mut self, handle: Handle) -> Result<'source, &mut Self> { let empty_arena = Arena::new(); let resolve_ctx; let typifier; let expressions; match self.expr_type { ExpressionContextType::Runtime(ref mut ctx) | ExpressionContextType::Constant(Some(ref mut ctx)) => { resolve_ctx = proc::ResolveContext::with_locals( self.module, &ctx.function.local_variables, &ctx.function.arguments, ); typifier = &mut *ctx.typifier; expressions = &ctx.function.expressions; } ExpressionContextType::Constant(None) | ExpressionContextType::Override => { resolve_ctx = proc::ResolveContext::with_locals(self.module, &empty_arena, &[]); typifier = self.const_typifier; expressions = &self.module.global_expressions; } }; typifier .grow(handle, expressions, &resolve_ctx) .map_err(Error::InvalidResolve)?; Ok(self) } fn image_data( &mut self, image: Handle, span: Span, ) -> Result<'source, (ir::ImageClass, bool)> { match *resolve_inner!(self, image) { ir::TypeInner::Image { class, arrayed, .. } => Ok((class, arrayed)), _ => Err(Box::new(Error::BadTexture(span))), } } fn prepare_args<'b>( &mut self, args: &'b [Handle>], min_args: u32, span: Span, ) -> ArgumentContext<'b, 'source> { ArgumentContext { args: args.iter(), min_args, args_used: 0, total_args: args.len() as u32, span, } } /// Insert splats, if needed by the non-'*' operations. /// /// See the "Binary arithmetic expressions with mixed scalar and vector operands" /// table in the WebGPU Shading Language specification for relevant operators. /// /// Multiply is not handled here as backends are expected to handle vec*scalar /// operations, so inserting splats into the IR increases size needlessly. fn binary_op_splat( &mut self, op: ir::BinaryOperator, left: &mut Handle, right: &mut Handle, ) -> Result<'source, ()> { if matches!( op, ir::BinaryOperator::Add | ir::BinaryOperator::Subtract | ir::BinaryOperator::Divide | ir::BinaryOperator::Modulo ) { match resolve_inner_binary!(self, *left, *right) { (&ir::TypeInner::Vector { size, .. }, &ir::TypeInner::Scalar { .. }) => { *right = self.append_expression( ir::Expression::Splat { size, value: *right, }, self.get_expression_span(*right), )?; } (&ir::TypeInner::Scalar { .. }, &ir::TypeInner::Vector { size, .. }) => { *left = self.append_expression( ir::Expression::Splat { size, value: *left }, self.get_expression_span(*left), )?; } _ => {} } } Ok(()) } /// Add a single expression to the expression table that is not covered by `self.emitter`. /// /// This is useful for `CallResult` and `AtomicResult` expressions, which should not be covered by /// `Emit` statements. fn interrupt_emitter( &mut self, expression: ir::Expression, span: Span, ) -> Result<'source, Handle> { match self.expr_type { ExpressionContextType::Runtime(ref mut rctx) | ExpressionContextType::Constant(Some(ref mut rctx)) => { rctx.block .extend(rctx.emitter.finish(&rctx.function.expressions)); } ExpressionContextType::Constant(None) | ExpressionContextType::Override => {} } let result = self.append_expression(expression, span); match self.expr_type { ExpressionContextType::Runtime(ref mut rctx) | ExpressionContextType::Constant(Some(ref mut rctx)) => { rctx.emitter.start(&rctx.function.expressions); } ExpressionContextType::Constant(None) | ExpressionContextType::Override => {} } result } /// Apply the WGSL Load Rule to `expr`. /// /// If `expr` is has type `ref`, perform a load to produce a value of type /// `T`. Otherwise, return `expr` unchanged. fn apply_load_rule( &mut self, expr: Typed>, ) -> Result<'source, Handle> { match expr { Typed::Reference(pointer) => { let load = ir::Expression::Load { pointer }; let span = self.get_expression_span(pointer); self.append_expression(load, span) } Typed::Plain(handle) => Ok(handle), } } fn ensure_type_exists(&mut self, inner: ir::TypeInner) -> Handle { self.as_global().ensure_type_exists(None, inner) } /// Check that `expr` is an identifier resolving to a predeclared enumerant. /// /// The identifier must not have any template parameters. /// /// Return the name of the identifier, together with its span. /// /// Actually, this only checks that the identifier refers to some /// predeclared object, not necessarily an enumerant. This should be good /// enough, since the caller is going to compare the name against some list /// of permitted enumerants anyway. fn enumerant( &self, expr: Handle>, ) -> Result<'source, (&'source str, Span)> { let span = self.ast_expressions.get_span(expr); let expr = &self.ast_expressions[expr]; let ast::Expression::Ident(ref ident) = *expr else { return Err(Box::new(Error::UnexpectedExprForEnumerant(span))); }; let ast::TemplateElaboratedIdent { ident: ast::IdentExpr::Unresolved(name), ref template_list, .. } = *ident else { return Err(Box::new(Error::UnexpectedIdentForEnumerant(span))); }; if self.globals.get(name).is_some() { return Err(Box::new(Error::UnexpectedIdentForEnumerant(span))); } if !template_list.is_empty() { return Err(Box::new(Error::UnexpectedTemplate(span))); } Ok((name, span)) } fn var_address_space( &self, template_list: &[Handle>], ) -> Result<'source, ir::AddressSpace> { let mut tl = TemplateListIter::new(Span::UNDEFINED, template_list); let mut address_space = tl.maybe_address_space(self)?; if let Some(ref mut address_space) = address_space { tl.maybe_access_mode(address_space, self)?; } tl.finish(self)?; Ok(address_space.unwrap_or(ir::AddressSpace::Handle)) } } struct ArgumentContext<'ctx, 'source> { args: core::slice::Iter<'ctx, Handle>>, min_args: u32, args_used: u32, total_args: u32, span: Span, } impl<'source> ArgumentContext<'_, 'source> { pub fn finish(self) -> Result<'source, ()> { if self.args.len() == 0 { Ok(()) } else { Err(Box::new(Error::WrongArgumentCount { found: self.total_args, expected: self.min_args..self.args_used + 1, span: self.span, })) } } pub fn next(&mut self) -> Result<'source, Handle>> { match self.args.next().copied() { Some(arg) => { self.args_used += 1; Ok(arg) } None => Err(Box::new(Error::WrongArgumentCount { found: self.total_args, expected: self.min_args..self.args_used + 1, span: self.span, })), } } } #[derive(Debug, Copy, Clone)] enum Declared { /// Value declared as const Const(T), /// Value declared as non-const Runtime(T), } impl Declared { fn runtime(self) -> T { match self { Declared::Const(t) | Declared::Runtime(t) => t, } } fn const_time(self) -> Option { match self { Declared::Const(t) => Some(t), Declared::Runtime(_) => None, } } } /// WGSL type annotations on expressions, types, values, etc. /// /// Naga and WGSL types are very close, but Naga lacks WGSL's `ref` types, which /// we need to know to apply the Load Rule. This enum carries some WGSL or Naga /// datum along with enough information to determine its corresponding WGSL /// type. /// /// The `T` type parameter can be any expression-like thing: /// /// - `Typed>` can represent a full WGSL type. For example, /// given some Naga `Pointer` type `ptr`, a WGSL reference type is a /// `Typed::Reference(ptr)` whereas a WGSL pointer type is a /// `Typed::Plain(ptr)`. /// /// - `Typed` or `Typed>` can /// represent references similarly. /// /// Use the `map` and `try_map` methods to convert from one expression /// representation to another. /// /// [`Expression`]: ir::Expression #[derive(Debug, Copy, Clone)] enum Typed { /// A WGSL reference. Reference(T), /// A WGSL plain type. Plain(T), } impl Typed { fn map(self, mut f: impl FnMut(T) -> U) -> Typed { match self { Self::Reference(v) => Typed::Reference(f(v)), Self::Plain(v) => Typed::Plain(f(v)), } } fn try_map( self, mut f: impl FnMut(T) -> core::result::Result, ) -> core::result::Result, E> { Ok(match self { Self::Reference(expr) => Typed::Reference(f(expr)?), Self::Plain(expr) => Typed::Plain(f(expr)?), }) } fn ref_or(self, error: E) -> core::result::Result { match self { Self::Reference(v) => Ok(v), Self::Plain(_) => Err(error), } } } /// A single vector component or swizzle. /// /// This represents the things that can appear after the `.` in a vector access /// expression: either a single component name, or a series of them, /// representing a swizzle. enum Components { Single(u32), Swizzle { size: ir::VectorSize, pattern: [ir::SwizzleComponent; 4], }, } impl Components { const fn letter_component(letter: char) -> Option { use ir::SwizzleComponent as Sc; match letter { 'x' | 'r' => Some(Sc::X), 'y' | 'g' => Some(Sc::Y), 'z' | 'b' => Some(Sc::Z), 'w' | 'a' => Some(Sc::W), _ => None, } } fn single_component(name: &str, name_span: Span) -> Result<'_, u32> { let ch = name.chars().next().ok_or(Error::BadAccessor(name_span))?; match Self::letter_component(ch) { Some(sc) => Ok(sc as u32), None => Err(Box::new(Error::BadAccessor(name_span))), } } /// Construct a `Components` value from a 'member' name, like `"wzy"` or `"x"`. /// /// Use `name_span` for reporting errors in parsing the component string. fn new(name: &str, name_span: Span) -> Result<'_, Self> { let size = match name.len() { 1 => return Ok(Components::Single(Self::single_component(name, name_span)?)), 2 => ir::VectorSize::Bi, 3 => ir::VectorSize::Tri, 4 => ir::VectorSize::Quad, _ => return Err(Box::new(Error::BadAccessor(name_span))), }; let mut pattern = [ir::SwizzleComponent::X; 4]; for (comp, ch) in pattern.iter_mut().zip(name.chars()) { *comp = Self::letter_component(ch).ok_or(Error::BadAccessor(name_span))?; } if name.chars().all(|c| matches!(c, 'x' | 'y' | 'z' | 'w')) || name.chars().all(|c| matches!(c, 'r' | 'g' | 'b' | 'a')) { Ok(Components::Swizzle { size, pattern }) } else { Err(Box::new(Error::BadAccessor(name_span))) } } } /// An `ast::GlobalDecl` for which we have built the Naga IR equivalent. enum LoweredGlobalDecl { Function { handle: Handle, must_use: bool, }, Var(Handle), Const(Handle), Override(Handle), Type(Handle), EntryPoint(usize), } enum Texture { Gather, GatherCompare, Sample, SampleBias, SampleCompare, SampleCompareLevel, SampleGrad, SampleLevel, SampleBaseClampToEdge, } impl Texture { pub fn map(word: &str) -> Option { Some(match word { "textureGather" => Self::Gather, "textureGatherCompare" => Self::GatherCompare, "textureSample" => Self::Sample, "textureSampleBias" => Self::SampleBias, "textureSampleCompare" => Self::SampleCompare, "textureSampleCompareLevel" => Self::SampleCompareLevel, "textureSampleGrad" => Self::SampleGrad, "textureSampleLevel" => Self::SampleLevel, "textureSampleBaseClampToEdge" => Self::SampleBaseClampToEdge, _ => return None, }) } pub const fn min_argument_count(&self) -> u32 { match *self { Self::Gather => 3, Self::GatherCompare => 4, Self::Sample => 3, Self::SampleBias => 5, Self::SampleCompare => 5, Self::SampleCompareLevel => 5, Self::SampleGrad => 6, Self::SampleLevel => 5, Self::SampleBaseClampToEdge => 3, } } } enum SubgroupGather { BroadcastFirst, Broadcast, Shuffle, ShuffleDown, ShuffleUp, ShuffleXor, QuadBroadcast, } impl SubgroupGather { pub fn map(word: &str) -> Option { Some(match word { "subgroupBroadcastFirst" => Self::BroadcastFirst, "subgroupBroadcast" => Self::Broadcast, "subgroupShuffle" => Self::Shuffle, "subgroupShuffleDown" => Self::ShuffleDown, "subgroupShuffleUp" => Self::ShuffleUp, "subgroupShuffleXor" => Self::ShuffleXor, "quadBroadcast" => Self::QuadBroadcast, _ => return None, }) } } /// Whether a declaration accepts abstract types, or concretizes. enum AbstractRule { /// This declaration concretizes its initialization expression. Concretize, /// This declaration can accept initializers with abstract types. Allow, } /// Whether `@must_use` applies to a call expression. #[derive(Debug, Copy, Clone)] enum MustUse { Yes, No, } impl From for MustUse { fn from(value: bool) -> Self { if value { MustUse::Yes } else { MustUse::No } } } pub struct Lowerer<'source, 'temp> { index: &'temp Index<'source>, } impl<'source, 'temp> Lowerer<'source, 'temp> { pub const fn new(index: &'temp Index<'source>) -> Self { Self { index } } pub fn lower(&mut self, tu: ast::TranslationUnit<'source>) -> Result<'source, ir::Module> { let mut module = ir::Module { diagnostic_filters: tu.diagnostic_filters, diagnostic_filter_leaf: tu.diagnostic_filter_leaf, ..Default::default() }; let mut ctx = GlobalContext { enable_extensions: tu.enable_extensions, ast_expressions: &tu.expressions, globals: &mut FastHashMap::default(), module: &mut module, const_typifier: &mut Typifier::new(), layouter: &mut proc::Layouter::default(), global_expression_kind_tracker: &mut proc::ExpressionKindTracker::new(), }; if !tu.doc_comments.is_empty() { ctx.module.get_or_insert_default_doc_comments().module = tu.doc_comments.iter().map(|s| s.to_string()).collect(); } for decl_handle in self.index.visit_ordered() { let span = tu.decls.get_span(decl_handle); let decl = &tu.decls[decl_handle]; match decl.kind { ast::GlobalDeclKind::Fn(ref f) => { let lowered_decl = self.function(f, span, &mut ctx)?; if !f.doc_comments.is_empty() { match lowered_decl { LoweredGlobalDecl::Function { handle, .. } => { ctx.module .get_or_insert_default_doc_comments() .functions .insert( handle, f.doc_comments.iter().map(|s| s.to_string()).collect(), ); } LoweredGlobalDecl::EntryPoint(index) => { ctx.module .get_or_insert_default_doc_comments() .entry_points .insert( index, f.doc_comments.iter().map(|s| s.to_string()).collect(), ); } _ => {} } } ctx.globals.insert(f.name.name, lowered_decl); } ast::GlobalDeclKind::Var(ref v) => { let explicit_ty = v.ty.as_ref() .map(|ast| self.resolve_ast_type(ast, &mut ctx.as_const())) .transpose()?; let (ty, initializer) = self.type_and_init( v.name, v.init, explicit_ty, AbstractRule::Concretize, &mut ctx.as_override(), )?; let binding = if let Some(ref binding) = v.binding { Some(ir::ResourceBinding { group: self.const_u32(binding.group, &mut ctx.as_const())?.0, binding: self.const_u32(binding.binding, &mut ctx.as_const())?.0, }) } else { None }; let space = ctx.as_const().var_address_space(&v.template_list)?; let handle = ctx.module.global_variables.append( ir::GlobalVariable { name: Some(v.name.name.to_string()), space, binding, ty, init: initializer, memory_decorations: v.memory_decorations, }, span, ); if !v.doc_comments.is_empty() { ctx.module .get_or_insert_default_doc_comments() .global_variables .insert( handle, v.doc_comments.iter().map(|s| s.to_string()).collect(), ); } ctx.globals .insert(v.name.name, LoweredGlobalDecl::Var(handle)); } ast::GlobalDeclKind::Const(ref c) => { let mut ectx = ctx.as_const(); let explicit_ty = c.ty.as_ref() .map(|ast| self.resolve_ast_type(ast, &mut ectx)) .transpose()?; let (ty, init) = self.type_and_init( c.name, Some(c.init), explicit_ty, AbstractRule::Allow, &mut ectx, )?; let init = init.expect("Global const must have init"); let handle = ctx.module.constants.append( ir::Constant { name: Some(c.name.name.to_string()), ty, init, }, span, ); ctx.globals .insert(c.name.name, LoweredGlobalDecl::Const(handle)); if !c.doc_comments.is_empty() { ctx.module .get_or_insert_default_doc_comments() .constants .insert( handle, c.doc_comments.iter().map(|s| s.to_string()).collect(), ); } } ast::GlobalDeclKind::Override(ref o) => { let explicit_ty = o.ty.as_ref() .map(|ast| self.resolve_ast_type(ast, &mut ctx.as_const())) .transpose()?; let mut ectx = ctx.as_override(); let (ty, init) = self.type_and_init( o.name, o.init, explicit_ty, AbstractRule::Concretize, &mut ectx, )?; let id = o.id.map(|id| self.const_u32(id, &mut ctx.as_const())) .transpose()?; let id = if let Some((id, id_span)) = id { Some( u16::try_from(id) .map_err(|_| Error::PipelineConstantIDValue(id_span))?, ) } else { None }; let handle = ctx.module.overrides.append( ir::Override { name: Some(o.name.name.to_string()), id, ty, init, }, span, ); ctx.globals .insert(o.name.name, LoweredGlobalDecl::Override(handle)); } ast::GlobalDeclKind::Struct(ref s) => { let handle = self.r#struct(s, span, &mut ctx)?; ctx.globals .insert(s.name.name, LoweredGlobalDecl::Type(handle)); if !s.doc_comments.is_empty() { ctx.module .get_or_insert_default_doc_comments() .types .insert( handle, s.doc_comments.iter().map(|s| s.to_string()).collect(), ); } } ast::GlobalDeclKind::Type(ref alias) => { let ty = self.resolve_named_ast_type( &alias.ty, alias.name.name.to_string(), &mut ctx.as_const(), )?; ctx.globals .insert(alias.name.name, LoweredGlobalDecl::Type(ty)); } ast::GlobalDeclKind::ConstAssert(condition) => { let condition = self.expression(condition, &mut ctx.as_const())?; let span = ctx.module.global_expressions.get_span(condition); match ctx .module .to_ctx() .get_const_val_from(condition, &ctx.module.global_expressions) { Ok(true) => Ok(()), Ok(false) => Err(Error::ConstAssertFailed(span)), Err(proc::ConstValueError::NonConst | proc::ConstValueError::Negative) => { unreachable!() } Err(proc::ConstValueError::InvalidType) => Err(Error::NotBool(span)), }?; } } } // Constant evaluation may leave abstract-typed literals and // compositions in expression arenas, so we need to compact the module // to remove unused expressions and types. crate::compact::compact(&mut module, KeepUnused::Yes); Ok(module) } /// Obtain (inferred) type and initializer after automatic conversion fn type_and_init( &mut self, name: ast::Ident<'source>, init: Option>>, explicit_ty: Option>, abstract_rule: AbstractRule, ectx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, (Handle, Option>)> { let ty; let initializer; match (init, explicit_ty) { (Some(init), Some(explicit_ty)) => { let init = self.expression_for_abstract(init, ectx)?; let ty_res = proc::TypeResolution::Handle(explicit_ty); let init = ectx .try_automatic_conversions(init, &ty_res, name.span) .map_err(|error| match *error { Error::AutoConversion(e) => Box::new(Error::InitializationTypeMismatch { name: name.span, expected: e.dest_type, got: e.source_type, }), _ => error, })?; let init_ty = ectx.register_type(init)?; if !ectx.module.compare_types( &proc::TypeResolution::Handle(explicit_ty), &proc::TypeResolution::Handle(init_ty), ) { return Err(Box::new(Error::InitializationTypeMismatch { name: name.span, expected: ectx.type_to_string(explicit_ty), got: ectx.type_to_string(init_ty), })); } ty = explicit_ty; initializer = Some(init); } (Some(init), None) => { let mut init = self.expression_for_abstract(init, ectx)?; if let AbstractRule::Concretize = abstract_rule { init = ectx.concretize(init)?; } ty = ectx.register_type(init)?; initializer = Some(init); } (None, Some(explicit_ty)) => { ty = explicit_ty; initializer = None; } (None, None) => return Err(Box::new(Error::DeclMissingTypeAndInit(name.span))), } Ok((ty, initializer)) } fn function( &mut self, f: &ast::Function<'source>, span: Span, ctx: &mut GlobalContext<'source, '_, '_>, ) -> Result<'source, LoweredGlobalDecl> { let mut local_table = FastHashMap::default(); let mut expressions = Arena::new(); let mut named_expressions = FastIndexMap::default(); let mut local_expression_kind_tracker = proc::ExpressionKindTracker::new(); let arguments = f .arguments .iter() .enumerate() .map(|(i, arg)| -> Result<'_, _> { let ty = self.resolve_ast_type(&arg.ty, &mut ctx.as_const())?; let expr = expressions.append(ir::Expression::FunctionArgument(i as u32), arg.name.span); local_table.insert(arg.handle, Declared::Runtime(Typed::Plain(expr))); named_expressions.insert(expr, (arg.name.name.to_string(), arg.name.span)); local_expression_kind_tracker.insert(expr, proc::ExpressionKind::Runtime); Ok(ir::FunctionArgument { name: Some(arg.name.name.to_string()), ty, binding: self.binding(&arg.binding, ty, ctx)?, }) }) .collect::>>()?; let result = f .result .as_ref() .map(|res| -> Result<'_, _> { let ty = self.resolve_ast_type(&res.ty, &mut ctx.as_const())?; Ok(ir::FunctionResult { ty, binding: self.binding(&res.binding, ty, ctx)?, }) }) .transpose()?; let mut function = ir::Function { name: Some(f.name.name.to_string()), arguments, result, local_variables: Arena::new(), expressions, named_expressions: crate::NamedExpressions::default(), body: ir::Block::default(), diagnostic_filter_leaf: f.diagnostic_filter_leaf, }; let mut typifier = Typifier::default(); let mut stmt_ctx = StatementContext { enable_extensions: ctx.enable_extensions, local_table: &mut local_table, globals: ctx.globals, ast_expressions: ctx.ast_expressions, const_typifier: ctx.const_typifier, typifier: &mut typifier, layouter: ctx.layouter, function: &mut function, named_expressions: &mut named_expressions, module: ctx.module, local_expression_kind_tracker: &mut local_expression_kind_tracker, global_expression_kind_tracker: ctx.global_expression_kind_tracker, }; let mut body = self.block(&f.body, false, &mut stmt_ctx)?; proc::ensure_block_returns(&mut body); function.body = body; function.named_expressions = named_expressions .into_iter() .map(|(key, (name, _))| (key, name)) .collect(); if let Some(ref entry) = f.entry_point { let (workgroup_size, workgroup_size_overrides) = if let Some(workgroup_size) = entry.workgroup_size { // TODO: replace with try_map once stabilized let mut workgroup_size_out = [1; 3]; let mut workgroup_size_overrides_out = [None; 3]; for (i, size) in workgroup_size.into_iter().enumerate() { if let Some(size_expr) = size { match self.const_u32(size_expr, &mut ctx.as_const()) { Ok(value) => { workgroup_size_out[i] = value.0; } Err(err) => { if let Error::ConstantEvaluatorError(ref ty, _) = *err { match **ty { proc::ConstantEvaluatorError::OverrideExpr => { workgroup_size_overrides_out[i] = Some(self.workgroup_size_override( size_expr, &mut ctx.as_override(), )?); } _ => { return Err(err); } } } else { return Err(err); } } } } } if workgroup_size_overrides_out.iter().all(|x| x.is_none()) { (workgroup_size_out, None) } else { (workgroup_size_out, Some(workgroup_size_overrides_out)) } } else { ([0; 3], None) }; let mesh_info = if let Some((var_name, var_span)) = entry.mesh_output_variable { let var = match ctx.globals.get(var_name) { Some(&LoweredGlobalDecl::Var(handle)) => handle, Some(_) => { return Err(Box::new(Error::ExpectedGlobalVariable { name_span: var_span, })) } None => return Err(Box::new(Error::UnknownIdent(var_span, var_name))), }; let mut info = ctx.module.analyze_mesh_shader_info(var); if let Some(h) = info.1[0] { info.0.max_vertices_override = Some( ctx.module .global_expressions .append(crate::Expression::Override(h), Span::UNDEFINED), ); } if let Some(h) = info.1[1] { info.0.max_primitives_override = Some( ctx.module .global_expressions .append(crate::Expression::Override(h), Span::UNDEFINED), ); } Some(info.0) } else { None }; let task_payload = if let Some((var_name, var_span)) = entry.task_payload { Some(match ctx.globals.get(var_name) { Some(&LoweredGlobalDecl::Var(handle)) => handle, Some(_) => { return Err(Box::new(Error::ExpectedGlobalVariable { name_span: var_span, })) } None => return Err(Box::new(Error::UnknownIdent(var_span, var_name))), }) } else { None }; let incoming_ray_payload = if let Some((var_name, var_span)) = entry.ray_incoming_payload { Some(match ctx.globals.get(var_name) { Some(&LoweredGlobalDecl::Var(handle)) => handle, Some(_) => { return Err(Box::new(Error::ExpectedGlobalVariable { name_span: var_span, })) } None => return Err(Box::new(Error::UnknownIdent(var_span, var_name))), }) } else { None }; ctx.module.entry_points.push(ir::EntryPoint { name: f.name.name.to_string(), stage: entry.stage, early_depth_test: entry.early_depth_test, workgroup_size, workgroup_size_overrides, function, mesh_info, task_payload, incoming_ray_payload, }); Ok(LoweredGlobalDecl::EntryPoint( ctx.module.entry_points.len() - 1, )) } else { let handle = ctx.module.functions.append(function, span); Ok(LoweredGlobalDecl::Function { handle, must_use: f.result.as_ref().is_some_and(|res| res.must_use), }) } } fn workgroup_size_override( &mut self, size_expr: Handle>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Handle> { let span = ctx.ast_expressions.get_span(size_expr); let expr = self.expression(size_expr, ctx)?; match resolve_inner!(ctx, expr).scalar_kind().ok_or(0) { Ok(ir::ScalarKind::Sint) | Ok(ir::ScalarKind::Uint) => Ok(expr), _ => Err(Box::new(Error::ExpectedConstExprConcreteIntegerScalar( span, ))), } } fn block( &mut self, b: &ast::Block<'source>, is_inside_loop: bool, ctx: &mut StatementContext<'source, '_, '_>, ) -> Result<'source, ir::Block> { let mut block = ir::Block::default(); for stmt in b.stmts.iter() { self.statement(stmt, &mut block, is_inside_loop, ctx)?; } Ok(block) } fn statement( &mut self, stmt: &ast::Statement<'source>, block: &mut ir::Block, is_inside_loop: bool, ctx: &mut StatementContext<'source, '_, '_>, ) -> Result<'source, ()> { let out = match stmt.kind { ast::StatementKind::Block(ref block) => { let block = self.block(block, is_inside_loop, ctx)?; ir::Statement::Block(block) } ast::StatementKind::LocalDecl(ref decl) => match *decl { ast::LocalDecl::Let(ref l) => { let mut emitter = proc::Emitter::default(); emitter.start(&ctx.function.expressions); let explicit_ty = l .ty .as_ref() .map(|ty| self.resolve_ast_type(ty, &mut ctx.as_const(block, &mut emitter))) .transpose()?; let mut ectx = ctx.as_expression(block, &mut emitter); let (ty, initializer) = self.type_and_init( l.name, Some(l.init), explicit_ty, AbstractRule::Concretize, &mut ectx, )?; // We have this special check here for `let` declarations because the // validator doesn't check them (they are comingled with other things in // `named_expressions`; see ). // The check could go in `type_and_init`, but then we'd have to // distinguish whether override-sized is allowed. The error ought to use // the type's span, but `module.types.get_span(ty)` is `Span::UNDEFINED` // (see ). if ctx.module.types[ty] .inner .is_dynamically_sized(&ctx.module.types) { return Err(Box::new(Error::TypeNotConstructible(l.name.span))); } // We passed `Some()` to `type_and_init`, so we // will get a lowered initializer expression back. let initializer = initializer.expect("type_and_init did not return an initializer"); // The WGSL spec says that any expression that refers to a // `let`-bound variable is not a const expression. This // affects when errors must be reported, so we can't even // treat suitable `let` bindings as constant as an // optimization. ctx.local_expression_kind_tracker .force_non_const(initializer); block.extend(emitter.finish(&ctx.function.expressions)); ctx.local_table .insert(l.handle, Declared::Runtime(Typed::Plain(initializer))); ctx.named_expressions .insert(initializer, (l.name.name.to_string(), l.name.span)); return Ok(()); } ast::LocalDecl::Var(ref v) => { let mut emitter = proc::Emitter::default(); emitter.start(&ctx.function.expressions); let explicit_ty = v.ty.as_ref() .map(|ast| { self.resolve_ast_type(ast, &mut ctx.as_const(block, &mut emitter)) }) .transpose()?; let mut ectx = ctx.as_expression(block, &mut emitter); let (ty, initializer) = self.type_and_init( v.name, v.init, explicit_ty, AbstractRule::Concretize, &mut ectx, )?; let (const_initializer, initializer) = { match initializer { Some(init) => { // It's not correct to hoist the initializer up // to the top of the function if: // - the initialization is inside a loop, and should // take place on every iteration, or // - the initialization is not a constant // expression, so its value depends on the // state at the point of initialization. if is_inside_loop || !ctx.local_expression_kind_tracker.is_const_or_override(init) { (None, Some(init)) } else { (Some(init), None) } } None => (None, None), } }; let var = ctx.function.local_variables.append( ir::LocalVariable { name: Some(v.name.name.to_string()), ty, init: const_initializer, }, stmt.span, ); let handle = ctx .as_expression(block, &mut emitter) .interrupt_emitter(ir::Expression::LocalVariable(var), Span::UNDEFINED)?; block.extend(emitter.finish(&ctx.function.expressions)); ctx.local_table .insert(v.handle, Declared::Runtime(Typed::Reference(handle))); match initializer { Some(initializer) => ir::Statement::Store { pointer: handle, value: initializer, }, None => return Ok(()), } } ast::LocalDecl::Const(ref c) => { let mut emitter = proc::Emitter::default(); emitter.start(&ctx.function.expressions); let ectx = &mut ctx.as_const(block, &mut emitter); let explicit_ty = c.ty.as_ref() .map(|ast| self.resolve_ast_type(ast, &mut ectx.as_const())) .transpose()?; let (_ty, init) = self.type_and_init( c.name, Some(c.init), explicit_ty, AbstractRule::Allow, &mut ectx.as_const(), )?; let init = init.expect("Local const must have init"); block.extend(emitter.finish(&ctx.function.expressions)); ctx.local_table .insert(c.handle, Declared::Const(Typed::Plain(init))); return Ok(()); } }, ast::StatementKind::If { condition, ref accept, ref reject, } => { let mut emitter = proc::Emitter::default(); emitter.start(&ctx.function.expressions); let condition = self.expression(condition, &mut ctx.as_expression(block, &mut emitter))?; block.extend(emitter.finish(&ctx.function.expressions)); let accept = self.block(accept, is_inside_loop, ctx)?; let reject = self.block(reject, is_inside_loop, ctx)?; ir::Statement::If { condition, accept, reject, } } ast::StatementKind::Switch { selector, ref cases, } => { let mut emitter = proc::Emitter::default(); emitter.start(&ctx.function.expressions); let mut ectx = ctx.as_expression(block, &mut emitter); // Determine the scalar type of the selector and case expressions, find the // consensus type for automatic conversion, then convert them. let (mut exprs, spans) = core::iter::once(selector) .chain(cases.iter().filter_map(|case| match case.value { ast::SwitchValue::Expr(expr) => Some(expr), ast::SwitchValue::Default => None, })) .enumerate() .map(|(i, expr)| { let span = ectx.ast_expressions.get_span(expr); let expr = self.expression_for_abstract(expr, &mut ectx)?; let ty = resolve_inner!(ectx, expr); match *ty { ir::TypeInner::Scalar( ir::Scalar::I32 | ir::Scalar::U32 | ir::Scalar::ABSTRACT_INT, ) => Ok((expr, span)), _ => match i { 0 => Err(Box::new(Error::InvalidSwitchSelector { span })), _ => Err(Box::new(Error::InvalidSwitchCase { span })), }, } }) .collect::, Vec<_>)>>()?; let mut consensus = ectx.automatic_conversion_consensus(None, &exprs) .map_err(|span_idx| Error::SwitchCaseTypeMismatch { span: spans[span_idx], })?; // Concretize to I32 if the selector and all cases were abstract if consensus == ir::Scalar::ABSTRACT_INT { consensus = ir::Scalar::I32; } for expr in &mut exprs { ectx.convert_to_leaf_scalar(expr, consensus)?; } block.extend(emitter.finish(&ctx.function.expressions)); let mut exprs = exprs.into_iter(); let selector = exprs .next() .expect("First element should be selector expression"); let cases = cases .iter() .map(|case| { Ok(ir::SwitchCase { value: match case.value { ast::SwitchValue::Expr(expr) => { let span = ctx.ast_expressions.get_span(expr); let expr = exprs.next().expect( "Should yield expression for each SwitchValue::Expr case", ); match ctx .module .to_ctx() .get_const_val_from(expr, &ctx.function.expressions) { Ok(ir::Literal::I32(value)) => ir::SwitchValue::I32(value), Ok(ir::Literal::U32(value)) => ir::SwitchValue::U32(value), _ => { return Err(Box::new(Error::InvalidSwitchCase { span, })); } } } ast::SwitchValue::Default => ir::SwitchValue::Default, }, body: self.block(&case.body, is_inside_loop, ctx)?, fall_through: case.fall_through, }) }) .collect::>()?; ir::Statement::Switch { selector, cases } } ast::StatementKind::Loop { ref body, ref continuing, break_if, } => { let body = self.block(body, true, ctx)?; let mut continuing = self.block(continuing, true, ctx)?; let mut emitter = proc::Emitter::default(); emitter.start(&ctx.function.expressions); let break_if = break_if .map(|expr| { self.expression(expr, &mut ctx.as_expression(&mut continuing, &mut emitter)) }) .transpose()?; continuing.extend(emitter.finish(&ctx.function.expressions)); ir::Statement::Loop { body, continuing, break_if, } } ast::StatementKind::Break => ir::Statement::Break, ast::StatementKind::Continue => ir::Statement::Continue, ast::StatementKind::Return { value: ast_value } => { let mut emitter = proc::Emitter::default(); emitter.start(&ctx.function.expressions); let value; if let Some(ast_expr) = ast_value { let result_ty = ctx.function.result.as_ref().map(|r| r.ty); let mut ectx = ctx.as_expression(block, &mut emitter); let expr = self.expression_for_abstract(ast_expr, &mut ectx)?; if let Some(result_ty) = result_ty { let mut ectx = ctx.as_expression(block, &mut emitter); let resolution = proc::TypeResolution::Handle(result_ty); let converted = ectx.try_automatic_conversions(expr, &resolution, Span::default())?; value = Some(converted); } else { value = Some(expr); } } else { value = None; } block.extend(emitter.finish(&ctx.function.expressions)); ir::Statement::Return { value } } ast::StatementKind::Kill => ir::Statement::Kill, ast::StatementKind::Call(ref call_phrase) => { let mut emitter = proc::Emitter::default(); emitter.start(&ctx.function.expressions); let _ = self.call( call_phrase, stmt.span, &mut ctx.as_expression(block, &mut emitter), true, )?; block.extend(emitter.finish(&ctx.function.expressions)); return Ok(()); } ast::StatementKind::Assign { target: ast_target, op, value, } => { let mut emitter = proc::Emitter::default(); emitter.start(&ctx.function.expressions); let target_span = ctx.ast_expressions.get_span(ast_target); let mut ectx = ctx.as_expression(block, &mut emitter); let target = self.expression_for_reference(ast_target, &mut ectx)?; let target_handle = match target { Typed::Reference(handle) => handle, Typed::Plain(handle) => { let ty = ctx.invalid_assignment_type(handle); return Err(Box::new(Error::InvalidAssignment { span: target_span, ty, })); } }; // Usually the value needs to be converted to match the type of // the memory view you're assigning it to. The bit shift // operators are exceptions, in that the right operand is always // a `u32` or `vecN`. let target_scalar = match op { Some(ir::BinaryOperator::ShiftLeft | ir::BinaryOperator::ShiftRight) => { Some(ir::Scalar::U32) } _ => resolve_inner!(ectx, target_handle) .pointer_automatically_convertible_scalar(&ectx.module.types), }; // Need to emit the LHS _before_ the RHS so that it is evaluated first. let op_assign = if let Some(op) = op { Some((op, ectx.apply_load_rule(target)?)) } else { None }; let value = self.expression_for_abstract(value, &mut ectx)?; let mut value = match target_scalar { Some(target_scalar) => ectx.try_automatic_conversion_for_leaf_scalar( value, target_scalar, target_span, )?, None => value, }; let value = match op_assign { Some((op, mut left)) => { ectx.binary_op_splat(op, &mut left, &mut value)?; ectx.append_expression( ir::Expression::Binary { op, left, right: value, }, stmt.span, )? } None => value, }; block.extend(emitter.finish(&ctx.function.expressions)); ir::Statement::Store { pointer: target_handle, value, } } ast::StatementKind::Increment(value) | ast::StatementKind::Decrement(value) => { let mut emitter = proc::Emitter::default(); emitter.start(&ctx.function.expressions); let op = match stmt.kind { ast::StatementKind::Increment(_) => ir::BinaryOperator::Add, ast::StatementKind::Decrement(_) => ir::BinaryOperator::Subtract, _ => unreachable!(), }; let value_span = ctx.ast_expressions.get_span(value); let target = self .expression_for_reference(value, &mut ctx.as_expression(block, &mut emitter))?; let target_handle = target.ref_or(Error::BadIncrDecrReferenceType(value_span))?; let mut ectx = ctx.as_expression(block, &mut emitter); let scalar = match *resolve_inner!(ectx, target_handle) { ir::TypeInner::ValuePointer { size: None, scalar, .. } => scalar, ir::TypeInner::Pointer { base, .. } => match ectx.module.types[base].inner { ir::TypeInner::Scalar(scalar) => scalar, _ => return Err(Box::new(Error::BadIncrDecrReferenceType(value_span))), }, _ => return Err(Box::new(Error::BadIncrDecrReferenceType(value_span))), }; let literal = match scalar.kind { ir::ScalarKind::Sint | ir::ScalarKind::Uint => ir::Literal::one(scalar) .ok_or(Error::BadIncrDecrReferenceType(value_span))?, _ => return Err(Box::new(Error::BadIncrDecrReferenceType(value_span))), }; let right = ectx.interrupt_emitter(ir::Expression::Literal(literal), Span::UNDEFINED)?; let rctx = ectx.runtime_expression_ctx(stmt.span)?; let left = rctx.function.expressions.append( ir::Expression::Load { pointer: target_handle, }, value_span, ); let value = rctx .function .expressions .append(ir::Expression::Binary { op, left, right }, stmt.span); rctx.local_expression_kind_tracker .insert(left, proc::ExpressionKind::Runtime); rctx.local_expression_kind_tracker .insert(value, proc::ExpressionKind::Runtime); block.extend(emitter.finish(&ctx.function.expressions)); ir::Statement::Store { pointer: target_handle, value, } } ast::StatementKind::ConstAssert(condition) => { let mut emitter = proc::Emitter::default(); emitter.start(&ctx.function.expressions); let condition = self.expression(condition, &mut ctx.as_const(block, &mut emitter))?; let span = ctx.function.expressions.get_span(condition); match ctx .module .to_ctx() .get_const_val_from(condition, &ctx.function.expressions) { Ok(true) => Ok(()), Ok(false) => Err(Error::ConstAssertFailed(span)), Err(proc::ConstValueError::NonConst | proc::ConstValueError::Negative) => { unreachable!() } Err(proc::ConstValueError::InvalidType) => Err(Error::NotBool(span)), }?; block.extend(emitter.finish(&ctx.function.expressions)); return Ok(()); } ast::StatementKind::Phony(expr) => { // Remembered the RHS of the phony assignment as a named expression. This // is important (1) to preserve the RHS for validation, (2) to track any // referenced globals. let mut emitter = proc::Emitter::default(); emitter.start(&ctx.function.expressions); let value = self.expression(expr, &mut ctx.as_expression(block, &mut emitter))?; block.extend(emitter.finish(&ctx.function.expressions)); ctx.named_expressions .insert(value, ("phony".to_string(), stmt.span)); return Ok(()); } }; block.push(out, stmt.span); Ok(()) } /// Lower `expr` and apply the Load Rule if possible. /// /// For the time being, this concretizes abstract values, to support /// consumers that haven't been adapted to consume them yet. Consumers /// prepared for abstract values can call [`expression_for_abstract`]. /// /// [`expression_for_abstract`]: Lowerer::expression_for_abstract fn expression( &mut self, expr: Handle>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Handle> { let expr = self.expression_for_abstract(expr, ctx)?; ctx.concretize(expr) } fn expression_for_abstract( &mut self, expr: Handle>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Handle> { let expr = self.expression_for_reference(expr, ctx)?; ctx.apply_load_rule(expr) } fn expression_with_leaf_scalar( &mut self, expr: Handle>, scalar: ir::Scalar, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Handle> { let unconverted = self.expression_for_abstract(expr, ctx)?; ctx.try_automatic_conversion_for_leaf_scalar(unconverted, scalar, Span::default()) } fn expression_for_reference( &mut self, expr: Handle>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Typed>> { let span = ctx.ast_expressions.get_span(expr); let expr = &ctx.ast_expressions[expr]; let expr: Typed = match *expr { ast::Expression::Literal(literal) => { let literal = match literal { ast::Literal::Number(Number::F16(f)) => ir::Literal::F16(f), ast::Literal::Number(Number::F32(f)) => ir::Literal::F32(f), ast::Literal::Number(Number::I32(i)) => ir::Literal::I32(i), ast::Literal::Number(Number::U32(u)) => ir::Literal::U32(u), ast::Literal::Number(Number::I64(i)) => ir::Literal::I64(i), ast::Literal::Number(Number::U64(u)) => ir::Literal::U64(u), ast::Literal::Number(Number::F64(f)) => ir::Literal::F64(f), ast::Literal::Number(Number::AbstractInt(i)) => ir::Literal::AbstractInt(i), ast::Literal::Number(Number::AbstractFloat(f)) => ir::Literal::AbstractFloat(f), ast::Literal::Bool(b) => ir::Literal::Bool(b), }; let handle = ctx.interrupt_emitter(ir::Expression::Literal(literal), span)?; return Ok(Typed::Plain(handle)); } ast::Expression::Ident(ast::TemplateElaboratedIdent { ref template_list, .. }) if !template_list.is_empty() => { return Err(Box::new(Error::UnexpectedTemplate(span))) } ast::Expression::Ident(ast::TemplateElaboratedIdent { ident: ast::IdentExpr::Local(local), .. }) => { return ctx.local(&local, span); } ast::Expression::Ident(ast::TemplateElaboratedIdent { ident: ast::IdentExpr::Unresolved(name), .. }) => { let global = ctx .globals .get(name) .ok_or(Error::UnknownIdent(span, name))?; let expr = match *global { LoweredGlobalDecl::Var(handle) => { let expr = ir::Expression::GlobalVariable(handle); let v = &ctx.module.global_variables[handle]; match v.space { ir::AddressSpace::Handle => Typed::Plain(expr), _ => Typed::Reference(expr), } } LoweredGlobalDecl::Const(handle) => { Typed::Plain(ir::Expression::Constant(handle)) } LoweredGlobalDecl::Override(handle) => { Typed::Plain(ir::Expression::Override(handle)) } LoweredGlobalDecl::Function { .. } | LoweredGlobalDecl::Type(_) | LoweredGlobalDecl::EntryPoint(_) => { return Err(Box::new(Error::Unexpected(span, ExpectedToken::Variable))); } }; return expr.try_map(|handle| ctx.interrupt_emitter(handle, span)); } ast::Expression::Unary { op, expr } => { let expr = self.expression_for_abstract(expr, ctx)?; Typed::Plain(ir::Expression::Unary { op, expr }) } ast::Expression::AddrOf(expr) => { // The `&` operator simply converts a reference to a pointer. And since a // reference is required, the Load Rule is not applied. match self.expression_for_reference(expr, ctx)? { Typed::Reference(handle) => { let expr = &ctx.runtime_expression_ctx(span)?.function.expressions[handle]; if let &ir::Expression::Access { base, .. } | &ir::Expression::AccessIndex { base, .. } = expr { if let Some(ty) = resolve_inner!(ctx, base).pointer_base_type() { if matches!( *ty.inner_with(&ctx.module.types), ir::TypeInner::Vector { .. }, ) { return Err(Box::new(Error::InvalidAddrOfOperand( ctx.get_expression_span(handle), ))); } } } // No code is generated. We just declare the reference a pointer now. return Ok(Typed::Plain(handle)); } Typed::Plain(_) => { return Err(Box::new(Error::NotReference( "the operand of the `&` operator", span, ))); } } } ast::Expression::Deref(expr) => { // The pointer we dereference must be loaded. let pointer = self.expression(expr, ctx)?; if resolve_inner!(ctx, pointer).pointer_space().is_none() { return Err(Box::new(Error::NotPointer(span))); } // No code is generated. We just declare the pointer a reference now. return Ok(Typed::Reference(pointer)); } ast::Expression::Binary { op, left, right } => { self.binary(op, left, right, span, ctx)? } ast::Expression::Call(ref call_phrase) => { let handle = self .call(call_phrase, span, ctx, false)? .ok_or(Error::FunctionReturnsVoid(span))?; return Ok(Typed::Plain(handle)); } ast::Expression::Index { base, index } => { let mut lowered_base = self.expression_for_reference(base, ctx)?; let index = self.expression(index, ctx)?; // // Declare pointer as reference if let Typed::Plain(handle) = lowered_base { if resolve_inner!(ctx, handle).pointer_space().is_some() { lowered_base = Typed::Reference(handle); } } lowered_base.try_map(|base| match ctx.get_const_val(index).ok() { Some(index) => Ok::<_, Box>(ir::Expression::AccessIndex { base, index }), None => { // When an abstract array value e is indexed by an expression // that is not a const-expression, then the array is concretized // before the index is applied. // https://www.w3.org/TR/WGSL/#array-access-expr // Also applies to vectors and matrices. let base = ctx.concretize(base)?; Ok(ir::Expression::Access { base, index }) } })? } ast::Expression::Member { base, ref field } => { let mut lowered_base = self.expression_for_reference(base, ctx)?; // // Declare pointer as reference if let Typed::Plain(handle) = lowered_base { if resolve_inner!(ctx, handle).pointer_space().is_some() { lowered_base = Typed::Reference(handle); } } let temp_ty; let composite_type: &ir::TypeInner = match lowered_base { Typed::Reference(handle) => { temp_ty = resolve_inner!(ctx, handle) .pointer_base_type() .expect("In Typed::Reference(handle), handle must be a Naga pointer"); temp_ty.inner_with(&ctx.module.types) } Typed::Plain(handle) => { resolve_inner!(ctx, handle) } }; let access = match *composite_type { ir::TypeInner::Struct { ref members, .. } => { let index = members .iter() .position(|m| m.name.as_deref() == Some(field.name)) .ok_or(Error::BadAccessor(field.span))? as u32; lowered_base.map(|base| ir::Expression::AccessIndex { base, index }) } ir::TypeInner::Vector { size: vec_size, .. } => { match Components::new(field.name, field.span)? { Components::Swizzle { size, pattern } => { for &component in pattern[..size as usize].iter() { if component as u8 >= vec_size as u8 { return Err(Box::new(Error::BadAccessor(field.span))); } } Typed::Plain(ir::Expression::Swizzle { size, vector: ctx.apply_load_rule(lowered_base)?, pattern, }) } Components::Single(index) => { if index >= vec_size as u32 { return Err(Box::new(Error::BadAccessor(field.span))); } lowered_base.map(|base| ir::Expression::AccessIndex { base, index }) } } } _ => return Err(Box::new(Error::BadAccessor(field.span))), }; access } }; expr.try_map(|handle| ctx.append_expression(handle, span)) } /// Generate IR for the short-circuiting operators `&&` and `||`. /// /// `binary` has already lowered the LHS expression and resolved its type. fn logical( &mut self, op: crate::BinaryOperator, left: Handle, right: Handle>, span: Span, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Typed> { debug_assert!( op == crate::BinaryOperator::LogicalAnd || op == crate::BinaryOperator::LogicalOr ); if ctx.is_runtime() { // To simulate short-circuiting behavior, we want to generate IR // like the following for `&&`. For `||`, the condition is `!_lhs` // and the else value is `true`. // // var _e0: bool; // if _lhs { // _e0 = _rhs; // } else { // _e0 = false; // } let (condition, else_val) = if op == crate::BinaryOperator::LogicalAnd { let condition = left; let else_val = ctx.append_expression( crate::Expression::Literal(crate::Literal::Bool(false)), span, )?; (condition, else_val) } else { let condition = ctx.append_expression( crate::Expression::Unary { op: crate::UnaryOperator::LogicalNot, expr: left, }, span, )?; let else_val = ctx.append_expression( crate::Expression::Literal(crate::Literal::Bool(true)), span, )?; (condition, else_val) }; let bool_ty = ctx.ensure_type_exists(crate::TypeInner::Scalar(crate::Scalar::BOOL)); let rctx = ctx.runtime_expression_ctx(span)?; let result_var = rctx.function.local_variables.append( crate::LocalVariable { name: None, ty: bool_ty, init: None, }, span, ); let pointer = ctx.append_expression(crate::Expression::LocalVariable(result_var), span)?; let (right, mut accept) = ctx.with_nested_runtime_expression_ctx(span, |ctx| { let right = self.expression_for_abstract(right, ctx)?; ctx.grow_types(right)?; Ok(right) })?; accept.push( crate::Statement::Store { pointer, value: right, }, span, ); let mut reject = crate::Block::with_capacity(1); reject.push( crate::Statement::Store { pointer, value: else_val, }, span, ); let rctx = ctx.runtime_expression_ctx(span)?; rctx.block.push( crate::Statement::If { condition, accept, reject, }, span, ); Ok(Typed::Reference(crate::Expression::LocalVariable( result_var, ))) } else { let left_val: Option = ctx.get_const_val(left).ok(); if left_val.is_some_and(|left_val| { op == crate::BinaryOperator::LogicalAnd && !left_val || op == crate::BinaryOperator::LogicalOr && left_val }) { // Short-circuit behavior: don't evaluate the RHS. // TODO(https://github.com/gfx-rs/wgpu/issues/8440): We shouldn't ignore the // RHS completely, it should still be type-checked. Preserving it for type // checking is a bit tricky, because we're trying to produce an expression // for a const context, but the RHS is allowed to have things that aren't // const. Ok(Typed::Plain(ctx.get(left).clone())) } else { // Evaluate the RHS and construct the entire binary expression as we // normally would. This case applies to well-formed constant logical // expressions that don't short-circuit (handled by the constant evaluator // shortly), to override expressions (handled when overrides are processed) // and to non-well-formed expressions (rejected by type checking). let right = self.expression_for_abstract(right, ctx)?; ctx.grow_types(right)?; Ok(Typed::Plain(crate::Expression::Binary { op, left, right })) } } } fn type_expression( &mut self, expr: Handle>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Handle> { let span = ctx.ast_expressions.get_span(expr); let expr = &ctx.ast_expressions[expr]; let ident = match *expr { ast::Expression::Ident(ref ident) => ident, _ => return Err(Box::new(Error::UnexpectedExprForTypeExpression(span))), }; self.type_specifier(ident, ctx, None) } fn type_specifier( &mut self, ident: &ast::TemplateElaboratedIdent<'source>, ctx: &mut ExpressionContext<'source, '_, '_>, alias_name: Option, ) -> Result<'source, Handle> { let &ast::TemplateElaboratedIdent { ref ident, ident_span, ref template_list, .. } = ident; let ident = match *ident { ast::IdentExpr::Unresolved(ident) => ident, ast::IdentExpr::Local(_) => { // Since WGSL only supports module-scope type definitions and // aliases, a local identifier can't possibly refer to a type. return Err(Box::new(Error::UnexpectedExprForTypeExpression(ident_span))); } }; let mut tl = TemplateListIter::new(ident_span, template_list); if let Some(global) = ctx.globals.get(ident) { let &LoweredGlobalDecl::Type(handle) = global else { return Err(Box::new(Error::UnexpectedExprForTypeExpression(ident_span))); }; // Type generators can only be predeclared, so since `ident` refers // to a module-scope declaration, the template parameter list should // be empty. tl.finish(ctx)?; return Ok(handle); } // If `ident` doesn't resolve to a module-scope declaration, then it // must resolve to a predeclared type or type generator. let ty = conv::map_predeclared_type(&ctx.enable_extensions, ident_span, ident)? .ok_or_else(|| Box::new(Error::UnknownIdent(ident_span, ident)))?; let ty = self.finalize_type(ctx, ty, &mut tl, alias_name)?; tl.finish(ctx)?; Ok(ty) } /// Construct an [`ir::Type`] from a [`conv::PredeclaredType`] and a list of /// template parameters. /// /// If we're processing a type alias, then `alias_name` is the name we /// should use in the new `ir::Type`. /// /// For example, when parsing `vec3`, the caller would pass: /// /// - for `ty`, [`TypeGenerator::Vector`], and /// /// - for `tl`, an iterator producing a single [`Expression::Ident`] representing `f32`. /// /// From those arguments this function will return a handle for the /// [`ir::Type`] representing `vec3`. /// /// [`TypeGenerator::Vector`]: conv::TypeGenerator::Vector /// [`Expression::Ident`]: crate::front::wgsl::parse::ast::Expression::Ident fn finalize_type( &mut self, ctx: &mut ExpressionContext<'source, '_, '_>, ty: conv::PredeclaredType, tl: &mut TemplateListIter<'_, 'source>, alias_name: Option, ) -> Result<'source, Handle> { let ty = match ty { conv::PredeclaredType::TypeInner(ty_inner) => { if let ir::TypeInner::Image { class: ir::ImageClass::External, .. } = ty_inner { // Other than the WGSL backend, every backend that supports // external textures does so by lowering them to a set of // ordinary textures and some parameters saying how to // sample from them. We don't know which backend will // consume the `Module` we're building, but in case it's not // WGSL, populate `SpecialTypes::external_texture_params` // and `SpecialTypes::external_texture_transfer_function` // with the types the backend will use for the parameter // buffer. // // Neither of these are the type we are lowering here: // that's an ordinary `TypeInner::Image`. But the fact we // are lowering a `texture_external` implies the backends // may need these additional types too. ctx.module.generate_external_texture_types(); } ctx.as_global().ensure_type_exists(alias_name, ty_inner) } conv::PredeclaredType::RayDesc => ctx.module.generate_ray_desc_type(), conv::PredeclaredType::RayIntersection => ctx.module.generate_ray_intersection_type(), conv::PredeclaredType::TypeGenerator(type_generator) => { let ty_inner = match type_generator { conv::TypeGenerator::Vector { size } => { let (scalar, _) = tl.scalar_ty(self, ctx)?; ir::TypeInner::Vector { size, scalar } } conv::TypeGenerator::Matrix { columns, rows } => { let (scalar, span) = tl.scalar_ty(self, ctx)?; if scalar.kind != ir::ScalarKind::Float { return Err(Box::new(Error::BadMatrixScalarKind(span, scalar))); } ir::TypeInner::Matrix { columns, rows, scalar, } } conv::TypeGenerator::Array => { let base = tl.ty(self, ctx)?; let size = tl.maybe_array_size(self, ctx)?; // Determine the size of the base type, if needed. ctx.layouter.update(ctx.module.to_ctx()).map_err(|err| { let LayoutErrorInner::TooLarge = err.inner else { unreachable!("unexpected layout error: {err:?}"); }; // Lots of type definitions don't get spans, so this error // message may not be very useful. Box::new(Error::TypeTooLarge { span: ctx.module.types.get_span(err.ty), }) })?; let stride = ctx.layouter[base].to_stride(); ir::TypeInner::Array { base, size, stride } } conv::TypeGenerator::Atomic => { let (scalar, _) = tl.scalar_ty(self, ctx)?; ir::TypeInner::Atomic(scalar) } conv::TypeGenerator::Pointer => { let mut space = tl.address_space(ctx)?; let base = tl.ty(self, ctx)?; tl.maybe_access_mode(&mut space, ctx)?; ir::TypeInner::Pointer { base, space } } conv::TypeGenerator::SampledTexture { dim, arrayed, multi, } => { let (scalar, span) = tl.scalar_ty(self, ctx)?; let ir::Scalar { kind, width } = scalar; if width != 4 { return Err(Box::new(Error::BadTextureSampleType { span, scalar })); } ir::TypeInner::Image { dim, arrayed, class: ir::ImageClass::Sampled { kind, multi }, } } conv::TypeGenerator::StorageTexture { dim, arrayed } => { let format = tl.storage_format(ctx)?; let access = tl.access_mode(ctx)?; ir::TypeInner::Image { dim, arrayed, class: ir::ImageClass::Storage { format, access }, } } conv::TypeGenerator::BindingArray => { let base = tl.ty(self, ctx)?; let size = tl.maybe_array_size(self, ctx)?; ir::TypeInner::BindingArray { base, size } } conv::TypeGenerator::AccelerationStructure => { let vertex_return = tl.maybe_vertex_return(ctx)?; ir::TypeInner::AccelerationStructure { vertex_return } } conv::TypeGenerator::RayQuery => { let vertex_return = tl.maybe_vertex_return(ctx)?; ir::TypeInner::RayQuery { vertex_return } } conv::TypeGenerator::CooperativeMatrix { columns, rows } => { let (ty, span) = tl.ty_with_span(self, ctx)?; let ir::TypeInner::Scalar(scalar) = ctx.module.types[ty].inner else { return Err(Box::new(Error::UnsupportedCooperativeScalar(span))); }; let role = tl.cooperative_role(ctx)?; ir::TypeInner::CooperativeMatrix { columns, rows, scalar, role, } } }; ctx.as_global().ensure_type_exists(alias_name, ty_inner) } }; Ok(ty) } fn binary( &mut self, op: ir::BinaryOperator, left: Handle>, right: Handle>, span: Span, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Typed> { if op == ir::BinaryOperator::LogicalAnd || op == ir::BinaryOperator::LogicalOr { let left = self.expression_for_abstract(left, ctx)?; ctx.grow_types(left)?; if !matches!( resolve_inner!(ctx, left), &ir::TypeInner::Scalar(ir::Scalar::BOOL) ) { // Pass it through as-is, will fail validation let right = self.expression_for_abstract(right, ctx)?; ctx.grow_types(right)?; Ok(Typed::Plain(crate::Expression::Binary { op, left, right })) } else { self.logical(op, left, right, span, ctx) } } else { // Load both operands. let mut left = self.expression_for_abstract(left, ctx)?; let mut right = self.expression_for_abstract(right, ctx)?; // Convert `scalar op vector` to `vector op vector` by introducing // `Splat` expressions. ctx.binary_op_splat(op, &mut left, &mut right)?; // Apply automatic conversions. match op { ir::BinaryOperator::ShiftLeft | ir::BinaryOperator::ShiftRight => { // Shift operators require the right operand to be `u32` or // `vecN`. We can let the validator sort out vector length // issues, but the right operand must be, or convert to, a u32 leaf // scalar. right = ctx.try_automatic_conversion_for_leaf_scalar(right, ir::Scalar::U32, span)?; // Additionally, we must concretize the left operand if the right operand // is not a const-expression. // See https://www.w3.org/TR/WGSL/#overload-resolution-section. // // 2. Eliminate any candidate where one of its subexpressions resolves to // an abstract type after feasible automatic conversions, but another of // the candidate’s subexpressions is not a const-expression. // // We only have to explicitly do so for shifts as their operands may be // of different types - for other binary ops this is achieved by finding // the conversion consensus for both operands. if !ctx.is_const(right) { left = ctx.concretize(left)?; } } // All other operators follow the same pattern: reconcile the // scalar leaf types. If there's no reconciliation possible, // leave the expressions as they are: validation will report the // problem. _ => { ctx.grow_types(left)?; ctx.grow_types(right)?; if let Ok(consensus_scalar) = ctx.automatic_conversion_consensus(None, [left, right].iter()) { ctx.convert_to_leaf_scalar(&mut left, consensus_scalar)?; ctx.convert_to_leaf_scalar(&mut right, consensus_scalar)?; } } } Ok(Typed::Plain(ir::Expression::Binary { op, left, right })) } } /// Generate Naga IR for a call to a WGSL builtin function. #[allow(clippy::too_many_arguments)] fn call_builtin<'phrase>( &mut self, function_name: &'source str, function_span: Span, arguments: &[Handle>], template_params: &mut TemplateListIter<'phrase, 'source>, call_span: Span, ctx: &mut ExpressionContext<'source, '_, '_>, is_statement: bool, ) -> Result<'source, Option<(Handle, MustUse)>> { let (expr, must_use) = if let Some(fun) = conv::map_relational_fun(function_name) { let mut args = ctx.prepare_args(arguments, 1, function_span); let argument = self.expression(args.next()?, ctx)?; args.finish()?; // Check for no-op all(bool) and any(bool): let argument_unmodified = matches!( fun, ir::RelationalFunction::All | ir::RelationalFunction::Any ) && { matches!( resolve_inner!(ctx, argument), &ir::TypeInner::Scalar(ir::Scalar { kind: ir::ScalarKind::Bool, .. }) ) }; if argument_unmodified { return Ok(Some((argument, MustUse::Yes))); } else { (ir::Expression::Relational { fun, argument }, MustUse::Yes) } } else if let Some((axis, ctrl)) = conv::map_derivative(function_name) { let mut args = ctx.prepare_args(arguments, 1, function_span); let expr = self.expression(args.next()?, ctx)?; args.finish()?; ( ir::Expression::Derivative { axis, ctrl, expr }, MustUse::Yes, ) } else if let Some(fun) = conv::map_standard_fun(function_name) { ( self.math_function_helper(function_span, fun, arguments, ctx)?, MustUse::Yes, ) } else if let Some(fun) = Texture::map(function_name) { ( self.texture_sample_helper(fun, arguments, function_span, ctx)?, MustUse::Yes, ) } else if let Some((op, cop)) = conv::map_subgroup_operation(function_name) { return Ok(Some(( self.subgroup_operation_helper(function_span, op, cop, arguments, ctx)?, MustUse::Yes, ))); } else if let Some(mode) = SubgroupGather::map(function_name) { return Ok(Some(( self.subgroup_gather_helper(function_span, mode, arguments, ctx)?, MustUse::Yes, ))); } else if let Some(fun) = ir::AtomicFunction::map(function_name) { return Ok(self .atomic_helper(function_span, fun, arguments, is_statement, ctx)? .map(|result| (result, MustUse::No))); } else { match function_name { "bitcast" => { let ty = template_params.ty(self, ctx)?; let mut args = ctx.prepare_args(arguments, 1, function_span); let expr = self.expression(args.next()?, ctx)?; args.finish()?; let element_scalar = match ctx.module.types[ty].inner { ir::TypeInner::Scalar(scalar) => scalar, ir::TypeInner::Vector { scalar, .. } => scalar, _ => { let ty_resolution = resolve!(ctx, expr); return Err(Box::new(Error::BadTypeCast { from_type: ctx.type_resolution_to_string(ty_resolution), span: function_span, to_type: ctx.type_to_string(ty), })); } }; ( ir::Expression::As { expr, kind: element_scalar.kind, convert: None, }, MustUse::Yes, ) } "coopLoad" | "coopLoadT" => { let row_major = function_name.ends_with("T"); let (matrix_ty, matrix_span) = template_params.ty_with_span(self, ctx)?; let mut args = ctx.prepare_args(arguments, 1, call_span); let pointer = self.expression(args.next()?, ctx)?; let (columns, rows, role) = match ctx.module.types[matrix_ty].inner { ir::TypeInner::CooperativeMatrix { columns, rows, role, .. } => (columns, rows, role), _ => return Err(Box::new(Error::InvalidCooperativeLoadType(matrix_span))), }; let stride = if args.total_args > 1 { self.expression(args.next()?, ctx)? } else { // Infer the stride from the matrix type let stride = if row_major { columns as u32 } else { rows as u32 }; ctx.append_expression( ir::Expression::Literal(ir::Literal::U32(stride)), Span::UNDEFINED, )? }; args.finish()?; ( crate::Expression::CooperativeLoad { columns, rows, role, data: crate::CooperativeData { pointer, stride, row_major, }, }, MustUse::Yes, ) } "select" => { let mut args = ctx.prepare_args(arguments, 3, function_span); let reject_orig = args.next()?; let accept_orig = args.next()?; let mut values = [ self.expression_for_abstract(reject_orig, ctx)?, self.expression_for_abstract(accept_orig, ctx)?, ]; let condition = self.expression(args.next()?, ctx)?; args.finish()?; let diagnostic_details = |ctx: &ExpressionContext<'_, '_, '_>, ty_res: &proc::TypeResolution, orig_expr| { ( ctx.ast_expressions.get_span(orig_expr), format!("`{}`", ctx.as_diagnostic_display(ty_res)), ) }; for (&value, orig_value) in values.iter().zip([reject_orig, accept_orig]) { let value_ty_res = resolve!(ctx, value); if value_ty_res .inner_with(&ctx.module.types) .vector_size_and_scalar() .is_none() { let (arg_span, arg_type) = diagnostic_details(ctx, value_ty_res, orig_value); return Err(Box::new(Error::SelectUnexpectedArgumentType { arg_span, arg_type, })); } } let mut consensus_scalar = ctx .automatic_conversion_consensus(None, &values) .map_err(|_idx| { let [reject, accept] = values; let [(reject_span, reject_type), (accept_span, accept_type)] = [(reject_orig, reject), (accept_orig, accept)].map( |(orig_expr, expr)| { let ty_res = &ctx.typifier()[expr]; diagnostic_details(ctx, ty_res, orig_expr) }, ); Error::SelectRejectAndAcceptHaveNoCommonType { reject_span, reject_type, accept_span, accept_type, } })?; if !ctx.is_const(condition) { consensus_scalar = consensus_scalar.concretize(); } ctx.convert_slice_to_common_leaf_scalar(&mut values, consensus_scalar)?; let [reject, accept] = values; ( ir::Expression::Select { reject, accept, condition, }, MustUse::Yes, ) } "arrayLength" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let expr = self.expression(args.next()?, ctx)?; args.finish()?; (ir::Expression::ArrayLength(expr), MustUse::Yes) } "atomicLoad" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let (pointer, _scalar) = self.atomic_pointer(args.next()?, ctx)?; args.finish()?; (ir::Expression::Load { pointer }, MustUse::No) } "atomicStore" => { let mut args = ctx.prepare_args(arguments, 2, function_span); let (pointer, scalar) = self.atomic_pointer(args.next()?, ctx)?; let value = self.expression_with_leaf_scalar(args.next()?, scalar, ctx)?; args.finish()?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block .extend(rctx.emitter.finish(&rctx.function.expressions)); rctx.emitter.start(&rctx.function.expressions); rctx.block .push(ir::Statement::Store { pointer, value }, function_span); return Ok(None); } "atomicCompareExchangeWeak" => { let mut args = ctx.prepare_args(arguments, 3, function_span); let (pointer, scalar) = self.atomic_pointer(args.next()?, ctx)?; let compare = self.expression_with_leaf_scalar(args.next()?, scalar, ctx)?; let value = args.next()?; let value_span = ctx.ast_expressions.get_span(value); let value = self.expression_with_leaf_scalar(value, scalar, ctx)?; args.finish()?; let expression = match *resolve_inner!(ctx, value) { ir::TypeInner::Scalar(scalar) => ir::Expression::AtomicResult { ty: ctx.module.generate_predeclared_type( ir::PredeclaredType::AtomicCompareExchangeWeakResult(scalar), ), comparison: true, }, _ => return Err(Box::new(Error::InvalidAtomicOperandType(value_span))), }; let result = ctx.interrupt_emitter(expression, function_span)?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block.push( ir::Statement::Atomic { pointer, fun: ir::AtomicFunction::Exchange { compare: Some(compare), }, value, result: Some(result), }, function_span, ); return Ok(Some((result, MustUse::No))); } "textureAtomicMin" | "textureAtomicMax" | "textureAtomicAdd" | "textureAtomicAnd" | "textureAtomicOr" | "textureAtomicXor" => { let mut args = ctx.prepare_args(arguments, 3, function_span); let image = args.next()?; let image_span = ctx.ast_expressions.get_span(image); let image = self.expression(image, ctx)?; let coordinate = self.expression(args.next()?, ctx)?; let (_, arrayed) = ctx.image_data(image, image_span)?; let array_index = arrayed .then(|| { args.min_args += 1; self.expression(args.next()?, ctx) }) .transpose()?; let value = self.expression(args.next()?, ctx)?; args.finish()?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block .extend(rctx.emitter.finish(&rctx.function.expressions)); rctx.emitter.start(&rctx.function.expressions); let stmt = ir::Statement::ImageAtomic { image, coordinate, array_index, fun: match function_name { "textureAtomicMin" => ir::AtomicFunction::Min, "textureAtomicMax" => ir::AtomicFunction::Max, "textureAtomicAdd" => ir::AtomicFunction::Add, "textureAtomicAnd" => ir::AtomicFunction::And, "textureAtomicOr" => ir::AtomicFunction::InclusiveOr, "textureAtomicXor" => ir::AtomicFunction::ExclusiveOr, _ => unreachable!(), }, value, }; rctx.block.push(stmt, function_span); return Ok(None); } "storageBarrier" => { ctx.prepare_args(arguments, 0, function_span).finish()?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block.push( ir::Statement::ControlBarrier(ir::Barrier::STORAGE), function_span, ); return Ok(None); } "workgroupBarrier" => { ctx.prepare_args(arguments, 0, function_span).finish()?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block.push( ir::Statement::ControlBarrier(ir::Barrier::WORK_GROUP), function_span, ); return Ok(None); } "subgroupBarrier" => { ctx.prepare_args(arguments, 0, function_span).finish()?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block.push( ir::Statement::ControlBarrier(ir::Barrier::SUB_GROUP), function_span, ); return Ok(None); } "textureBarrier" => { ctx.prepare_args(arguments, 0, function_span).finish()?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block.push( ir::Statement::ControlBarrier(ir::Barrier::TEXTURE), function_span, ); return Ok(None); } "workgroupUniformLoad" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let expr = args.next()?; args.finish()?; let pointer = self.expression(expr, ctx)?; let result_ty = match *resolve_inner!(ctx, pointer) { ir::TypeInner::Pointer { base, space: ir::AddressSpace::WorkGroup, } => match ctx.module.types[base].inner { // Match `Expression::Load` semantics: // loading through a pointer to `atomic` produces a `T`. ir::TypeInner::Atomic(scalar) => ctx.module.types.insert( ir::Type { name: None, inner: ir::TypeInner::Scalar(scalar), }, function_span, ), _ => base, }, ir::TypeInner::ValuePointer { size, scalar, space: ir::AddressSpace::WorkGroup, } => ctx.module.types.insert( ir::Type { name: None, inner: match size { Some(size) => ir::TypeInner::Vector { size, scalar }, None => ir::TypeInner::Scalar(scalar), }, }, function_span, ), _ => { let span = ctx.ast_expressions.get_span(expr); return Err(Box::new(Error::InvalidWorkGroupUniformLoad(span))); } }; let result = ctx.interrupt_emitter( ir::Expression::WorkGroupUniformLoadResult { ty: result_ty }, function_span, )?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block.push( ir::Statement::WorkGroupUniformLoad { pointer, result }, function_span, ); return Ok(Some((result, MustUse::Yes))); } "textureStore" => { let mut args = ctx.prepare_args(arguments, 3, function_span); let image = args.next()?; let image_span = ctx.ast_expressions.get_span(image); let image = self.expression(image, ctx)?; let coordinate = self.expression(args.next()?, ctx)?; let (class, arrayed) = ctx.image_data(image, image_span)?; let array_index = arrayed .then(|| { args.min_args += 1; self.expression(args.next()?, ctx) }) .transpose()?; let scalar = if let ir::ImageClass::Storage { format, .. } = class { format.into() } else { return Err(Box::new(Error::NotStorageTexture(image_span))); }; let value = self.expression_with_leaf_scalar(args.next()?, scalar, ctx)?; args.finish()?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block .extend(rctx.emitter.finish(&rctx.function.expressions)); rctx.emitter.start(&rctx.function.expressions); let stmt = ir::Statement::ImageStore { image, coordinate, array_index, value, }; rctx.block.push(stmt, function_span); return Ok(None); } "textureLoad" => { let mut args = ctx.prepare_args(arguments, 2, function_span); let image = args.next()?; let image_span = ctx.ast_expressions.get_span(image); let image = self.expression(image, ctx)?; let coordinate = self.expression(args.next()?, ctx)?; let (class, arrayed) = ctx.image_data(image, image_span)?; let array_index = arrayed .then(|| { args.min_args += 1; self.expression(args.next()?, ctx) }) .transpose()?; let level = class .is_mipmapped() .then(|| { args.min_args += 1; self.expression(args.next()?, ctx) }) .transpose()?; let sample = class .is_multisampled() .then(|| self.expression(args.next()?, ctx)) .transpose()?; args.finish()?; ( ir::Expression::ImageLoad { image, coordinate, array_index, level, sample, }, MustUse::Yes, ) } "textureDimensions" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let image = self.expression(args.next()?, ctx)?; let level = args .next() .map(|arg| self.expression(arg, ctx)) .ok() .transpose()?; args.finish()?; ( ir::Expression::ImageQuery { image, query: ir::ImageQuery::Size { level }, }, MustUse::Yes, ) } "textureNumLevels" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let image = self.expression(args.next()?, ctx)?; args.finish()?; ( ir::Expression::ImageQuery { image, query: ir::ImageQuery::NumLevels, }, MustUse::Yes, ) } "textureNumLayers" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let image = self.expression(args.next()?, ctx)?; args.finish()?; ( ir::Expression::ImageQuery { image, query: ir::ImageQuery::NumLayers, }, MustUse::Yes, ) } "textureNumSamples" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let image = self.expression(args.next()?, ctx)?; args.finish()?; ( ir::Expression::ImageQuery { image, query: ir::ImageQuery::NumSamples, }, MustUse::Yes, ) } "rayQueryInitialize" => { let mut args = ctx.prepare_args(arguments, 3, function_span); let query = self.ray_query_pointer(args.next()?, ctx)?; let acceleration_structure = self.expression(args.next()?, ctx)?; let descriptor = self.expression(args.next()?, ctx)?; args.finish()?; let _ = ctx.module.generate_ray_desc_type(); let fun = ir::RayQueryFunction::Initialize { acceleration_structure, descriptor, }; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block .extend(rctx.emitter.finish(&rctx.function.expressions)); rctx.emitter.start(&rctx.function.expressions); rctx.block .push(ir::Statement::RayQuery { query, fun }, function_span); return Ok(None); } "getCommittedHitVertexPositions" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let query = self.ray_query_pointer(args.next()?, ctx)?; args.finish()?; let _ = ctx.module.generate_vertex_return_type(); ( ir::Expression::RayQueryVertexPositions { query, committed: true, }, MustUse::No, ) } "getCandidateHitVertexPositions" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let query = self.ray_query_pointer(args.next()?, ctx)?; args.finish()?; let _ = ctx.module.generate_vertex_return_type(); ( ir::Expression::RayQueryVertexPositions { query, committed: false, }, MustUse::No, ) } "rayQueryProceed" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let query = self.ray_query_pointer(args.next()?, ctx)?; args.finish()?; let result = ctx .interrupt_emitter(ir::Expression::RayQueryProceedResult, function_span)?; let fun = ir::RayQueryFunction::Proceed { result }; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block .push(ir::Statement::RayQuery { query, fun }, function_span); return Ok(Some((result, MustUse::No))); } "rayQueryGenerateIntersection" => { let mut args = ctx.prepare_args(arguments, 2, function_span); let query = self.ray_query_pointer(args.next()?, ctx)?; let hit_t = self.expression(args.next()?, ctx)?; args.finish()?; let fun = ir::RayQueryFunction::GenerateIntersection { hit_t }; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block .push(ir::Statement::RayQuery { query, fun }, function_span); return Ok(None); } "rayQueryConfirmIntersection" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let query = self.ray_query_pointer(args.next()?, ctx)?; args.finish()?; let fun = ir::RayQueryFunction::ConfirmIntersection; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block .push(ir::Statement::RayQuery { query, fun }, function_span); return Ok(None); } "rayQueryTerminate" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let query = self.ray_query_pointer(args.next()?, ctx)?; args.finish()?; let fun = ir::RayQueryFunction::Terminate; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block .push(ir::Statement::RayQuery { query, fun }, function_span); return Ok(None); } "rayQueryGetCommittedIntersection" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let query = self.ray_query_pointer(args.next()?, ctx)?; args.finish()?; let _ = ctx.module.generate_ray_intersection_type(); ( ir::Expression::RayQueryGetIntersection { query, committed: true, }, MustUse::No, ) } "rayQueryGetCandidateIntersection" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let query = self.ray_query_pointer(args.next()?, ctx)?; args.finish()?; let _ = ctx.module.generate_ray_intersection_type(); ( ir::Expression::RayQueryGetIntersection { query, committed: false, }, MustUse::No, ) } "subgroupBallot" => { let mut args = ctx.prepare_args(arguments, 0, function_span); let predicate = if arguments.len() == 1 { Some(self.expression(args.next()?, ctx)?) } else { None }; args.finish()?; let result = ctx.interrupt_emitter(ir::Expression::SubgroupBallotResult, function_span)?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block.push( ir::Statement::SubgroupBallot { result, predicate }, function_span, ); return Ok(Some((result, MustUse::Yes))); } "quadSwapX" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let argument = self.expression(args.next()?, ctx)?; args.finish()?; let ty = ctx.register_type(argument)?; let result = ctx.interrupt_emitter( crate::Expression::SubgroupOperationResult { ty }, function_span, )?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block.push( crate::Statement::SubgroupGather { mode: crate::GatherMode::QuadSwap(crate::Direction::X), argument, result, }, function_span, ); return Ok(Some((result, MustUse::Yes))); } "quadSwapY" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let argument = self.expression(args.next()?, ctx)?; args.finish()?; let ty = ctx.register_type(argument)?; let result = ctx.interrupt_emitter( crate::Expression::SubgroupOperationResult { ty }, function_span, )?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block.push( crate::Statement::SubgroupGather { mode: crate::GatherMode::QuadSwap(crate::Direction::Y), argument, result, }, function_span, ); return Ok(Some((result, MustUse::Yes))); } "quadSwapDiagonal" => { let mut args = ctx.prepare_args(arguments, 1, function_span); let argument = self.expression(args.next()?, ctx)?; args.finish()?; let ty = ctx.register_type(argument)?; let result = ctx.interrupt_emitter( crate::Expression::SubgroupOperationResult { ty }, function_span, )?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block.push( crate::Statement::SubgroupGather { mode: crate::GatherMode::QuadSwap(crate::Direction::Diagonal), argument, result, }, function_span, ); return Ok(Some((result, MustUse::Yes))); } "coopStore" | "coopStoreT" => { let row_major = function_name.ends_with("T"); let mut args = ctx.prepare_args(arguments, 2, function_span); let target = self.expression(args.next()?, ctx)?; let pointer = self.expression(args.next()?, ctx)?; let stride = if args.total_args > 2 { self.expression(args.next()?, ctx)? } else { // Infer the stride from the matrix type let stride = match *resolve_inner!(ctx, target) { ir::TypeInner::CooperativeMatrix { columns, rows, .. } => { if row_major { columns as u32 } else { rows as u32 } } _ => 0, }; ctx.append_expression( ir::Expression::Literal(ir::Literal::U32(stride)), Span::UNDEFINED, )? }; args.finish()?; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block.push( crate::Statement::CooperativeStore { target, data: crate::CooperativeData { pointer, stride, row_major, }, }, function_span, ); return Ok(None); } "coopMultiplyAdd" => { let mut args = ctx.prepare_args(arguments, 3, function_span); let a = self.expression(args.next()?, ctx)?; let b = self.expression(args.next()?, ctx)?; let c = self.expression(args.next()?, ctx)?; args.finish()?; ( ir::Expression::CooperativeMultiplyAdd { a, b, c }, MustUse::Yes, ) } "traceRay" => { let mut args = ctx.prepare_args(arguments, 3, function_span); let acceleration_structure = self.expression(args.next()?, ctx)?; let descriptor = self.expression(args.next()?, ctx)?; let payload = self.expression(args.next()?, ctx)?; args.finish()?; let _ = ctx.module.generate_ray_desc_type(); let fun = ir::RayPipelineFunction::TraceRay { acceleration_structure, descriptor, payload, }; let rctx = ctx.runtime_expression_ctx(function_span)?; rctx.block .extend(rctx.emitter.finish(&rctx.function.expressions)); rctx.emitter.start(&rctx.function.expressions); rctx.block .push(ir::Statement::RayPipelineFunction(fun), function_span); return Ok(None); } _ => return Err(Box::new(Error::UnknownIdent(function_span, function_name))), } }; let expr = ctx.append_expression(expr, function_span)?; Ok(Some((expr, must_use))) } /// Generate Naga IR for call expressions and statements, and type /// constructor expressions. /// /// The "function" being called is simply an `Ident` that we know refers to /// some module-scope definition. /// /// - If it is the name of a type, then the expression is a type constructor /// expression: either constructing a value from components, a conversion /// expression, or a zero value expression. /// /// - If it is the name of a function, then we're generating a [`Call`] /// statement. We may be in the midst of generating code for an /// expression, in which case we must generate an `Emit` statement to /// force evaluation of the IR expressions we've generated so far, add the /// `Call` statement to the current block, and then resume generating /// expressions. /// /// [`Call`]: ir::Statement::Call fn call( &mut self, call_phrase: &ast::CallPhrase<'source>, span: Span, ctx: &mut ExpressionContext<'source, '_, '_>, is_statement: bool, ) -> Result<'source, Option>> { let function_name = match call_phrase.function.ident { ast::IdentExpr::Unresolved(name) => name, ast::IdentExpr::Local(_) => { return Err(Box::new(Error::CalledLocalDecl( call_phrase.function.ident_span, ))) } }; let mut function_span = call_phrase.function.ident_span; function_span.subsume(call_phrase.function.template_list_span); let arguments = call_phrase.arguments.as_slice(); let mut tl = TemplateListIter::new(function_span, &call_phrase.function.template_list); let result = match ctx.globals.get(function_name) { Some(&LoweredGlobalDecl::Type(ty)) => { // user-declared types can't make use of template lists tl.finish(ctx)?; let handle = self.construct(span, Constructor::Type(ty), function_span, arguments, ctx)?; Some((handle, MustUse::Yes)) } Some( &LoweredGlobalDecl::Const(_) | &LoweredGlobalDecl::Override(_) | &LoweredGlobalDecl::Var(_), ) => { return Err(Box::new(Error::Unexpected( function_span, ExpectedToken::Function, ))) } Some(&LoweredGlobalDecl::EntryPoint(_)) => { return Err(Box::new(Error::CalledEntryPoint(function_span))); } Some(&LoweredGlobalDecl::Function { handle: function, must_use, }) => { // user-declared functions can't make use of template lists tl.finish(ctx)?; let arguments = arguments .iter() .enumerate() .map(|(i, &arg)| { // Try to convert abstract values to the known argument types let Some(&ir::FunctionArgument { ty: parameter_ty, .. }) = ctx.module.functions[function].arguments.get(i) else { // Wrong number of arguments... just concretize the type here // and let the validator report the error. return self.expression(arg, ctx); }; let expr = self.expression_for_abstract(arg, ctx)?; ctx.try_automatic_conversions( expr, &proc::TypeResolution::Handle(parameter_ty), ctx.ast_expressions.get_span(arg), ) }) .collect::>>()?; let has_result = ctx.module.functions[function].result.is_some(); let rctx = ctx.runtime_expression_ctx(span)?; // we need to always do this before a fn call since all arguments need to be emitted before the fn call rctx.block .extend(rctx.emitter.finish(&rctx.function.expressions)); let result = has_result.then(|| { let result = rctx .function .expressions .append(ir::Expression::CallResult(function), span); rctx.local_expression_kind_tracker .insert(result, proc::ExpressionKind::Runtime); (result, must_use.into()) }); rctx.emitter.start(&rctx.function.expressions); rctx.block.push( ir::Statement::Call { function, arguments, result: result.map(|(expr, _)| expr), }, span, ); result } None => { // If the name refers to a predeclared type, this is a construction expression. let ty = conv::map_predeclared_type( &ctx.enable_extensions, function_span, function_name, )?; if let Some(ty) = ty { let empty_template_list = call_phrase.function.template_list.is_empty(); let constructor_ty = match ty { conv::PredeclaredType::TypeGenerator(conv::TypeGenerator::Vector { size, }) if empty_template_list => Constructor::PartialVector { size }, conv::PredeclaredType::TypeGenerator(conv::TypeGenerator::Matrix { columns, rows, }) if empty_template_list => Constructor::PartialMatrix { columns, rows }, conv::PredeclaredType::TypeGenerator(conv::TypeGenerator::Array) if empty_template_list => { Constructor::PartialArray } conv::PredeclaredType::TypeGenerator( conv::TypeGenerator::CooperativeMatrix { .. }, ) if empty_template_list => { return Err(Box::new(Error::UnderspecifiedCooperativeMatrix)); } _ => Constructor::Type(self.finalize_type(ctx, ty, &mut tl, None)?), }; tl.finish(ctx)?; let handle = self.construct(span, constructor_ty, function_span, arguments, ctx)?; Some((handle, MustUse::Yes)) } else { // Otherwise, it must be a call to a builtin function. let result = self.call_builtin( function_name, function_span, arguments, &mut tl, span, ctx, is_statement, )?; tl.finish(ctx)?; result } } }; let result_used = !is_statement; if matches!(result, Some((_, MustUse::Yes))) && !result_used { return Err(Box::new(Error::FunctionMustUseUnused(function_span))); } Ok(result.map(|(expr, _)| expr)) } /// Generate a Naga IR [`Math`] expression. /// /// Generate Naga IR for a call to the [`MathFunction`] `fun`, whose /// unlowered arguments are `ast_arguments`. /// /// The `span` argument should give the span of the function name in the /// call expression. /// /// [`Math`]: ir::Expression::Math /// [`MathFunction`]: ir::MathFunction fn math_function_helper( &mut self, span: Span, fun: ir::MathFunction, ast_arguments: &[Handle>], ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, ir::Expression> { let mut lowered_arguments = Vec::with_capacity(ast_arguments.len()); for &arg in ast_arguments { let lowered = self.expression_for_abstract(arg, ctx)?; ctx.grow_types(lowered)?; lowered_arguments.push(lowered); } let fun_overloads = fun.overloads(); let rule = self.resolve_overloads(span, fun, fun_overloads, &lowered_arguments, ctx)?; self.apply_automatic_conversions_for_call(&rule, &mut lowered_arguments, ctx)?; // If this function returns a predeclared type, register it // in `Module::special_types`. The typifier will expect to // be able to find it there. if let proc::Conclusion::Predeclared(predeclared) = rule.conclusion { ctx.module.generate_predeclared_type(predeclared); } Ok(ir::Expression::Math { fun, arg: lowered_arguments[0], arg1: lowered_arguments.get(1).cloned(), arg2: lowered_arguments.get(2).cloned(), arg3: lowered_arguments.get(3).cloned(), }) } /// Choose the right overload for a function call. /// /// Return a [`Rule`] representing the most preferred overload in /// `overloads` to apply to `arguments`, or return an error explaining why /// the call is not valid. /// /// Use `fun` to identify the function being called in error messages; /// `span` should be the span of the function name in the call expression. /// /// [`Rule`]: proc::Rule fn resolve_overloads( &self, span: Span, fun: F, overloads: O, arguments: &[Handle], ctx: &ExpressionContext<'source, '_, '_>, ) -> Result<'source, proc::Rule> where O: proc::OverloadSet, F: TryToWgsl + core::fmt::Debug + Copy, { let mut remaining_overloads = overloads.clone(); let min_arguments = remaining_overloads.min_arguments(); let max_arguments = remaining_overloads.max_arguments(); if arguments.len() < min_arguments { return Err(Box::new(Error::WrongArgumentCount { span, expected: min_arguments as u32..max_arguments as u32, found: arguments.len() as u32, })); } if arguments.len() > max_arguments { return Err(Box::new(Error::TooManyArguments { function: fun.to_wgsl_for_diagnostics(), call_span: span, arg_span: ctx.get_expression_span(arguments[max_arguments]), max_arguments: max_arguments as _, })); } log::debug!( "Initial overloads: {:#?}", remaining_overloads.for_debug(&ctx.module.types) ); for (arg_index, &arg) in arguments.iter().enumerate() { let arg_type_resolution = &ctx.typifier()[arg]; let arg_inner = arg_type_resolution.inner_with(&ctx.module.types); log::debug!( "Supplying argument {arg_index} of type {:?}", arg_type_resolution.for_debug(&ctx.module.types) ); let next_remaining_overloads = remaining_overloads.arg(arg_index, arg_inner, &ctx.module.types); // If any argument is not a constant expression, then no overloads // that accept abstract values should be considered. // (`OverloadSet::concrete_only` is supposed to help impose this // restriction.) However, no `MathFunction` accepts a mix of // abstract and concrete arguments, so we don't need to worry // about that here. log::debug!( "Remaining overloads: {:#?}", next_remaining_overloads.for_debug(&ctx.module.types) ); // If the set of remaining overloads is empty, then this argument's type // was unacceptable. Diagnose the problem and produce an error message. if next_remaining_overloads.is_empty() { let function = fun.to_wgsl_for_diagnostics(); let call_span = span; let arg_span = ctx.get_expression_span(arg); let arg_ty = ctx.as_diagnostic_display(arg_type_resolution).to_string(); // Is this type *ever* permitted for the arg_index'th argument? // For example, `bool` is never permitted for `max`. let only_this_argument = overloads.arg(arg_index, arg_inner, &ctx.module.types); if only_this_argument.is_empty() { // No overload of `fun` accepts this type as the // arg_index'th argument. Determine the set of types that // would ever be allowed there. let allowed: Vec = overloads .allowed_args(arg_index, &ctx.module.to_ctx()) .iter() .map(|ty| ctx.type_resolution_to_string(ty)) .collect(); if allowed.is_empty() { // No overload of `fun` accepts any argument at this // index, so it's a simple case of excess arguments. // However, since each `MathFunction`'s overloads all // have the same arity, we should have detected this // earlier. unreachable!("expected all overloads to have the same arity"); } // Some overloads of `fun` do accept this many arguments, // but none accept one of this type. return Err(Box::new(Error::WrongArgumentType { function, call_span, arg_span, arg_index: arg_index as u32, arg_ty, allowed, })); } // This argument's type is accepted by some overloads---just // not those overloads that remain, given the prior arguments. // For example, `max` accepts `f32` as its second argument - // but not if the first was `i32`. // Build a list of the types that would have been accepted here, // given the prior arguments. let allowed: Vec = remaining_overloads .allowed_args(arg_index, &ctx.module.to_ctx()) .iter() .map(|ty| ctx.type_resolution_to_string(ty)) .collect(); // Re-run the argument list to determine which prior argument // made this one unacceptable. let mut remaining_overloads = overloads; for (prior_index, &prior_expr) in arguments.iter().enumerate() { let prior_type_resolution = &ctx.typifier()[prior_expr]; let prior_ty = prior_type_resolution.inner_with(&ctx.module.types); remaining_overloads = remaining_overloads.arg(prior_index, prior_ty, &ctx.module.types); if remaining_overloads .arg(arg_index, arg_inner, &ctx.module.types) .is_empty() { // This is the argument that killed our dreams. let inconsistent_span = ctx.get_expression_span(arguments[prior_index]); let inconsistent_ty = ctx.as_diagnostic_display(prior_type_resolution).to_string(); if allowed.is_empty() { // Some overloads did accept `ty` at `arg_index`, but // given the arguments up through `prior_expr`, we see // no types acceptable at `arg_index`. This means that some // overloads expect fewer arguments than others. However, // each `MathFunction`'s overloads have the same arity, so this // should be impossible. unreachable!("expected all overloads to have the same arity"); } // Report `arg`'s type as inconsistent with `prior_expr`'s return Err(Box::new(Error::InconsistentArgumentType { function, call_span, arg_span, arg_index: arg_index as u32, arg_ty, inconsistent_span, inconsistent_index: prior_index as u32, inconsistent_ty, allowed, })); } } unreachable!("Failed to eliminate argument type when re-tried"); } remaining_overloads = next_remaining_overloads; } // Select the most preferred type rule for this call, // given the argument types supplied above. Ok(remaining_overloads.most_preferred()) } /// Apply automatic type conversions for a function call. /// /// Apply whatever automatic conversions are needed to pass `arguments` to /// the function overload described by `rule`. Update `arguments` to refer /// to the converted arguments. fn apply_automatic_conversions_for_call( &self, rule: &proc::Rule, arguments: &mut [Handle], ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, ()> { for (i, argument) in arguments.iter_mut().enumerate() { let goal_inner = rule.arguments[i].inner_with(&ctx.module.types); let converted = match goal_inner.scalar_for_conversions(&ctx.module.types) { Some(goal_scalar) => { let arg_span = ctx.get_expression_span(*argument); ctx.try_automatic_conversion_for_leaf_scalar(*argument, goal_scalar, arg_span)? } // No conversion is necessary. None => *argument, }; *argument = converted; } Ok(()) } fn atomic_pointer( &mut self, expr: Handle>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, (Handle, ir::Scalar)> { let span = ctx.ast_expressions.get_span(expr); let pointer = self.expression(expr, ctx)?; match *resolve_inner!(ctx, pointer) { ir::TypeInner::Pointer { base, .. } => match ctx.module.types[base].inner { ir::TypeInner::Atomic(scalar) => Ok((pointer, scalar)), ref other => { log::error!("Pointer type to {other:?} passed to atomic op"); Err(Box::new(Error::InvalidAtomicPointer(span))) } }, ref other => { log::error!("Type {other:?} passed to atomic op"); Err(Box::new(Error::InvalidAtomicPointer(span))) } } } fn atomic_helper( &mut self, span: Span, fun: ir::AtomicFunction, args: &[Handle>], is_statement: bool, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Option>> { let mut args = ctx.prepare_args(args, 2, span); let (pointer, scalar) = self.atomic_pointer(args.next()?, ctx)?; let value = self.expression_with_leaf_scalar(args.next()?, scalar, ctx)?; let value_inner = resolve_inner!(ctx, value); args.finish()?; // If we don't use the return value of a 64-bit `min` or `max` // operation, generate a no-result form of the `Atomic` statement, so // that we can pass validation with only `SHADER_INT64_ATOMIC_MIN_MAX` // whenever possible. let is_64_bit_min_max = matches!(fun, ir::AtomicFunction::Min | ir::AtomicFunction::Max) && matches!( *value_inner, ir::TypeInner::Scalar(ir::Scalar { width: 8, .. }) ); let result = if is_64_bit_min_max && is_statement { let rctx = ctx.runtime_expression_ctx(span)?; rctx.block .extend(rctx.emitter.finish(&rctx.function.expressions)); rctx.emitter.start(&rctx.function.expressions); None } else { let ty = ctx.register_type(value)?; Some(ctx.interrupt_emitter( ir::Expression::AtomicResult { ty, comparison: false, }, span, )?) }; let rctx = ctx.runtime_expression_ctx(span)?; rctx.block.push( ir::Statement::Atomic { pointer, fun, value, result, }, span, ); Ok(result) } fn texture_sample_helper( &mut self, fun: Texture, args: &[Handle>], span: Span, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, ir::Expression> { let mut args = ctx.prepare_args(args, fun.min_argument_count(), span); fn get_image_and_span<'source>( lowerer: &mut Lowerer<'source, '_>, args: &mut ArgumentContext<'_, 'source>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, (Handle, Span)> { let image = args.next()?; let image_span = ctx.ast_expressions.get_span(image); let image = lowerer.expression_for_abstract(image, ctx)?; Ok((image, image_span)) } let image; let image_span; let gather; match fun { Texture::Gather => { let image_or_component = args.next()?; let image_or_component_span = ctx.ast_expressions.get_span(image_or_component); // Gathers from depth textures don't take an initial `component` argument. let lowered_image_or_component = self.expression(image_or_component, ctx)?; match *resolve_inner!(ctx, lowered_image_or_component) { ir::TypeInner::Image { class: ir::ImageClass::Depth { .. }, .. } => { image = lowered_image_or_component; image_span = image_or_component_span; gather = Some(ir::SwizzleComponent::X); } _ => { (image, image_span) = get_image_and_span(self, &mut args, ctx)?; gather = Some(ctx.gather_component( lowered_image_or_component, image_or_component_span, span, )?); } } } Texture::GatherCompare => { (image, image_span) = get_image_and_span(self, &mut args, ctx)?; gather = Some(ir::SwizzleComponent::X); } _ => { (image, image_span) = get_image_and_span(self, &mut args, ctx)?; gather = None; } }; let sampler = self.expression_for_abstract(args.next()?, ctx)?; let coordinate = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?; let clamp_to_edge = matches!(fun, Texture::SampleBaseClampToEdge); let (class, arrayed) = ctx.image_data(image, image_span)?; let array_index = arrayed .then(|| self.expression(args.next()?, ctx)) .transpose()?; let level; let depth_ref; match fun { Texture::Gather => { level = ir::SampleLevel::Zero; depth_ref = None; } Texture::GatherCompare => { let reference = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?; level = ir::SampleLevel::Zero; depth_ref = Some(reference); } Texture::Sample => { level = ir::SampleLevel::Auto; depth_ref = None; } Texture::SampleBias => { let bias = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?; level = ir::SampleLevel::Bias(bias); depth_ref = None; } Texture::SampleCompare => { let reference = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?; level = ir::SampleLevel::Auto; depth_ref = Some(reference); } Texture::SampleCompareLevel => { let reference = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?; level = ir::SampleLevel::Zero; depth_ref = Some(reference); } Texture::SampleGrad => { let x = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?; let y = self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)?; level = ir::SampleLevel::Gradient { x, y }; depth_ref = None; } Texture::SampleLevel => { let exact = match class { // When applied to depth textures, `textureSampleLevel`'s // `level` argument is an `i32` or `u32`. ir::ImageClass::Depth { .. } => self.expression(args.next()?, ctx)?, // When applied to other sampled types, its `level` argument // is an `f32`. ir::ImageClass::Sampled { .. } => { self.expression_with_leaf_scalar(args.next()?, ir::Scalar::F32, ctx)? } // Sampling `External` textures with a specified level isn't // allowed, and sampling `Storage` textures isn't allowed at // all. Let the validator report the error. ir::ImageClass::Storage { .. } | ir::ImageClass::External => { self.expression(args.next()?, ctx)? } }; level = ir::SampleLevel::Exact(exact); depth_ref = None; } Texture::SampleBaseClampToEdge => { level = crate::SampleLevel::Zero; depth_ref = None; } }; let offset = args .next() .map(|arg| self.expression_with_leaf_scalar(arg, ir::Scalar::I32, &mut ctx.as_const())) .ok() .transpose()?; args.finish()?; Ok(ir::Expression::ImageSample { image, sampler, gather, coordinate, array_index, offset, level, depth_ref, clamp_to_edge, }) } fn subgroup_operation_helper( &mut self, span: Span, op: ir::SubgroupOperation, collective_op: ir::CollectiveOperation, arguments: &[Handle>], ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Handle> { let mut args = ctx.prepare_args(arguments, 1, span); let argument = self.expression(args.next()?, ctx)?; args.finish()?; let ty = ctx.register_type(argument)?; let result = ctx.interrupt_emitter(ir::Expression::SubgroupOperationResult { ty }, span)?; let rctx = ctx.runtime_expression_ctx(span)?; rctx.block.push( ir::Statement::SubgroupCollectiveOperation { op, collective_op, argument, result, }, span, ); Ok(result) } fn subgroup_gather_helper( &mut self, span: Span, mode: SubgroupGather, arguments: &[Handle>], ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Handle> { let mut args = ctx.prepare_args(arguments, 2, span); let argument = self.expression(args.next()?, ctx)?; use SubgroupGather as Sg; let mode = if let Sg::BroadcastFirst = mode { ir::GatherMode::BroadcastFirst } else { let index = self.expression(args.next()?, ctx)?; match mode { Sg::BroadcastFirst => unreachable!(), Sg::Broadcast => ir::GatherMode::Broadcast(index), Sg::Shuffle => ir::GatherMode::Shuffle(index), Sg::ShuffleDown => ir::GatherMode::ShuffleDown(index), Sg::ShuffleUp => ir::GatherMode::ShuffleUp(index), Sg::ShuffleXor => ir::GatherMode::ShuffleXor(index), Sg::QuadBroadcast => ir::GatherMode::QuadBroadcast(index), } }; args.finish()?; let ty = ctx.register_type(argument)?; let result = ctx.interrupt_emitter(ir::Expression::SubgroupOperationResult { ty }, span)?; let rctx = ctx.runtime_expression_ctx(span)?; rctx.block.push( ir::Statement::SubgroupGather { mode, argument, result, }, span, ); Ok(result) } fn r#struct( &mut self, s: &ast::Struct<'source>, span: Span, ctx: &mut GlobalContext<'source, '_, '_>, ) -> Result<'source, Handle> { let mut offset = 0; let mut struct_alignment = proc::Alignment::ONE; let mut members = Vec::with_capacity(s.members.len()); let mut doc_comments: Vec>> = Vec::new(); for member in s.members.iter() { let ty = self.resolve_ast_type(&member.ty, &mut ctx.as_const())?; ctx.layouter.update(ctx.module.to_ctx()).map_err(|err| { let LayoutErrorInner::TooLarge = err.inner else { unreachable!("unexpected layout error: {err:?}"); }; // Since anonymous types of struct members don't get a span, // associate the error with the member. The layouter could have // failed on any type that was pending layout, but if it wasn't // the current struct member, it wasn't a struct member at all, // because we resolve struct members one-by-one. if ty == err.ty { Box::new(Error::StructMemberTooLarge { member_name_span: member.name.span, }) } else { // Lots of type definitions don't get spans, so this error // message may not be very useful. Box::new(Error::TypeTooLarge { span: ctx.module.types.get_span(err.ty), }) } })?; let member_min_size = ctx.layouter[ty].size; let member_min_alignment = ctx.layouter[ty].alignment; let member_size = if let Some(size_expr) = member.size { let (size, span) = self.const_u32(size_expr, &mut ctx.as_const())?; if size < member_min_size { return Err(Box::new(Error::SizeAttributeTooLow(span, member_min_size))); } else { size } } else { member_min_size }; let member_alignment = if let Some(align_expr) = member.align { let (align, span) = self.const_u32(align_expr, &mut ctx.as_const())?; if let Some(alignment) = proc::Alignment::new(align) { if alignment < member_min_alignment { return Err(Box::new(Error::AlignAttributeTooLow( span, member_min_alignment, ))); } else { alignment } } else { return Err(Box::new(Error::NonPowerOfTwoAlignAttribute(span))); } } else { member_min_alignment }; let binding = self.binding(&member.binding, ty, ctx)?; offset = member_alignment.round_up(offset); struct_alignment = struct_alignment.max(member_alignment); if !member.doc_comments.is_empty() { doc_comments.push(Some( member.doc_comments.iter().map(|s| s.to_string()).collect(), )); } members.push(ir::StructMember { name: Some(member.name.name.to_owned()), ty, binding, offset, }); offset += member_size; if offset > crate::valid::MAX_TYPE_SIZE { return Err(Box::new(Error::TypeTooLarge { span })); } } let size = struct_alignment.round_up(offset); let inner = ir::TypeInner::Struct { members, span: size, }; let handle = ctx.module.types.insert( ir::Type { name: Some(s.name.name.to_string()), inner, }, span, ); for (i, c) in doc_comments.drain(..).enumerate() { if let Some(comment) = c { ctx.module .get_or_insert_default_doc_comments() .struct_members .insert((handle, i), comment); } } Ok(handle) } fn const_u32( &mut self, expr: Handle>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, (u32, Span)> { let span = ctx.ast_expressions.get_span(expr); let expr = self.expression(expr, ctx)?; let value = ctx .module .to_ctx() .get_const_val(expr) .map_err(|err| match err { proc::ConstValueError::NonConst | proc::ConstValueError::InvalidType => { Error::ExpectedConstExprConcreteIntegerScalar(span) } proc::ConstValueError::Negative => Error::ExpectedNonNegative(span), })?; Ok((value, span)) } fn array_size( &mut self, expr: Handle>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, ir::ArraySize> { let span = ctx.ast_expressions.get_span(expr); let const_ctx = &mut ctx.as_const(); let const_expr = self.expression(expr, const_ctx); match const_expr { Ok(value) => { let len = const_ctx.get_const_val(value).map_err(|err| { Box::new(match err { proc::ConstValueError::NonConst | proc::ConstValueError::InvalidType => { Error::ExpectedConstExprConcreteIntegerScalar(span) } proc::ConstValueError::Negative => Error::ExpectedPositiveArrayLength(span), }) })?; let size = NonZeroU32::new(len).ok_or(Error::ExpectedPositiveArrayLength(span))?; Ok(ir::ArraySize::Constant(size)) } Err(err) => { // If the error is simply that `expr` was an override expression, then we // can represent that as an array length. let Error::ConstantEvaluatorError(ref ty, _) = *err else { return Err(err); }; let proc::ConstantEvaluatorError::OverrideExpr = **ty else { return Err(err); }; Ok(ir::ArraySize::Pending(self.array_size_override( expr, &mut ctx.as_global().as_override(), span, )?)) } } } fn array_size_override( &mut self, size_expr: Handle>, ctx: &mut ExpressionContext<'source, '_, '_>, span: Span, ) -> Result<'source, Handle> { let expr = self.expression(size_expr, ctx)?; match resolve_inner!(ctx, expr).scalar_kind().ok_or(0) { Ok(ir::ScalarKind::Sint) | Ok(ir::ScalarKind::Uint) => Ok({ if let ir::Expression::Override(handle) = ctx.module.global_expressions[expr] { handle } else { let ty = ctx.register_type(expr)?; ctx.module.overrides.append( ir::Override { name: None, id: None, ty, init: Some(expr), }, span, ) } }), _ => Err(Box::new(Error::ExpectedConstExprConcreteIntegerScalar( span, ))), } } /// Build the Naga equivalent of a named AST type. /// /// Return a Naga `Handle` representing the front-end type /// `handle`, which should be named `name`, if given. /// /// If `handle` refers to a type cached in [`SpecialTypes`], /// `name` may be ignored. /// /// [`SpecialTypes`]: ir::SpecialTypes fn resolve_named_ast_type( &mut self, ident: &ast::TemplateElaboratedIdent<'source>, name: String, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Handle> { self.type_specifier(ident, ctx, Some(name)) } /// Return a Naga `Handle` representing the front-end type `handle`. fn resolve_ast_type( &mut self, ident: &ast::TemplateElaboratedIdent<'source>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Handle> { self.type_specifier(ident, ctx, None) } fn binding( &mut self, binding: &Option>, ty: Handle, ctx: &mut GlobalContext<'source, '_, '_>, ) -> Result<'source, Option> { Ok(match *binding { Some(ast::Binding::BuiltIn(b)) => Some(ir::Binding::BuiltIn(b)), Some(ast::Binding::Location { location, interpolation, sampling, blend_src, per_primitive, }) => { let blend_src = if let Some(blend_src) = blend_src { Some(self.const_u32(blend_src, &mut ctx.as_const())?.0) } else { None }; let mut binding = ir::Binding::Location { location: self.const_u32(location, &mut ctx.as_const())?.0, interpolation, sampling, blend_src, per_primitive, }; binding.apply_default_interpolation(&ctx.module.types[ty].inner); Some(binding) } None => None, }) } fn ray_query_pointer( &mut self, expr: Handle>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Handle> { let span = ctx.ast_expressions.get_span(expr); let pointer = self.expression(expr, ctx)?; match *resolve_inner!(ctx, pointer) { ir::TypeInner::Pointer { base, .. } => match ctx.module.types[base].inner { ir::TypeInner::RayQuery { .. } => Ok(pointer), ref other => { log::error!("Pointer type to {other:?} passed to ray query op"); Err(Box::new(Error::InvalidRayQueryPointer(span))) } }, ref other => { log::error!("Type {other:?} passed to ray query op"); Err(Box::new(Error::InvalidRayQueryPointer(span))) } } } } impl ir::AtomicFunction { pub fn map(word: &str) -> Option { Some(match word { "atomicAdd" => ir::AtomicFunction::Add, "atomicSub" => ir::AtomicFunction::Subtract, "atomicAnd" => ir::AtomicFunction::And, "atomicOr" => ir::AtomicFunction::InclusiveOr, "atomicXor" => ir::AtomicFunction::ExclusiveOr, "atomicMin" => ir::AtomicFunction::Min, "atomicMax" => ir::AtomicFunction::Max, "atomicExchange" => ir::AtomicFunction::Exchange { compare: None }, _ => return None, }) } } ================================================ FILE: naga/src/front/wgsl/lower/template_list.rs ================================================ use alloc::{boxed::Box, vec::Vec}; use crate::{ front::wgsl::{ error::Error, lower::{ExpressionContext, Lowerer, Result}, parse::{ast, conv}, }, ir, Handle, Span, }; /// Iterator over a template list. /// /// All functions will attempt to consume an element in the list. /// /// Function variants prefixed with "maybe" will not return an error if there /// are no more elements left in the list. pub struct TemplateListIter<'iter, 'source> { ident_span: Span, template_list: core::slice::Iter<'iter, Handle>>, } impl<'iter, 'source> TemplateListIter<'iter, 'source> { pub fn new(ident_span: Span, template_list: &'iter [Handle>]) -> Self { Self { ident_span, template_list: template_list.iter(), } } pub fn finish(self, ctx: &ExpressionContext<'source, '_, '_>) -> Result<'source, ()> { let unused_args: Vec = self .template_list .map(|expr| ctx.ast_expressions.get_span(*expr)) .collect(); if unused_args.is_empty() { Ok(()) } else { Err(Box::new(Error::UnusedArgsForTemplate(unused_args))) } } fn expect_next( &mut self, description: &'static str, ) -> Result<'source, Handle>> { if let Some(expr) = self.template_list.next() { Ok(*expr) } else { Err(Box::new(Error::MissingTemplateArg { span: self.ident_span, description, })) } } pub fn ty( &mut self, lowerer: &mut Lowerer<'source, '_>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Handle> { let expr = self.expect_next("`T`, a type")?; lowerer.type_expression(expr, ctx) } /// Lower the next template list element as a type, and return its span. /// /// This returns the span of the template list element. This is generally /// different from the span of the returned `Handle`, as the /// latter may refer to the type's definition, not its use in the template list. pub fn ty_with_span( &mut self, lowerer: &mut Lowerer<'source, '_>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, (Handle, Span)> { let expr = self.expect_next("`T`, a type")?; let span = ctx.ast_expressions.get_span(expr); let ty = lowerer.type_expression(expr, ctx)?; Ok((ty, span)) } pub fn scalar_ty( &mut self, lowerer: &mut Lowerer<'source, '_>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, (ir::Scalar, Span)> { let expr = self.expect_next("`T`, a scalar type")?; let ty = lowerer.type_expression(expr, ctx)?; let span = ctx.ast_expressions.get_span(expr); match ctx.module.types[ty].inner { ir::TypeInner::Scalar(scalar) => Ok((scalar, span)), _ => Err(Box::new(Error::UnknownScalarType(span))), } } pub fn maybe_array_size( &mut self, lowerer: &mut Lowerer<'source, '_>, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, ir::ArraySize> { if let Some(expr) = self.template_list.next() { lowerer.array_size(*expr, ctx) } else { Ok(ir::ArraySize::Dynamic) } } pub fn address_space( &mut self, ctx: &ExpressionContext<'source, '_, '_>, ) -> Result<'source, ir::AddressSpace> { let expr = self.expect_next("`AS`, an address space")?; let (enumerant, span) = ctx.enumerant(expr)?; conv::map_address_space(enumerant, span, &ctx.enable_extensions) } pub fn maybe_address_space( &mut self, ctx: &ExpressionContext<'source, '_, '_>, ) -> Result<'source, Option> { let Some(expr) = self.template_list.next() else { return Ok(None); }; let (enumerant, span) = ctx.enumerant(*expr)?; Ok(Some(conv::map_address_space( enumerant, span, &ctx.enable_extensions, )?)) } pub fn access_mode( &mut self, ctx: &ExpressionContext<'source, '_, '_>, ) -> Result<'source, ir::StorageAccess> { let expr = self.expect_next("`Access`, an access mode")?; let (enumerant, span) = ctx.enumerant(expr)?; conv::map_access_mode(enumerant, span) } /// Update `space` with an access mode from `self`, if appropriate. /// /// If `space` is [`Storage`], and there is another template parameter in /// `self`, parse it as a storage mode and mutate `space` accordingly. If /// there are no more template parameters, treat that like `read`. /// /// If `space` is some other address space, don't consume any template /// parameters. /// /// [`Storage`]: ir::AddressSpace::Storage pub fn maybe_access_mode( &mut self, space: &mut ir::AddressSpace, ctx: &ExpressionContext<'source, '_, '_>, ) -> Result<'source, ()> { if let &mut ir::AddressSpace::Storage { ref mut access } = space { if let Some(expr) = self.template_list.next() { let (enumerant, span) = ctx.enumerant(*expr)?; let access_mode = conv::map_access_mode(enumerant, span)?; *access = access_mode; } else { // defaulting to `read` *access = ir::StorageAccess::LOAD } } Ok(()) } pub fn storage_format( &mut self, ctx: &ExpressionContext<'source, '_, '_>, ) -> Result<'source, ir::StorageFormat> { let expr = self.expect_next("`Format`, a texel format")?; let (enumerant, span) = ctx.enumerant(expr)?; conv::map_storage_format(enumerant, span) } pub fn maybe_vertex_return( &mut self, ctx: &ExpressionContext<'source, '_, '_>, ) -> Result<'source, bool> { let Some(expr) = self.template_list.next() else { return Ok(false); }; let (enumerant, span) = ctx.enumerant(*expr)?; conv::map_ray_flag(&ctx.enable_extensions, enumerant, span)?; Ok(true) } pub fn cooperative_role( &mut self, ctx: &ExpressionContext<'source, '_, '_>, ) -> Result<'source, crate::CooperativeRole> { let role_expr = self.expect_next("`Role`, a cooperative matrix role")?; let (enumerant, span) = ctx.enumerant(role_expr)?; let role = conv::map_cooperative_role(enumerant, span)?; Ok(role) } } ================================================ FILE: naga/src/front/wgsl/mod.rs ================================================ /*! Frontend for [WGSL][wgsl] (WebGPU Shading Language). [wgsl]: https://gpuweb.github.io/gpuweb/wgsl.html */ mod error; mod index; mod lower; mod parse; #[cfg(test)] mod tests; pub use parse::directive::enable_extension::{ EnableExtension, ImplementedEnableExtension, UnimplementedEnableExtension, }; pub use crate::front::wgsl::error::ParseError; pub use crate::front::wgsl::parse::directive::language_extension::{ ImplementedLanguageExtension, LanguageExtension, UnimplementedLanguageExtension, }; pub use crate::front::wgsl::parse::Options; use alloc::boxed::Box; use thiserror::Error; use crate::front::wgsl::error::Error; use crate::front::wgsl::lower::Lowerer; use crate::front::wgsl::parse::Parser; use crate::Scalar; #[cfg(test)] use std::println; pub(crate) type Result<'a, T> = core::result::Result>>; pub struct Frontend { parser: Parser, options: Options, } impl Frontend { pub const fn new() -> Self { Self { parser: Parser::new(), options: Options::new(), } } pub const fn new_with_options(options: Options) -> Self { Self { parser: Parser::new(), options, } } pub const fn set_options(&mut self, options: Options) { self.options = options; } pub fn parse(&mut self, source: &str) -> core::result::Result { self.inner(source).map_err(|x| x.as_parse_error(source)) } fn inner<'a>(&mut self, source: &'a str) -> Result<'a, crate::Module> { let tu = self.parser.parse(source, &self.options)?; let index = index::Index::generate(&tu)?; let module = Lowerer::new(&index).lower(tu)?; Ok(module) } } ///

pub fn parse_str(source: &str) -> core::result::Result { Frontend::new().parse(source) } #[cfg(test)] #[track_caller] pub fn assert_parse_err(input: &str, snapshot: &str) { let output = parse_str(input) .expect_err("expected parser error") .emit_to_string(input); if output != snapshot { for diff in diff::lines(snapshot, &output) { match diff { diff::Result::Left(l) => println!("-{l}"), diff::Result::Both(l, _) => println!(" {l}"), diff::Result::Right(r) => println!("+{r}"), } } panic!("Error snapshot failed"); } } ================================================ FILE: naga/src/front/wgsl/parse/ast.rs ================================================ use alloc::vec::Vec; use core::hash::Hash; use crate::diagnostic_filter::DiagnosticFilterNode; use crate::front::wgsl::parse::directive::enable_extension::EnableExtensions; use crate::front::wgsl::parse::number::Number; use crate::{Arena, FastIndexSet, Handle, Span}; #[derive(Debug, Default)] pub struct TranslationUnit<'a> { pub enable_extensions: EnableExtensions, pub decls: Arena>, /// The common expressions arena for the entire translation unit. /// /// All functions, global initializers, array lengths, etc. store their /// expressions here. We apportion these out to individual Naga /// [`Function`]s' expression arenas at lowering time. Keeping them all in a /// single arena simplifies handling of things like array lengths (which are /// effectively global and thus don't clearly belong to any function) and /// initializers (which can appear in both function-local and module-scope /// contexts). /// /// [`Function`]: crate::Function pub expressions: Arena>, /// Arena for all diagnostic filter rules parsed in this module, including those in functions. /// /// See [`DiagnosticFilterNode`] for details on how the tree is represented and used in /// validation. pub diagnostic_filters: Arena, /// The leaf of all `diagnostic(…)` directives in this module. /// /// See [`DiagnosticFilterNode`] for details on how the tree is represented and used in /// validation. pub diagnostic_filter_leaf: Option>, /// Doc comments appearing first in the file. /// This serves as documentation for the whole TranslationUnit. pub doc_comments: Vec<&'a str>, } #[derive(Debug, Clone, Copy)] pub struct Ident<'a> { pub name: &'a str, pub span: Span, } /// An identifier that [resolves] to some declaration. /// /// This does not cover context-dependent names: attributes, built-in values, /// and so on. We map those to their Naga IR equivalents as soon as they're /// parsed, so they never need to appear as identifiers in the AST. /// /// [resolves]: https://gpuweb.github.io/gpuweb/wgsl/#resolves #[derive(Debug)] pub enum IdentExpr<'a> { /// An identifier referring to a module-scope declaration or predeclared /// object. /// /// We need to collect the entire module before we can resolve this, to /// distinguish between predeclared objects and module-scope declarations /// that appear after their uses. /// /// Whenever you create one of these values, you almost certainly want to /// insert the `&str` into [`ExpressionContext::unresolved`][ECu], to ensure /// that [indexing] knows that the name's declaration must be lowered before /// the one containing this use. Using [`Parser::ident_expr`][ie] to build /// `IdentExpr` will take care of that for you. /// /// [ECu]: super::ExpressionContext::unresolved /// [ie]: super::Parser::ident_expr /// [indexing]: crate::front::wgsl::index::Index::generate Unresolved(&'a str), /// An identifier that has been resolved to a non-module-scope declaration. Local(Handle), } /// An identifier with optional template parameters. /// /// Following the WGSL specification (see the [`template_list`] non-terminal), /// `TemplateElaboratedIdent` represents all template parameters as expressions: /// even parameters to type generators, like the `f32` in `vec3`, are [Type /// Expressions]. /// /// # Examples /// /// - A use of a global variable `colors` would be an [`Expression::Ident(v)`][EI], /// where `v` is an `TemplateElaboratedIdent` whose `ident` is /// [`IdentExpr::Unresolved("colors")`][IEU]. Lowering will resolve this to a /// reference to the global variable. /// /// - The type `f32` in a variable declaration is represented as a /// `TemplateElaboratedIdent` whose `ident` is /// [`IdentExpr::Unresolved("f32")`][IEU]. Lowering will resolve this to /// WGSL's predeclared `f32` type. /// /// - The type `vec3` can be represented as a `TemplateElaboratedIdent` /// whose `ident` is [`IdentExpr::Unresolved("vec3")`][IEU], and whose /// `template_list` has one element: an [`ExpressionIdent(v)`][EI] where `v` is a /// nested `TemplateElaboratedIdent` representing `f32` as described above. /// /// - The type `array, 4>` has `"array"` as its `ident`, and then /// a two-element `template_list`: /// /// - `template_list[0]` is an [`Expression::Ident(v)`][EI] where `v` is a nested /// `TemplateElaboratedIdent` representing `vec3` as described above. /// /// - `template_list[1]` is an [`Expression`] representing `4`. /// /// After [indexing] the module to ensure that declarations appear before uses, /// lowering can see which declaration a given `TemplateElaboratedIdent`s /// `ident` refers to. The declaration then determines how to interpret the /// `template_list`. /// /// [`template_list`]: https://gpuweb.github.io/gpuweb/wgsl/#syntax-template_list /// [Type Expressions]: https://gpuweb.github.io/gpuweb/wgsl/#type-expr /// [IEU]: IdentExpr::Unresolved /// [EI]: Expression::Ident /// [indexing]: crate::front::wgsl::index::Index::generate #[derive(Debug)] pub struct TemplateElaboratedIdent<'a> { pub ident: IdentExpr<'a>, pub ident_span: Span, /// If non-empty, the template parameters following the identifier. pub template_list: Vec>>, pub template_list_span: Span, } /// A function call or value constructor expression. /// /// We can't tell whether an expression like `IDENTIFIER(EXPR, ...)` is a /// construction expression or a function call until we know `IDENTIFIER`'s /// definition, so we represent everything of that form as one of these /// expressions until lowering. At that point, [`Lowerer::call`] has /// everything's definition in hand, and can decide whether to emit a Naga /// [`Constant`], [`As`], [`Splat`], or [`Compose`] expression. /// /// [`Lowerer::call`]: Lowerer::call /// [`Constant`]: crate::Expression::Constant /// [`As`]: crate::Expression::As /// [`Splat`]: crate::Expression::Splat /// [`Compose`]: crate::Expression::Compose #[derive(Debug)] pub struct CallPhrase<'a> { pub function: TemplateElaboratedIdent<'a>, pub arguments: Vec>>, } /// A reference to a module-scope definition or predeclared object. /// /// Each [`GlobalDecl`] holds a set of these values, to be resolved to /// specific definitions later. To support de-duplication, `Eq` and /// `Hash` on a `Dependency` value consider only the name, not the /// source location at which the reference occurs. #[derive(Debug)] pub struct Dependency<'a> { /// The name referred to. pub ident: &'a str, /// The location at which the reference to that name occurs. pub usage: Span, } impl Hash for Dependency<'_> { fn hash(&self, state: &mut H) { self.ident.hash(state); } } impl PartialEq for Dependency<'_> { fn eq(&self, other: &Self) -> bool { self.ident == other.ident } } impl Eq for Dependency<'_> {} /// A module-scope declaration. #[derive(Debug)] pub struct GlobalDecl<'a> { pub kind: GlobalDeclKind<'a>, /// Names of all module-scope or predeclared objects this /// declaration uses. pub dependencies: FastIndexSet>, } #[derive(Debug)] pub enum GlobalDeclKind<'a> { Fn(Function<'a>), Var(GlobalVariable<'a>), Const(Const<'a>), Override(Override<'a>), Struct(Struct<'a>), Type(TypeAlias<'a>), ConstAssert(Handle>), } #[derive(Debug)] pub struct FunctionArgument<'a> { pub name: Ident<'a>, pub ty: TemplateElaboratedIdent<'a>, pub binding: Option>, pub handle: Handle, } #[derive(Debug)] pub struct FunctionResult<'a> { pub ty: TemplateElaboratedIdent<'a>, pub binding: Option>, pub must_use: bool, } #[derive(Debug)] pub struct EntryPoint<'a> { pub stage: crate::ShaderStage, pub early_depth_test: Option, pub workgroup_size: Option<[Option>>; 3]>, pub mesh_output_variable: Option<(&'a str, Span)>, pub task_payload: Option<(&'a str, Span)>, pub ray_incoming_payload: Option<(&'a str, Span)>, } #[cfg(doc)] use crate::front::wgsl::lower::{LocalExpressionContext, StatementContext}; #[derive(Debug)] pub struct Function<'a> { pub entry_point: Option>, pub name: Ident<'a>, pub arguments: Vec>, pub result: Option>, pub body: Block<'a>, pub diagnostic_filter_leaf: Option>, pub doc_comments: Vec<&'a str>, } #[derive(Debug)] pub enum Binding<'a> { BuiltIn(crate::BuiltIn), Location { location: Handle>, interpolation: Option, sampling: Option, blend_src: Option>>, per_primitive: bool, }, } #[derive(Debug)] pub struct ResourceBinding<'a> { pub group: Handle>, pub binding: Handle>, } #[derive(Debug)] pub struct GlobalVariable<'a> { pub name: Ident<'a>, /// The template list parameters for the `var`, giving the variable's /// address space and access mode, if present. pub template_list: Vec>>, /// The `@group` and `@binding` attributes, if present. pub binding: Option>, pub ty: Option>, pub init: Option>>, pub doc_comments: Vec<&'a str>, /// Memory decorations for this variable (`@coherent`, `@volatile`). pub memory_decorations: crate::MemoryDecorations, } #[derive(Debug)] pub struct StructMember<'a> { pub name: Ident<'a>, pub ty: TemplateElaboratedIdent<'a>, pub binding: Option>, pub align: Option>>, pub size: Option>>, pub doc_comments: Vec<&'a str>, } #[derive(Debug)] pub struct Struct<'a> { pub name: Ident<'a>, pub members: Vec>, pub doc_comments: Vec<&'a str>, } #[derive(Debug)] pub struct TypeAlias<'a> { pub name: Ident<'a>, pub ty: TemplateElaboratedIdent<'a>, } #[derive(Debug)] pub struct Const<'a> { pub name: Ident<'a>, pub ty: Option>, pub init: Handle>, pub doc_comments: Vec<&'a str>, } #[derive(Debug)] pub struct Override<'a> { pub name: Ident<'a>, pub id: Option>>, pub ty: Option>, pub init: Option>>, } #[derive(Debug, Default)] pub struct Block<'a> { pub stmts: Vec>, } #[derive(Debug)] pub struct Statement<'a> { pub kind: StatementKind<'a>, pub span: Span, } #[derive(Debug)] pub enum StatementKind<'a> { LocalDecl(LocalDecl<'a>), Block(Block<'a>), If { condition: Handle>, accept: Block<'a>, reject: Block<'a>, }, Switch { selector: Handle>, cases: Vec>, }, Loop { body: Block<'a>, continuing: Block<'a>, break_if: Option>>, }, Break, Continue, Return { value: Option>>, }, Kill, Call(CallPhrase<'a>), Assign { target: Handle>, op: Option, value: Handle>, }, Increment(Handle>), Decrement(Handle>), Phony(Handle>), ConstAssert(Handle>), } #[derive(Debug)] pub enum SwitchValue<'a> { Expr(Handle>), Default, } #[derive(Debug)] pub struct SwitchCase<'a> { pub value: SwitchValue<'a>, pub body: Block<'a>, pub fall_through: bool, } #[derive(Debug, Copy, Clone)] pub enum Literal { Bool(bool), Number(Number), } #[cfg(doc)] use crate::front::wgsl::lower::Lowerer; #[derive(Debug)] pub enum Expression<'a> { Literal(Literal), Ident(TemplateElaboratedIdent<'a>), Unary { op: crate::UnaryOperator, expr: Handle>, }, AddrOf(Handle>), Deref(Handle>), Binary { op: crate::BinaryOperator, left: Handle>, right: Handle>, }, Call(CallPhrase<'a>), Index { base: Handle>, index: Handle>, }, Member { base: Handle>, field: Ident<'a>, }, } #[derive(Debug)] pub struct LocalVariable<'a> { pub name: Ident<'a>, pub ty: Option>, pub init: Option>>, pub handle: Handle, } #[derive(Debug)] pub struct Let<'a> { pub name: Ident<'a>, pub ty: Option>, pub init: Handle>, pub handle: Handle, } #[derive(Debug)] pub struct LocalConst<'a> { pub name: Ident<'a>, pub ty: Option>, pub init: Handle>, pub handle: Handle, } #[derive(Debug)] pub enum LocalDecl<'a> { Var(LocalVariable<'a>), Let(Let<'a>), Const(LocalConst<'a>), } #[derive(Debug)] /// A placeholder for a local variable declaration. /// /// See [`super::ExpressionContext::locals`] for more information. pub struct Local; ================================================ FILE: naga/src/front/wgsl/parse/conv.rs ================================================ use crate::front::wgsl::parse::directive::enable_extension::{ EnableExtensions, ImplementedEnableExtension, }; use crate::front::wgsl::{Error, Result, Scalar}; use crate::{ImageClass, ImageDimension, Span, TypeInner, VectorSize}; use alloc::boxed::Box; pub fn map_address_space<'a>( word: &str, span: Span, enable_extensions: &EnableExtensions, ) -> Result<'a, crate::AddressSpace> { match word { "private" => Ok(crate::AddressSpace::Private), "workgroup" => Ok(crate::AddressSpace::WorkGroup), "uniform" => Ok(crate::AddressSpace::Uniform), "storage" => Ok(crate::AddressSpace::Storage { access: crate::StorageAccess::default(), }), "immediate" => Ok(crate::AddressSpace::Immediate), "function" => Ok(crate::AddressSpace::Function), "task_payload" => { enable_extensions.require(ImplementedEnableExtension::WgpuMeshShader, span)?; Ok(crate::AddressSpace::TaskPayload) } "ray_payload" => { if enable_extensions.contains(ImplementedEnableExtension::WgpuRayTracingPipeline) { Ok(crate::AddressSpace::RayPayload) } else { Err(Box::new(Error::EnableExtensionNotEnabled { span, kind: ImplementedEnableExtension::WgpuRayTracingPipeline.into(), })) } } "incoming_ray_payload" => { if enable_extensions.contains(ImplementedEnableExtension::WgpuRayTracingPipeline) { Ok(crate::AddressSpace::IncomingRayPayload) } else { Err(Box::new(Error::EnableExtensionNotEnabled { span, kind: ImplementedEnableExtension::WgpuRayTracingPipeline.into(), })) } } _ => Err(Box::new(Error::UnknownAddressSpace(span))), } } pub fn map_access_mode(word: &str, span: Span) -> Result<'_, crate::StorageAccess> { match word { "read" => Ok(crate::StorageAccess::LOAD), "write" => Ok(crate::StorageAccess::STORE), "read_write" => Ok(crate::StorageAccess::LOAD | crate::StorageAccess::STORE), "atomic" => Ok(crate::StorageAccess::ATOMIC | crate::StorageAccess::LOAD | crate::StorageAccess::STORE), _ => Err(Box::new(Error::UnknownAccess(span))), } } pub fn map_ray_flag( enable_extensions: &EnableExtensions, word: &str, span: Span, ) -> Result<'static, ()> { match word { "vertex_return" => { if !enable_extensions.contains(ImplementedEnableExtension::WgpuRayQueryVertexReturn) { return Err(Box::new(Error::EnableExtensionNotEnabled { span, kind: ImplementedEnableExtension::WgpuRayQueryVertexReturn.into(), })); } Ok(()) } _ => Err(Box::new(Error::UnknownRayFlag(span))), } } pub fn map_cooperative_role(word: &str, span: Span) -> Result<'_, crate::CooperativeRole> { match word { "A" => Ok(crate::CooperativeRole::A), "B" => Ok(crate::CooperativeRole::B), "C" => Ok(crate::CooperativeRole::C), _ => Err(Box::new(Error::UnknownAccess(span))), } } pub fn map_built_in( enable_extensions: &EnableExtensions, word: &str, span: Span, ) -> Result<'static, crate::BuiltIn> { let built_in = match word { "position" => crate::BuiltIn::Position { invariant: false }, // vertex "vertex_index" => crate::BuiltIn::VertexIndex, "instance_index" => crate::BuiltIn::InstanceIndex, "view_index" => crate::BuiltIn::ViewIndex, "clip_distances" => crate::BuiltIn::ClipDistances, // fragment "front_facing" => crate::BuiltIn::FrontFacing, "frag_depth" => crate::BuiltIn::FragDepth, "primitive_index" => crate::BuiltIn::PrimitiveIndex, "draw_index" => crate::BuiltIn::DrawIndex, "barycentric" => crate::BuiltIn::Barycentric { perspective: true }, "barycentric_no_perspective" => crate::BuiltIn::Barycentric { perspective: false }, "sample_index" => crate::BuiltIn::SampleIndex, "sample_mask" => crate::BuiltIn::SampleMask, // compute "global_invocation_id" => crate::BuiltIn::GlobalInvocationId, "local_invocation_id" => crate::BuiltIn::LocalInvocationId, "local_invocation_index" => crate::BuiltIn::LocalInvocationIndex, "workgroup_id" => crate::BuiltIn::WorkGroupId, "num_workgroups" => crate::BuiltIn::NumWorkGroups, // subgroup "num_subgroups" => crate::BuiltIn::NumSubgroups, "subgroup_id" => crate::BuiltIn::SubgroupId, "subgroup_size" => crate::BuiltIn::SubgroupSize, "subgroup_invocation_id" => crate::BuiltIn::SubgroupInvocationId, // mesh "cull_primitive" => crate::BuiltIn::CullPrimitive, "point_index" => crate::BuiltIn::PointIndex, "line_indices" => crate::BuiltIn::LineIndices, "triangle_indices" => crate::BuiltIn::TriangleIndices, "mesh_task_size" => crate::BuiltIn::MeshTaskSize, // mesh global variable "vertex_count" => crate::BuiltIn::VertexCount, "vertices" => crate::BuiltIn::Vertices, "primitive_count" => crate::BuiltIn::PrimitiveCount, "primitives" => crate::BuiltIn::Primitives, // ray tracing pipeline "ray_invocation_id" => crate::BuiltIn::RayInvocationId, "num_ray_invocations" => crate::BuiltIn::NumRayInvocations, "instance_custom_data" => crate::BuiltIn::InstanceCustomData, "geometry_index" => crate::BuiltIn::GeometryIndex, "world_ray_origin" => crate::BuiltIn::WorldRayOrigin, "world_ray_direction" => crate::BuiltIn::WorldRayDirection, "object_ray_origin" => crate::BuiltIn::ObjectRayOrigin, "object_ray_direction" => crate::BuiltIn::ObjectRayDirection, "ray_t_min" => crate::BuiltIn::RayTmin, "ray_t_current_max" => crate::BuiltIn::RayTCurrentMax, "object_to_world" => crate::BuiltIn::ObjectToWorld, "world_to_object" => crate::BuiltIn::WorldToObject, "hit_kind" => crate::BuiltIn::HitKind, _ => return Err(Box::new(Error::UnknownBuiltin(span))), }; match built_in { crate::BuiltIn::ClipDistances => { enable_extensions.require(ImplementedEnableExtension::ClipDistances, span)? } crate::BuiltIn::PrimitiveIndex => { enable_extensions.require(ImplementedEnableExtension::PrimitiveIndex, span)? } crate::BuiltIn::DrawIndex => { enable_extensions.require(ImplementedEnableExtension::DrawIndex, span)? } crate::BuiltIn::CullPrimitive | crate::BuiltIn::PointIndex | crate::BuiltIn::LineIndices | crate::BuiltIn::TriangleIndices | crate::BuiltIn::VertexCount | crate::BuiltIn::Vertices | crate::BuiltIn::PrimitiveCount | crate::BuiltIn::Primitives => { enable_extensions.require(ImplementedEnableExtension::WgpuMeshShader, span)? } _ => {} } Ok(built_in) } pub fn map_interpolation(word: &str, span: Span) -> Result<'_, crate::Interpolation> { match word { "linear" => Ok(crate::Interpolation::Linear), "flat" => Ok(crate::Interpolation::Flat), "perspective" => Ok(crate::Interpolation::Perspective), "per_vertex" => Ok(crate::Interpolation::PerVertex), _ => Err(Box::new(Error::UnknownAttribute(span))), } } pub fn map_sampling(word: &str, span: Span) -> Result<'_, crate::Sampling> { match word { "center" => Ok(crate::Sampling::Center), "centroid" => Ok(crate::Sampling::Centroid), "sample" => Ok(crate::Sampling::Sample), "first" => Ok(crate::Sampling::First), "either" => Ok(crate::Sampling::Either), _ => Err(Box::new(Error::UnknownAttribute(span))), } } pub fn map_storage_format(word: &str, span: Span) -> Result<'_, crate::StorageFormat> { use crate::StorageFormat as Sf; Ok(match word { "r8unorm" => Sf::R8Unorm, "r8snorm" => Sf::R8Snorm, "r8uint" => Sf::R8Uint, "r8sint" => Sf::R8Sint, "r16unorm" => Sf::R16Unorm, "r16snorm" => Sf::R16Snorm, "r16uint" => Sf::R16Uint, "r16sint" => Sf::R16Sint, "r16float" => Sf::R16Float, "rg8unorm" => Sf::Rg8Unorm, "rg8snorm" => Sf::Rg8Snorm, "rg8uint" => Sf::Rg8Uint, "rg8sint" => Sf::Rg8Sint, "r32uint" => Sf::R32Uint, "r32sint" => Sf::R32Sint, "r32float" => Sf::R32Float, "rg16unorm" => Sf::Rg16Unorm, "rg16snorm" => Sf::Rg16Snorm, "rg16uint" => Sf::Rg16Uint, "rg16sint" => Sf::Rg16Sint, "rg16float" => Sf::Rg16Float, "rgba8unorm" => Sf::Rgba8Unorm, "rgba8snorm" => Sf::Rgba8Snorm, "rgba8uint" => Sf::Rgba8Uint, "rgba8sint" => Sf::Rgba8Sint, "rgb10a2uint" => Sf::Rgb10a2Uint, "rgb10a2unorm" => Sf::Rgb10a2Unorm, "rg11b10ufloat" => Sf::Rg11b10Ufloat, "r64uint" => Sf::R64Uint, "rg32uint" => Sf::Rg32Uint, "rg32sint" => Sf::Rg32Sint, "rg32float" => Sf::Rg32Float, "rgba16unorm" => Sf::Rgba16Unorm, "rgba16snorm" => Sf::Rgba16Snorm, "rgba16uint" => Sf::Rgba16Uint, "rgba16sint" => Sf::Rgba16Sint, "rgba16float" => Sf::Rgba16Float, "rgba32uint" => Sf::Rgba32Uint, "rgba32sint" => Sf::Rgba32Sint, "rgba32float" => Sf::Rgba32Float, "bgra8unorm" => Sf::Bgra8Unorm, _ => return Err(Box::new(Error::UnknownStorageFormat(span))), }) } pub fn map_derivative(word: &str) -> Option<(crate::DerivativeAxis, crate::DerivativeControl)> { use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; match word { "dpdxCoarse" => Some((Axis::X, Ctrl::Coarse)), "dpdyCoarse" => Some((Axis::Y, Ctrl::Coarse)), "fwidthCoarse" => Some((Axis::Width, Ctrl::Coarse)), "dpdxFine" => Some((Axis::X, Ctrl::Fine)), "dpdyFine" => Some((Axis::Y, Ctrl::Fine)), "fwidthFine" => Some((Axis::Width, Ctrl::Fine)), "dpdx" => Some((Axis::X, Ctrl::None)), "dpdy" => Some((Axis::Y, Ctrl::None)), "fwidth" => Some((Axis::Width, Ctrl::None)), _ => None, } } pub fn map_relational_fun(word: &str) -> Option { match word { "any" => Some(crate::RelationalFunction::Any), "all" => Some(crate::RelationalFunction::All), _ => None, } } pub fn map_standard_fun(word: &str) -> Option { use crate::MathFunction as Mf; Some(match word { // comparison "abs" => Mf::Abs, "min" => Mf::Min, "max" => Mf::Max, "clamp" => Mf::Clamp, "saturate" => Mf::Saturate, // trigonometry "cos" => Mf::Cos, "cosh" => Mf::Cosh, "sin" => Mf::Sin, "sinh" => Mf::Sinh, "tan" => Mf::Tan, "tanh" => Mf::Tanh, "acos" => Mf::Acos, "acosh" => Mf::Acosh, "asin" => Mf::Asin, "asinh" => Mf::Asinh, "atan" => Mf::Atan, "atanh" => Mf::Atanh, "atan2" => Mf::Atan2, "radians" => Mf::Radians, "degrees" => Mf::Degrees, // decomposition "ceil" => Mf::Ceil, "floor" => Mf::Floor, "round" => Mf::Round, "fract" => Mf::Fract, "trunc" => Mf::Trunc, "modf" => Mf::Modf, "frexp" => Mf::Frexp, "ldexp" => Mf::Ldexp, // exponent "exp" => Mf::Exp, "exp2" => Mf::Exp2, "log" => Mf::Log, "log2" => Mf::Log2, "pow" => Mf::Pow, // geometry "dot" => Mf::Dot, "dot4I8Packed" => Mf::Dot4I8Packed, "dot4U8Packed" => Mf::Dot4U8Packed, "cross" => Mf::Cross, "distance" => Mf::Distance, "length" => Mf::Length, "normalize" => Mf::Normalize, "faceForward" => Mf::FaceForward, "reflect" => Mf::Reflect, "refract" => Mf::Refract, // computational "sign" => Mf::Sign, "fma" => Mf::Fma, "mix" => Mf::Mix, "step" => Mf::Step, "smoothstep" => Mf::SmoothStep, "sqrt" => Mf::Sqrt, "inverseSqrt" => Mf::InverseSqrt, "transpose" => Mf::Transpose, "determinant" => Mf::Determinant, "quantizeToF16" => Mf::QuantizeToF16, // bits "countTrailingZeros" => Mf::CountTrailingZeros, "countLeadingZeros" => Mf::CountLeadingZeros, "countOneBits" => Mf::CountOneBits, "reverseBits" => Mf::ReverseBits, "extractBits" => Mf::ExtractBits, "insertBits" => Mf::InsertBits, "firstTrailingBit" => Mf::FirstTrailingBit, "firstLeadingBit" => Mf::FirstLeadingBit, // data packing "pack4x8snorm" => Mf::Pack4x8snorm, "pack4x8unorm" => Mf::Pack4x8unorm, "pack2x16snorm" => Mf::Pack2x16snorm, "pack2x16unorm" => Mf::Pack2x16unorm, "pack2x16float" => Mf::Pack2x16float, "pack4xI8" => Mf::Pack4xI8, "pack4xU8" => Mf::Pack4xU8, "pack4xI8Clamp" => Mf::Pack4xI8Clamp, "pack4xU8Clamp" => Mf::Pack4xU8Clamp, // data unpacking "unpack4x8snorm" => Mf::Unpack4x8snorm, "unpack4x8unorm" => Mf::Unpack4x8unorm, "unpack2x16snorm" => Mf::Unpack2x16snorm, "unpack2x16unorm" => Mf::Unpack2x16unorm, "unpack2x16float" => Mf::Unpack2x16float, "unpack4xI8" => Mf::Unpack4xI8, "unpack4xU8" => Mf::Unpack4xU8, _ => return None, }) } pub fn map_conservative_depth(word: &str, span: Span) -> Result<'_, crate::ConservativeDepth> { use crate::ConservativeDepth as Cd; match word { "greater_equal" => Ok(Cd::GreaterEqual), "less_equal" => Ok(Cd::LessEqual), "unchanged" => Ok(Cd::Unchanged), _ => Err(Box::new(Error::UnknownConservativeDepth(span))), } } pub fn map_subgroup_operation( word: &str, ) -> Option<(crate::SubgroupOperation, crate::CollectiveOperation)> { use crate::CollectiveOperation as co; use crate::SubgroupOperation as sg; Some(match word { "subgroupAll" => (sg::All, co::Reduce), "subgroupAny" => (sg::Any, co::Reduce), "subgroupAdd" => (sg::Add, co::Reduce), "subgroupMul" => (sg::Mul, co::Reduce), "subgroupMin" => (sg::Min, co::Reduce), "subgroupMax" => (sg::Max, co::Reduce), "subgroupAnd" => (sg::And, co::Reduce), "subgroupOr" => (sg::Or, co::Reduce), "subgroupXor" => (sg::Xor, co::Reduce), "subgroupExclusiveAdd" => (sg::Add, co::ExclusiveScan), "subgroupExclusiveMul" => (sg::Mul, co::ExclusiveScan), "subgroupInclusiveAdd" => (sg::Add, co::InclusiveScan), "subgroupInclusiveMul" => (sg::Mul, co::InclusiveScan), _ => return None, }) } pub enum TypeGenerator { Vector { size: VectorSize, }, Matrix { columns: VectorSize, rows: VectorSize, }, Array, Atomic, Pointer, SampledTexture { dim: ImageDimension, arrayed: bool, multi: bool, }, StorageTexture { dim: ImageDimension, arrayed: bool, }, BindingArray, AccelerationStructure, RayQuery, CooperativeMatrix { columns: crate::CooperativeSize, rows: crate::CooperativeSize, }, } pub enum PredeclaredType { TypeInner(TypeInner), RayDesc, RayIntersection, TypeGenerator(TypeGenerator), } impl From for PredeclaredType { fn from(value: TypeInner) -> Self { Self::TypeInner(value) } } impl From for PredeclaredType { fn from(value: TypeGenerator) -> Self { Self::TypeGenerator(value) } } pub fn map_predeclared_type( enable_extensions: &EnableExtensions, span: Span, word: &str, ) -> Result<'static, Option> { use Scalar as Sc; use TypeInner as Ti; use VectorSize as Vs; #[rustfmt::skip] let ty = match word { // predeclared types // scalars "bool" => Ti::Scalar(Sc::BOOL).into(), "i32" => Ti::Scalar(Sc::I32).into(), "u32" => Ti::Scalar(Sc::U32).into(), "f32" => Ti::Scalar(Sc::F32).into(), "f16" => Ti::Scalar(Sc::F16).into(), "i64" => Ti::Scalar(Sc::I64).into(), "u64" => Ti::Scalar(Sc::U64).into(), "f64" => Ti::Scalar(Sc::F64).into(), // vector aliases "vec2i" => Ti::Vector { size: Vs::Bi, scalar: Sc::I32 }.into(), "vec3i" => Ti::Vector { size: Vs::Tri, scalar: Sc::I32 }.into(), "vec4i" => Ti::Vector { size: Vs::Quad, scalar: Sc::I32 }.into(), "vec2u" => Ti::Vector { size: Vs::Bi, scalar: Sc::U32 }.into(), "vec3u" => Ti::Vector { size: Vs::Tri, scalar: Sc::U32 }.into(), "vec4u" => Ti::Vector { size: Vs::Quad, scalar: Sc::U32 }.into(), "vec2f" => Ti::Vector { size: Vs::Bi, scalar: Sc::F32 }.into(), "vec3f" => Ti::Vector { size: Vs::Tri, scalar: Sc::F32 }.into(), "vec4f" => Ti::Vector { size: Vs::Quad, scalar: Sc::F32 }.into(), "vec2h" => Ti::Vector { size: Vs::Bi, scalar: Sc::F16 }.into(), "vec3h" => Ti::Vector { size: Vs::Tri, scalar: Sc::F16 }.into(), "vec4h" => Ti::Vector { size: Vs::Quad, scalar: Sc::F16 }.into(), // matrix aliases "mat2x2f" => Ti::Matrix { columns: Vs::Bi, rows: Vs::Bi, scalar: Sc::F32 }.into(), "mat2x3f" => Ti::Matrix { columns: Vs::Bi, rows: Vs::Tri, scalar: Sc::F32 }.into(), "mat2x4f" => Ti::Matrix { columns: Vs::Bi, rows: Vs::Quad, scalar: Sc::F32 }.into(), "mat3x2f" => Ti::Matrix { columns: Vs::Tri, rows: Vs::Bi, scalar: Sc::F32 }.into(), "mat3x3f" => Ti::Matrix { columns: Vs::Tri, rows: Vs::Tri, scalar: Sc::F32 }.into(), "mat3x4f" => Ti::Matrix { columns: Vs::Tri, rows: Vs::Quad, scalar: Sc::F32 }.into(), "mat4x2f" => Ti::Matrix { columns: Vs::Quad, rows: Vs::Bi, scalar: Sc::F32 }.into(), "mat4x3f" => Ti::Matrix { columns: Vs::Quad, rows: Vs::Tri, scalar: Sc::F32 }.into(), "mat4x4f" => Ti::Matrix { columns: Vs::Quad, rows: Vs::Quad, scalar: Sc::F32 }.into(), "mat2x2h" => Ti::Matrix { columns: Vs::Bi, rows: Vs::Bi, scalar: Sc::F16 }.into(), "mat2x3h" => Ti::Matrix { columns: Vs::Bi, rows: Vs::Tri, scalar: Sc::F16 }.into(), "mat2x4h" => Ti::Matrix { columns: Vs::Bi, rows: Vs::Quad, scalar: Sc::F16 }.into(), "mat3x2h" => Ti::Matrix { columns: Vs::Tri, rows: Vs::Bi, scalar: Sc::F16 }.into(), "mat3x3h" => Ti::Matrix { columns: Vs::Tri, rows: Vs::Tri, scalar: Sc::F16 }.into(), "mat3x4h" => Ti::Matrix { columns: Vs::Tri, rows: Vs::Quad, scalar: Sc::F16 }.into(), "mat4x2h" => Ti::Matrix { columns: Vs::Quad, rows: Vs::Bi, scalar: Sc::F16 }.into(), "mat4x3h" => Ti::Matrix { columns: Vs::Quad, rows: Vs::Tri, scalar: Sc::F16 }.into(), "mat4x4h" => Ti::Matrix { columns: Vs::Quad, rows: Vs::Quad, scalar: Sc::F16 }.into(), // samplers "sampler" => Ti::Sampler { comparison: false }.into(), "sampler_comparison" => Ti::Sampler { comparison: true }.into(), // depth textures "texture_depth_2d" => Ti::Image { dim: ImageDimension::D2, arrayed: false, class: ImageClass::Depth { multi: false } }.into(), "texture_depth_2d_array" => Ti::Image { dim: ImageDimension::D2, arrayed: true, class: ImageClass::Depth { multi: false } }.into(), "texture_depth_cube" => Ti::Image { dim: ImageDimension::Cube, arrayed: false, class: ImageClass::Depth { multi: false } }.into(), "texture_depth_cube_array" => Ti::Image { dim: ImageDimension::Cube, arrayed: true, class: ImageClass::Depth { multi: false } }.into(), "texture_depth_multisampled_2d" => Ti::Image { dim: ImageDimension::D2, arrayed: false, class: ImageClass::Depth { multi: true } }.into(), // external texture "texture_external" => Ti::Image { dim: ImageDimension::D2, arrayed: false, class: ImageClass::External }.into(), // ray desc "RayDesc" => PredeclaredType::RayDesc, // ray intersection "RayIntersection" => PredeclaredType::RayIntersection, // predeclared type generators // vector "vec2" => TypeGenerator::Vector { size: Vs::Bi }.into(), "vec3" => TypeGenerator::Vector { size: Vs::Tri }.into(), "vec4" => TypeGenerator::Vector { size: Vs::Quad }.into(), // matrix "mat2x2" => TypeGenerator::Matrix { columns: Vs::Bi, rows: Vs::Bi }.into(), "mat2x3" => TypeGenerator::Matrix { columns: Vs::Bi, rows: Vs::Tri }.into(), "mat2x4" => TypeGenerator::Matrix { columns: Vs::Bi, rows: Vs::Quad }.into(), "mat3x2" => TypeGenerator::Matrix { columns: Vs::Tri, rows: Vs::Bi }.into(), "mat3x3" => TypeGenerator::Matrix { columns: Vs::Tri, rows: Vs::Tri }.into(), "mat3x4" => TypeGenerator::Matrix { columns: Vs::Tri, rows: Vs::Quad }.into(), "mat4x2" => TypeGenerator::Matrix { columns: Vs::Quad, rows: Vs::Bi }.into(), "mat4x3" => TypeGenerator::Matrix { columns: Vs::Quad, rows: Vs::Tri }.into(), "mat4x4" => TypeGenerator::Matrix { columns: Vs::Quad, rows: Vs::Quad }.into(), // array "array" => TypeGenerator::Array.into(), // atomic "atomic" => TypeGenerator::Atomic.into(), // pointer "ptr" => TypeGenerator::Pointer.into(), // sampled textures "texture_1d" => TypeGenerator::SampledTexture { dim: ImageDimension::D1, arrayed: false, multi: false }.into(), "texture_2d" => TypeGenerator::SampledTexture { dim: ImageDimension::D2, arrayed: false, multi: false }.into(), "texture_2d_array" => TypeGenerator::SampledTexture { dim: ImageDimension::D2, arrayed: true, multi: false }.into(), "texture_3d" => TypeGenerator::SampledTexture { dim: ImageDimension::D3, arrayed: false, multi: false }.into(), "texture_cube" => TypeGenerator::SampledTexture { dim: ImageDimension::Cube, arrayed: false, multi: false }.into(), "texture_cube_array" => TypeGenerator::SampledTexture { dim: ImageDimension::Cube, arrayed: true, multi: false }.into(), "texture_multisampled_2d" => TypeGenerator::SampledTexture { dim: ImageDimension::D2, arrayed: false, multi: true }.into(), // storage textures "texture_storage_1d" => TypeGenerator::StorageTexture { dim: ImageDimension::D1, arrayed: false }.into(), "texture_storage_2d" => TypeGenerator::StorageTexture { dim: ImageDimension::D2, arrayed: false }.into(), "texture_storage_2d_array" => TypeGenerator::StorageTexture { dim: ImageDimension::D2, arrayed: true }.into(), "texture_storage_3d" => TypeGenerator::StorageTexture { dim: ImageDimension::D3, arrayed: false }.into(), // binding array "binding_array" => TypeGenerator::BindingArray.into(), // acceleration structure "acceleration_structure" => TypeGenerator::AccelerationStructure.into(), // ray query "ray_query" => TypeGenerator::RayQuery.into(), // cooperative matrix "coop_mat8x8" => TypeGenerator::CooperativeMatrix { columns: crate::CooperativeSize::Eight, rows: crate::CooperativeSize::Eight, }.into(), "coop_mat16x16" => TypeGenerator::CooperativeMatrix { columns: crate::CooperativeSize::Sixteen, rows: crate::CooperativeSize::Sixteen, }.into(), _ => return Ok(None), }; // Check for the enable extension required to use this type, if any. // Slice should be at least len one otherwise extension_needed should be None. let extensions_needed: Option<&[_]> = match ty { PredeclaredType::TypeInner(ref ty) if ty.scalar() == Some(Sc::F16) => { Some(&[ImplementedEnableExtension::F16]) } PredeclaredType::RayDesc | PredeclaredType::RayIntersection | PredeclaredType::TypeGenerator(TypeGenerator::AccelerationStructure) | PredeclaredType::TypeGenerator(TypeGenerator::RayQuery) => Some(&[ ImplementedEnableExtension::WgpuRayQuery, ImplementedEnableExtension::WgpuRayTracingPipeline, ]), PredeclaredType::TypeGenerator(TypeGenerator::CooperativeMatrix { .. }) => { Some(&[ImplementedEnableExtension::WgpuCooperativeMatrix]) } _ => None, }; if let Some(extensions_needed) = extensions_needed { let mut any_extension_enabled = false; for extension_needed in extensions_needed { if enable_extensions.contains(*extension_needed) { any_extension_enabled = true; } } if !any_extension_enabled { return Err(Box::new(Error::EnableExtensionNotEnabled { span, kind: extensions_needed[0].into(), })); } } Ok(Some(ty)) } ================================================ FILE: naga/src/front/wgsl/parse/directive/enable_extension.rs ================================================ //! `enable …;` extensions in WGSL. //! //! The focal point of this module is the [`EnableExtension`] API. use crate::front::wgsl::{Error, Result}; use crate::Span; use alloc::boxed::Box; /// Tracks the status of every enable-extension known to Naga. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) struct EnableExtensions { wgpu_mesh_shader: bool, wgpu_ray_query: bool, wgpu_ray_query_vertex_return: bool, wgpu_ray_tracing_pipelines: bool, dual_source_blending: bool, /// Whether `enable f16;` was written earlier in the shader module. f16: bool, clip_distances: bool, wgpu_cooperative_matrix: bool, draw_index: bool, primitive_index: bool, } impl EnableExtensions { pub(crate) const fn empty() -> Self { Self { wgpu_mesh_shader: false, wgpu_ray_query: false, wgpu_ray_query_vertex_return: false, wgpu_ray_tracing_pipelines: false, f16: false, dual_source_blending: false, clip_distances: false, wgpu_cooperative_matrix: false, draw_index: false, primitive_index: false, } } /// Add an enable-extension to the set requested by a module. pub(crate) const fn add(&mut self, ext: ImplementedEnableExtension) { let field = match ext { ImplementedEnableExtension::WgpuMeshShader => &mut self.wgpu_mesh_shader, ImplementedEnableExtension::WgpuRayQuery => &mut self.wgpu_ray_query, ImplementedEnableExtension::WgpuRayQueryVertexReturn => { &mut self.wgpu_ray_query_vertex_return } ImplementedEnableExtension::WgpuRayTracingPipeline => { &mut self.wgpu_ray_tracing_pipelines } ImplementedEnableExtension::DualSourceBlending => &mut self.dual_source_blending, ImplementedEnableExtension::F16 => &mut self.f16, ImplementedEnableExtension::ClipDistances => &mut self.clip_distances, ImplementedEnableExtension::WgpuCooperativeMatrix => &mut self.wgpu_cooperative_matrix, ImplementedEnableExtension::DrawIndex => &mut self.draw_index, ImplementedEnableExtension::PrimitiveIndex => &mut self.primitive_index, }; *field = true; } /// Query whether an enable-extension tracked here has been requested. pub(crate) const fn contains(&self, ext: ImplementedEnableExtension) -> bool { match ext { ImplementedEnableExtension::WgpuMeshShader => self.wgpu_mesh_shader, ImplementedEnableExtension::WgpuRayQuery => self.wgpu_ray_query, ImplementedEnableExtension::WgpuRayQueryVertexReturn => { self.wgpu_ray_query_vertex_return } ImplementedEnableExtension::WgpuRayTracingPipeline => self.wgpu_ray_tracing_pipelines, ImplementedEnableExtension::DualSourceBlending => self.dual_source_blending, ImplementedEnableExtension::F16 => self.f16, ImplementedEnableExtension::ClipDistances => self.clip_distances, ImplementedEnableExtension::WgpuCooperativeMatrix => self.wgpu_cooperative_matrix, ImplementedEnableExtension::DrawIndex => self.draw_index, ImplementedEnableExtension::PrimitiveIndex => self.primitive_index, } } pub(crate) fn require( &self, ext: ImplementedEnableExtension, span: Span, ) -> Result<'static, ()> { if !self.contains(ext) { Err(Box::new(Error::EnableExtensionNotEnabled { span, kind: ext.into(), })) } else { Ok(()) } } } impl Default for EnableExtensions { fn default() -> Self { Self::empty() } } /// An enable-extension not guaranteed to be present in all environments. /// /// WGSL spec.: #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] pub enum EnableExtension { Implemented(ImplementedEnableExtension), Unimplemented(UnimplementedEnableExtension), } impl From for EnableExtension { fn from(value: ImplementedEnableExtension) -> Self { Self::Implemented(value) } } impl EnableExtension { const F16: &'static str = "f16"; const CLIP_DISTANCES: &'static str = "clip_distances"; const DUAL_SOURCE_BLENDING: &'static str = "dual_source_blending"; const MESH_SHADER: &'static str = "wgpu_mesh_shader"; const RAY_QUERY: &'static str = "wgpu_ray_query"; const RAY_QUERY_VERTEX_RETURN: &'static str = "wgpu_ray_query_vertex_return"; const RAY_TRACING_PIPELINE: &'static str = "wgpu_ray_tracing_pipeline"; const COOPERATIVE_MATRIX: &'static str = "wgpu_cooperative_matrix"; const SUBGROUPS: &'static str = "subgroups"; const PRIMITIVE_INDEX: &'static str = "primitive_index"; const DRAW_INDEX: &'static str = "draw_index"; /// Convert from a sentinel word in WGSL into its associated [`EnableExtension`], if possible. pub(crate) fn from_ident(word: &str, span: Span) -> Result<'_, Self> { Ok(match word { Self::F16 => Self::Implemented(ImplementedEnableExtension::F16), Self::CLIP_DISTANCES => Self::Implemented(ImplementedEnableExtension::ClipDistances), Self::DUAL_SOURCE_BLENDING => { Self::Implemented(ImplementedEnableExtension::DualSourceBlending) } Self::MESH_SHADER => Self::Implemented(ImplementedEnableExtension::WgpuMeshShader), Self::RAY_QUERY => Self::Implemented(ImplementedEnableExtension::WgpuRayQuery), Self::RAY_QUERY_VERTEX_RETURN => { Self::Implemented(ImplementedEnableExtension::WgpuRayQueryVertexReturn) } Self::RAY_TRACING_PIPELINE => { Self::Implemented(ImplementedEnableExtension::WgpuRayTracingPipeline) } Self::COOPERATIVE_MATRIX => { Self::Implemented(ImplementedEnableExtension::WgpuCooperativeMatrix) } Self::SUBGROUPS => Self::Unimplemented(UnimplementedEnableExtension::Subgroups), Self::DRAW_INDEX => Self::Implemented(ImplementedEnableExtension::DrawIndex), Self::PRIMITIVE_INDEX => Self::Implemented(ImplementedEnableExtension::PrimitiveIndex), _ => return Err(Box::new(Error::UnknownEnableExtension(span, word))), }) } /// Maps this [`EnableExtension`] into the sentinel word associated with it in WGSL. pub const fn to_ident(self) -> &'static str { match self { Self::Implemented(kind) => match kind { ImplementedEnableExtension::WgpuMeshShader => Self::MESH_SHADER, ImplementedEnableExtension::WgpuRayQuery => Self::RAY_QUERY, ImplementedEnableExtension::WgpuRayQueryVertexReturn => { Self::RAY_QUERY_VERTEX_RETURN } ImplementedEnableExtension::WgpuCooperativeMatrix => Self::COOPERATIVE_MATRIX, ImplementedEnableExtension::DualSourceBlending => Self::DUAL_SOURCE_BLENDING, ImplementedEnableExtension::F16 => Self::F16, ImplementedEnableExtension::ClipDistances => Self::CLIP_DISTANCES, ImplementedEnableExtension::DrawIndex => Self::DRAW_INDEX, ImplementedEnableExtension::PrimitiveIndex => Self::PRIMITIVE_INDEX, ImplementedEnableExtension::WgpuRayTracingPipeline => Self::RAY_TRACING_PIPELINE, }, Self::Unimplemented(kind) => match kind { UnimplementedEnableExtension::Subgroups => Self::SUBGROUPS, }, } } } /// A variant of [`EnableExtension::Implemented`]. #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] #[cfg_attr(test, derive(strum::VariantArray))] pub enum ImplementedEnableExtension { /// Enables `f16`/`half` primitive support in all shader languages. /// /// In the WGSL standard, this corresponds to [`enable f16;`]. /// /// [`enable f16;`]: https://www.w3.org/TR/WGSL/#extension-f16 F16, /// Enables the `blend_src` attribute in WGSL. /// /// In the WGSL standard, this corresponds to [`enable dual_source_blending;`]. /// /// [`enable dual_source_blending;`]: https://www.w3.org/TR/WGSL/#extension-dual_source_blending DualSourceBlending, /// Enables the `clip_distances` variable in WGSL. /// /// In the WGSL standard, this corresponds to [`enable clip_distances;`]. /// /// [`enable clip_distances;`]: https://www.w3.org/TR/WGSL/#extension-clip_distances ClipDistances, /// Enables the `wgpu_mesh_shader` extension, native only WgpuMeshShader, /// Enables the `wgpu_ray_query` extension, native only. WgpuRayQuery, /// Enables the `wgpu_ray_query_vertex_return` extension, native only. WgpuRayQueryVertexReturn, /// Enables the `wgpu_ray_tracing_pipeline` extension, native only. WgpuRayTracingPipeline, /// Enables the `wgpu_cooperative_matrix` extension, native only. WgpuCooperativeMatrix, /// Enables the `draw_index` builtin. Not currently part of the WGSL spec but probably will be at some point. DrawIndex, /// Enables the `@builtin(primitive_index)` attribute in WGSL. /// /// In the WGSL standard, this corresponds to [`enable primitive-index;`]. /// /// [`enable primitive-index;`]: https://www.w3.org/TR/WGSL/#extension-primitive_index PrimitiveIndex, } impl ImplementedEnableExtension { /// A slice of all variants of [`ImplementedEnableExtension`]. pub const VARIANTS: &'static [Self] = &[ Self::F16, Self::DualSourceBlending, Self::ClipDistances, Self::WgpuMeshShader, Self::WgpuRayQuery, Self::WgpuRayQueryVertexReturn, Self::WgpuRayTracingPipeline, Self::WgpuCooperativeMatrix, Self::DrawIndex, Self::PrimitiveIndex, ]; /// Returns slice of all variants of [`ImplementedEnableExtension`]. pub const fn all() -> &'static [Self] { Self::VARIANTS } /// Returns the capability required for this enable extension. pub const fn capability(self) -> crate::valid::Capabilities { use crate::valid::Capabilities as C; match self { Self::F16 => C::SHADER_FLOAT16, Self::DualSourceBlending => C::DUAL_SOURCE_BLENDING, Self::ClipDistances => C::CLIP_DISTANCES, Self::WgpuMeshShader => C::MESH_SHADER, Self::WgpuRayQuery => C::RAY_QUERY, Self::WgpuRayQueryVertexReturn => C::RAY_HIT_VERTEX_POSITION, Self::WgpuCooperativeMatrix => C::COOPERATIVE_MATRIX, Self::WgpuRayTracingPipeline => C::RAY_TRACING_PIPELINE, Self::DrawIndex => C::DRAW_INDEX, Self::PrimitiveIndex => C::PRIMITIVE_INDEX, } } } #[test] /// Asserts that the manual implementation of VARIANTS is the same as the derived strum version would be /// while still allowing strum to be a dev-only dependency fn test_manual_variants_array_is_correct() { assert_eq!( ::VARIANTS, ImplementedEnableExtension::VARIANTS ); } /// A variant of [`EnableExtension::Unimplemented`]. #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] pub enum UnimplementedEnableExtension { /// Enables subgroup built-ins in all languages. /// /// In the WGSL standard, this corresponds to [`enable subgroups;`]. /// /// [`enable subgroups;`]: https://www.w3.org/TR/WGSL/#extension-subgroups Subgroups, } impl UnimplementedEnableExtension { pub(crate) const fn tracking_issue_num(self) -> u16 { match self { Self::Subgroups => 5555, } } } ================================================ FILE: naga/src/front/wgsl/parse/directive/language_extension.rs ================================================ //! `requires …;` extensions in WGSL. //! //! The focal point of this module is the [`LanguageExtension`] API. /// A language extension recognized by Naga, but not guaranteed to be present in all environments. /// /// WGSL spec.: #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum LanguageExtension { Implemented(ImplementedLanguageExtension), Unimplemented(UnimplementedLanguageExtension), } impl LanguageExtension { const READONLY_AND_READWRITE_STORAGE_TEXTURES: &'static str = "readonly_and_readwrite_storage_textures"; const PACKED4X8_INTEGER_DOT_PRODUCT: &'static str = "packed_4x8_integer_dot_product"; const UNRESTRICTED_POINTER_PARAMETERS: &'static str = "unrestricted_pointer_parameters"; const POINTER_COMPOSITE_ACCESS: &'static str = "pointer_composite_access"; /// Convert from a sentinel word in WGSL into its associated [`LanguageExtension`], if possible. pub fn from_ident(s: &str) -> Option { Some(match s { Self::READONLY_AND_READWRITE_STORAGE_TEXTURES => { Self::Implemented(ImplementedLanguageExtension::ReadOnlyAndReadWriteStorageTextures) } Self::PACKED4X8_INTEGER_DOT_PRODUCT => { Self::Implemented(ImplementedLanguageExtension::Packed4x8IntegerDotProduct) } Self::UNRESTRICTED_POINTER_PARAMETERS => { Self::Unimplemented(UnimplementedLanguageExtension::UnrestrictedPointerParameters) } Self::POINTER_COMPOSITE_ACCESS => { Self::Implemented(ImplementedLanguageExtension::PointerCompositeAccess) } _ => return None, }) } /// Maps this [`LanguageExtension`] into the sentinel word associated with it in WGSL. pub const fn to_ident(self) -> &'static str { match self { Self::Implemented(kind) => kind.to_ident(), Self::Unimplemented(kind) => match kind { UnimplementedLanguageExtension::UnrestrictedPointerParameters => { Self::UNRESTRICTED_POINTER_PARAMETERS } }, } } } /// A variant of [`LanguageExtension::Implemented`]. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[cfg_attr(test, derive(strum::VariantArray))] pub enum ImplementedLanguageExtension { ReadOnlyAndReadWriteStorageTextures, Packed4x8IntegerDotProduct, PointerCompositeAccess, } impl ImplementedLanguageExtension { /// A slice of all variants of [`ImplementedLanguageExtension`]. pub const VARIANTS: &'static [Self] = &[ Self::ReadOnlyAndReadWriteStorageTextures, Self::Packed4x8IntegerDotProduct, Self::PointerCompositeAccess, ]; /// Returns slice of all variants of [`ImplementedLanguageExtension`]. pub const fn all() -> &'static [Self] { Self::VARIANTS } /// Maps this [`ImplementedLanguageExtension`] into the sentinel word associated with it in WGSL. pub const fn to_ident(self) -> &'static str { match self { ImplementedLanguageExtension::ReadOnlyAndReadWriteStorageTextures => { LanguageExtension::READONLY_AND_READWRITE_STORAGE_TEXTURES } ImplementedLanguageExtension::Packed4x8IntegerDotProduct => { LanguageExtension::PACKED4X8_INTEGER_DOT_PRODUCT } ImplementedLanguageExtension::PointerCompositeAccess => { LanguageExtension::POINTER_COMPOSITE_ACCESS } } } } #[test] /// Asserts that the manual implementation of VARIANTS is the same as the derived strum version would be /// while still allowing strum to be a dev-only dependency fn test_manual_variants_array_is_correct() { assert_eq!( ::VARIANTS, ImplementedLanguageExtension::VARIANTS ); } /// A variant of [`LanguageExtension::Unimplemented`]. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum UnimplementedLanguageExtension { UnrestrictedPointerParameters, } impl UnimplementedLanguageExtension { pub(crate) const fn tracking_issue_num(self) -> u16 { match self { Self::UnrestrictedPointerParameters => 5158, } } } ================================================ FILE: naga/src/front/wgsl/parse/directive.rs ================================================ //! WGSL directives. The focal point of this API is [`DirectiveKind`]. //! //! See also . pub mod enable_extension; pub(crate) mod language_extension; use alloc::boxed::Box; /// A parsed sentinel word indicating the type of directive to be parsed next. #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] #[cfg_attr(test, derive(strum::EnumIter))] pub(crate) enum DirectiveKind { /// A [`crate::diagnostic_filter`]. Diagnostic, /// An [`enable_extension`]. Enable, /// A [`language_extension`]. Requires, } impl DirectiveKind { const DIAGNOSTIC: &'static str = "diagnostic"; const ENABLE: &'static str = "enable"; const REQUIRES: &'static str = "requires"; /// Convert from a sentinel word in WGSL into its associated [`DirectiveKind`], if possible. pub fn from_ident(s: &str) -> Option { Some(match s { Self::DIAGNOSTIC => Self::Diagnostic, Self::ENABLE => Self::Enable, Self::REQUIRES => Self::Requires, _ => return None, }) } } impl crate::diagnostic_filter::Severity { #[cfg(feature = "wgsl-in")] pub(crate) fn report_wgsl_parse_diag<'a>( self, err: Box>, source: &str, ) -> crate::front::wgsl::Result<'a, ()> { self.report_diag(err, |e, level| { let e = e.as_parse_error(source); log::log!(level, "{}", e.emit_to_string(source)); }) } } #[cfg(test)] mod test { use alloc::format; use strum::IntoEnumIterator; use super::DirectiveKind; use crate::front::wgsl::assert_parse_err; #[test] fn directive_after_global_decl() { for unsupported_shader in DirectiveKind::iter() { let directive; let expected_msg; match unsupported_shader { DirectiveKind::Diagnostic => { directive = "diagnostic(off,derivative_uniformity)"; expected_msg = "\ error: expected global declaration, but found a global directive ┌─ wgsl:2:1 │ 2 │ diagnostic(off,derivative_uniformity); │ ^^^^^^^^^^ written after first global declaration │ = note: global directives are only allowed before global declarations; maybe hoist this closer to the top of the shader module? "; } DirectiveKind::Enable => { directive = "enable f16"; expected_msg = "\ error: expected global declaration, but found a global directive ┌─ wgsl:2:1 │ 2 │ enable f16; │ ^^^^^^ written after first global declaration │ = note: global directives are only allowed before global declarations; maybe hoist this closer to the top of the shader module? "; } DirectiveKind::Requires => { directive = "requires readonly_and_readwrite_storage_textures"; expected_msg = "\ error: expected global declaration, but found a global directive ┌─ wgsl:2:1 │ 2 │ requires readonly_and_readwrite_storage_textures; │ ^^^^^^^^ written after first global declaration │ = note: global directives are only allowed before global declarations; maybe hoist this closer to the top of the shader module? "; } } let shader = format!( "\ @group(0) @binding(0) var thing: i32; {directive}; " ); assert_parse_err(&shader, expected_msg); } } } ================================================ FILE: naga/src/front/wgsl/parse/lexer.rs ================================================ use super::{number::consume_number, Error, ExpectedToken, Result}; use crate::front::wgsl::error::NumberError; use crate::front::wgsl::parse::directive::enable_extension::{ EnableExtensions, ImplementedEnableExtension, }; use crate::front::wgsl::parse::Number; use crate::Span; use alloc::{boxed::Box, vec::Vec}; pub type TokenSpan<'a> = (Token<'a>, Span); #[derive(Copy, Clone, Debug, PartialEq)] pub enum Token<'a> { /// A separator character: `:;,`, and `.` when not part of a numeric /// literal. Separator(char), /// A parenthesis-like character: `()[]{}`, and also `<>`. /// /// Note that `<>` representing template argument brackets are distinguished /// using WGSL's [template list discovery algorithm][tlda], and are returned /// as [`Token::TemplateArgsStart`] and [`Token::TemplateArgsEnd`]. That is, /// we use `Paren` for `<>` when they are *not* parens. /// /// [tlda]: https://gpuweb.github.io/gpuweb/wgsl/#template-list-discovery Paren(char), /// The attribute introduction character `@`. Attribute, /// A numeric literal, either integral or floating-point, including any /// type suffix. Number(core::result::Result), /// An identifier, possibly a reserved word. Word(&'a str), /// A miscellaneous single-character operator, like an arithmetic unary or /// binary operator. This includes `=`, for assignment and initialization. Operation(char), /// Certain multi-character logical operators: `!=`, `==`, `&&`, /// `||`, `<=` and `>=`. The value gives the operator's first /// character. /// /// For `<` and `>` operators, see [`Token::Paren`]. LogicalOperation(char), /// A shift operator: `>>` or `<<`. ShiftOperation(char), /// A compound assignment operator like `+=`. /// /// When the given character is `<` or `>`, those represent the left shift /// and right shift assignment operators, `<<=` and `>>=`. AssignmentOperation(char), /// The `++` operator. IncrementOperation, /// The `--` operator. DecrementOperation, /// The `->` token. Arrow, /// A `<` representing the start of a template argument list, according to /// WGSL's [template list discovery algorithm][tlda]. /// /// [tlda]: https://gpuweb.github.io/gpuweb/wgsl/#template-list-discovery TemplateArgsStart, /// A `>` representing the end of a template argument list, according to /// WGSL's [template list discovery algorithm][tlda]. /// /// [tlda]: https://gpuweb.github.io/gpuweb/wgsl/#template-list-discovery TemplateArgsEnd, /// A character that does not represent a legal WGSL token. Unknown(char), /// Comment or whitespace. Trivia, /// A doc comment, beginning with `///` or `/**`. DocComment(&'a str), /// A module-level doc comment, beginning with `//!` or `/*!`. ModuleDocComment(&'a str), /// The end of the input. End, } fn consume_any(input: &str, what: impl Fn(char) -> bool) -> (&str, &str) { let pos = input.find(|c| !what(c)).unwrap_or(input.len()); input.split_at(pos) } struct UnclosedCandidate { index: usize, depth: usize, } /// Produce at least one token, distinguishing [template lists] from other uses /// of `<` and `>`. /// /// Consume one or more tokens from `input` and store them in `tokens`, updating /// `input` to refer to the remaining text. Apply WGSL's [template list /// discovery algorithm] to decide what sort of tokens `<` and `>` characters in /// the input actually represent. /// /// Store the tokens in `tokens` in the *reverse* of the order they appear in /// the text, such that the caller can pop from the end of the vector to see the /// tokens in textual order. /// /// The `tokens` vector must be empty on entry. The idea is for the caller to /// use it as a buffer of unconsumed tokens, and call this function to refill it /// when it's empty. /// /// The `source` argument must be the whole original source code, used to /// compute spans. /// /// If `ignore_doc_comments` is true, then doc comments are returned as /// [`Token::Trivia`], like ordinary comments. /// /// [template lists]: https://gpuweb.github.io/gpuweb/wgsl/#template-lists-sec /// [template list discovery algorithm]: https://gpuweb.github.io/gpuweb/wgsl/#template-list-discovery fn discover_template_lists<'a>( tokens: &mut Vec<(TokenSpan<'a>, &'a str)>, source: &'a str, mut input: &'a str, ignore_doc_comments: bool, ) { assert!(tokens.is_empty()); let mut looking_for_template_start = false; let mut pending: Vec = Vec::new(); // Current nesting depth of `()` and `[]` brackets. (`{}` brackets // exit all template list processing.) let mut depth = 0; fn pop_until(pending: &mut Vec, depth: usize) { while pending .last() .map(|candidate| candidate.depth >= depth) .unwrap_or(false) { pending.pop(); } } loop { // Decide whether `consume_token` should treat a `>` character as // `TemplateArgsEnd`, without considering the characters that follow. // // This condition matches the one that determines whether the spec's // template list discovery algorithm looks past a `>` character for a // `=`. By passing this flag to `consume_token`, we ensure it follows // that behavior. let waiting_for_template_end = pending .last() .is_some_and(|candidate| candidate.depth == depth); // Ask `consume_token` for the next token and add it to `tokens`, along // with its span. // // This means that `<` enters the buffer as `Token::Paren('<')`, the // ordinary comparison operator. We'll change that to // `Token::TemplateArgsStart` later if appropriate. let (token, rest) = consume_token(input, waiting_for_template_end, ignore_doc_comments); let span = Span::from(source.len() - input.len()..source.len() - rest.len()); tokens.push(((token, span), rest)); input = rest; // Since `consume_token` treats `<<=`, `<<` and `<=` as operators, not // `Token::Paren`, that takes care of the WGSL algorithm's post-'<' lookahead // for us. match token { Token::Word(_) => { looking_for_template_start = true; continue; } Token::Trivia | Token::DocComment(_) | Token::ModuleDocComment(_) if looking_for_template_start => { continue; } Token::Paren('<') if looking_for_template_start => { pending.push(UnclosedCandidate { index: tokens.len() - 1, depth, }); } Token::TemplateArgsEnd => { // The `consume_token` function only returns `TemplateArgsEnd` // if `waiting_for_template_end` is true, so we know `pending` // has a top entry at the appropriate depth. // // Find the matching `<` token and change its type to // `TemplateArgsStart`. let candidate = pending.pop().unwrap(); let &mut ((ref mut token, _), _) = tokens.get_mut(candidate.index).unwrap(); *token = Token::TemplateArgsStart; } Token::Paren('(' | '[') => { depth += 1; } Token::Paren(')' | ']') => { pop_until(&mut pending, depth); depth = depth.saturating_sub(1); } Token::Operation('=') | Token::Separator(':' | ';') | Token::Paren('{') => { pending.clear(); depth = 0; } Token::LogicalOperation('&') | Token::LogicalOperation('|') => { pop_until(&mut pending, depth); } Token::End => break, _ => {} } looking_for_template_start = false; // The WGSL spec's template list discovery algorithm processes the // entire source at once, but Naga would rather limit its lookahead to // the actual text that could possibly be a template parameter list. // This is usually less than a line. if pending.is_empty() { break; } } tokens.reverse(); } /// Return the token at the start of `input`. /// /// The `waiting_for_template_end` flag enables some special handling to help out /// `discover_template_lists`: /// /// - If `waiting_for_template_end` is `true`, then return text starting with /// '>` as [`Token::TemplateArgsEnd`] and consume only the `>` character, /// regardless of what characters follow it. This is required by the [template /// list discovery algorithm][tlda] when the `>` would end a template argument list. /// /// - If `waiting_for_template_end` is false, recognize multi-character tokens /// beginning with `>` as usual. /// /// If `ignore_doc_comments` is true, then doc comments are returned as /// [`Token::Trivia`], like ordinary comments. /// /// [tlda]: https://gpuweb.github.io/gpuweb/wgsl/#template-list-discovery fn consume_token( input: &str, waiting_for_template_end: bool, ignore_doc_comments: bool, ) -> (Token<'_>, &str) { let mut chars = input.chars(); let cur = match chars.next() { Some(c) => c, None => return (Token::End, ""), }; match cur { ':' | ';' | ',' => (Token::Separator(cur), chars.as_str()), '.' => { let og_chars = chars.as_str(); match chars.next() { Some('0'..='9') => consume_number(input), _ => (Token::Separator(cur), og_chars), } } '@' => (Token::Attribute, chars.as_str()), '(' | ')' | '{' | '}' | '[' | ']' => (Token::Paren(cur), chars.as_str()), '<' | '>' => { let og_chars = chars.as_str(); if cur == '>' && waiting_for_template_end { return (Token::TemplateArgsEnd, og_chars); } match chars.next() { Some('=') => (Token::LogicalOperation(cur), chars.as_str()), Some(c) if c == cur => { let og_chars = chars.as_str(); match chars.next() { Some('=') => (Token::AssignmentOperation(cur), chars.as_str()), _ => (Token::ShiftOperation(cur), og_chars), } } _ => (Token::Paren(cur), og_chars), } } '0'..='9' => consume_number(input), '/' => { let og_chars = chars.as_str(); match chars.next() { Some('/') => { let mut input_chars = input.char_indices(); let doc_comment_end = input_chars .find_map(|(index, c)| is_comment_end(c).then_some(index)) .unwrap_or(input.len()); let token = match chars.next() { Some('/') if !ignore_doc_comments => { Token::DocComment(&input[..doc_comment_end]) } Some('!') if !ignore_doc_comments => { Token::ModuleDocComment(&input[..doc_comment_end]) } _ => Token::Trivia, }; (token, input_chars.as_str()) } Some('*') => { let next_c = chars.next(); enum CommentType { Doc, ModuleDoc, Normal, } let comment_type = match next_c { Some('*') if !ignore_doc_comments => CommentType::Doc, Some('!') if !ignore_doc_comments => CommentType::ModuleDoc, _ => CommentType::Normal, }; let mut depth = 1; let mut prev = next_c; for c in &mut chars { match (prev, c) { (Some('*'), '/') => { prev = None; depth -= 1; if depth == 0 { let rest = chars.as_str(); let token = match comment_type { CommentType::Doc => { let doc_comment_end = input.len() - rest.len(); Token::DocComment(&input[..doc_comment_end]) } CommentType::ModuleDoc => { let doc_comment_end = input.len() - rest.len(); Token::ModuleDocComment(&input[..doc_comment_end]) } CommentType::Normal => Token::Trivia, }; return (token, rest); } } (Some('/'), '*') => { prev = None; depth += 1; } _ => { prev = Some(c); } } } (Token::End, "") } Some('=') => (Token::AssignmentOperation(cur), chars.as_str()), _ => (Token::Operation(cur), og_chars), } } '-' => { let og_chars = chars.as_str(); match chars.next() { Some('>') => (Token::Arrow, chars.as_str()), Some('-') => (Token::DecrementOperation, chars.as_str()), Some('=') => (Token::AssignmentOperation(cur), chars.as_str()), _ => (Token::Operation(cur), og_chars), } } '+' => { let og_chars = chars.as_str(); match chars.next() { Some('+') => (Token::IncrementOperation, chars.as_str()), Some('=') => (Token::AssignmentOperation(cur), chars.as_str()), _ => (Token::Operation(cur), og_chars), } } '*' | '%' | '^' => { let og_chars = chars.as_str(); match chars.next() { Some('=') => (Token::AssignmentOperation(cur), chars.as_str()), _ => (Token::Operation(cur), og_chars), } } '~' => (Token::Operation(cur), chars.as_str()), '=' | '!' => { let og_chars = chars.as_str(); match chars.next() { Some('=') => (Token::LogicalOperation(cur), chars.as_str()), _ => (Token::Operation(cur), og_chars), } } '&' | '|' => { let og_chars = chars.as_str(); match chars.next() { Some(c) if c == cur => (Token::LogicalOperation(cur), chars.as_str()), Some('=') => (Token::AssignmentOperation(cur), chars.as_str()), _ => (Token::Operation(cur), og_chars), } } _ if is_blankspace(cur) => { let (_, rest) = consume_any(input, is_blankspace); (Token::Trivia, rest) } _ if is_word_start(cur) => { let (word, rest) = consume_any(input, is_word_part); (Token::Word(word), rest) } _ => (Token::Unknown(cur), chars.as_str()), } } /// Returns whether or not a char is a comment end /// (Unicode Pattern_White_Space excluding U+0020, U+0009, U+200E and U+200F) /// const fn is_comment_end(c: char) -> bool { match c { '\u{000a}'..='\u{000d}' | '\u{0085}' | '\u{2028}' | '\u{2029}' => true, _ => false, } } /// Returns whether or not a char is a blankspace (Unicode Pattern_White_Space) const fn is_blankspace(c: char) -> bool { match c { '\u{0020}' | '\u{0009}'..='\u{000d}' | '\u{0085}' | '\u{200e}' | '\u{200f}' | '\u{2028}' | '\u{2029}' => true, _ => false, } } /// Returns whether or not a char is a word start (Unicode XID_Start + '_') fn is_word_start(c: char) -> bool { c == '_' || unicode_ident::is_xid_start(c) } /// Returns whether or not a char is a word part (Unicode XID_Continue) fn is_word_part(c: char) -> bool { unicode_ident::is_xid_continue(c) } pub(in crate::front::wgsl) struct Lexer<'a> { /// The remaining unconsumed input. input: &'a str, /// The full original source code. /// /// We compare `input` against this to compute the lexer's current offset in /// the source. pub(in crate::front::wgsl) source: &'a str, /// The byte offset of the end of the most recently returned non-trivia /// token. /// /// This is consulted by the `span_from` function, for finding the /// end of the span for larger structures like expressions or /// statements. last_end_offset: usize, /// A stack of unconsumed tokens to which template list discovery has been /// applied. /// /// This is a stack: the next token is at the *end* of the vector, not the /// start. So tokens appear here in the reverse of the order they appear in /// the source. /// /// This doesn't contain the whole source, only those tokens produced by /// [`discover_template_lists`]'s look-ahead, or that have been produced by /// other look-ahead functions like `peek` and `next_if`. When this is empty, /// we call [`discover_template_lists`] to get more. tokens: Vec<(TokenSpan<'a>, &'a str)>, /// Whether or not to ignore doc comments. /// If `true`, doc comments are treated as [`Token::Trivia`]. ignore_doc_comments: bool, /// The set of [enable-extensions] present in the module, determined in a pre-pass. /// /// [enable-extensions]: https://gpuweb.github.io/gpuweb/wgsl/#enable-extensions-sec pub(in crate::front::wgsl) enable_extensions: EnableExtensions, } impl<'a> Lexer<'a> { pub(in crate::front::wgsl) const fn new(input: &'a str, ignore_doc_comments: bool) -> Self { Lexer { input, source: input, last_end_offset: 0, tokens: Vec::new(), enable_extensions: EnableExtensions::empty(), ignore_doc_comments, } } /// Check that `extension` is enabled in `self`. pub(in crate::front::wgsl) fn require_enable_extension( &self, extension: ImplementedEnableExtension, span: Span, ) -> Result<'static, ()> { self.enable_extensions.require(extension, span) } /// Calls the function with a lexer and returns the result of the function as well as the span for everything the function parsed /// /// # Examples /// ```ignore /// let lexer = Lexer::new("5"); /// let (value, span) = lexer.capture_span(Lexer::next_uint_literal); /// assert_eq!(value, 5); /// ``` #[inline] pub fn capture_span( &mut self, inner: impl FnOnce(&mut Self) -> core::result::Result, ) -> core::result::Result<(T, Span), E> { let start = self.current_byte_offset(); let res = inner(self)?; let end = self.current_byte_offset(); Ok((res, Span::from(start..end))) } pub(in crate::front::wgsl) fn start_byte_offset(&mut self) -> usize { loop { // Eat all trivia because `next` doesn't eat trailing trivia. let (token, rest) = consume_token(self.input, false, true); if let Token::Trivia = token { self.input = rest; } else { return self.current_byte_offset(); } } } /// Collect all module doc comments until a non doc token is found. pub(in crate::front::wgsl) fn accumulate_module_doc_comments(&mut self) -> Vec<&'a str> { let mut doc_comments = Vec::new(); loop { // ignore blankspace self.input = consume_any(self.input, is_blankspace).1; let (token, rest) = consume_token(self.input, false, self.ignore_doc_comments); if let Token::ModuleDocComment(doc_comment) = token { self.input = rest; doc_comments.push(doc_comment); } else { return doc_comments; } } } /// Collect all doc comments until a non doc token is found. pub(in crate::front::wgsl) fn accumulate_doc_comments(&mut self) -> Vec<&'a str> { let mut doc_comments = Vec::new(); loop { // ignore blankspace self.input = consume_any(self.input, is_blankspace).1; let (token, rest) = consume_token(self.input, false, self.ignore_doc_comments); if let Token::DocComment(doc_comment) = token { self.input = rest; doc_comments.push(doc_comment); } else { return doc_comments; } } } const fn current_byte_offset(&self) -> usize { self.source.len() - self.input.len() } pub(in crate::front::wgsl) fn span_from(&self, offset: usize) -> Span { Span::from(offset..self.last_end_offset) } pub(in crate::front::wgsl) fn span_with_start(&self, span: Span) -> Span { span.until(&Span::from(0..self.last_end_offset)) } /// Return the next non-whitespace token from `self`. /// /// Assume we are a parse state where bit shift operators may /// occur, but not angle brackets. #[must_use] pub(in crate::front::wgsl) fn next(&mut self) -> TokenSpan<'a> { self.next_impl(true) } #[cfg(test)] pub fn next_with_unignored_doc_comments(&mut self) -> TokenSpan<'a> { self.next_impl(false) } /// Return the next non-whitespace token from `self`, with a span. fn next_impl(&mut self, ignore_doc_comments: bool) -> TokenSpan<'a> { loop { if self.tokens.is_empty() { discover_template_lists( &mut self.tokens, self.source, self.input, ignore_doc_comments || self.ignore_doc_comments, ); } assert!(!self.tokens.is_empty()); let (token, rest) = self.tokens.pop().unwrap(); self.input = rest; self.last_end_offset = self.current_byte_offset(); match token.0 { Token::Trivia => {} _ => return token, } } } #[must_use] pub(in crate::front::wgsl) fn peek(&mut self) -> TokenSpan<'a> { let input = self.input; let last_end_offset = self.last_end_offset; let token = self.next(); self.tokens.push((token, self.input)); self.input = input; self.last_end_offset = last_end_offset; token } /// If the next token matches it's consumed and true is returned pub(in crate::front::wgsl) fn next_if(&mut self, what: Token<'_>) -> bool { let input = self.input; let last_end_offset = self.last_end_offset; let token = self.next(); if token.0 == what { true } else { self.tokens.push((token, self.input)); self.input = input; self.last_end_offset = last_end_offset; false } } pub(in crate::front::wgsl) fn expect_span(&mut self, expected: Token<'a>) -> Result<'a, Span> { let next = self.next(); if next.0 == expected { Ok(next.1) } else { Err(Box::new(Error::Unexpected( next.1, ExpectedToken::Token(expected), ))) } } pub(in crate::front::wgsl) fn expect(&mut self, expected: Token<'a>) -> Result<'a, ()> { self.expect_span(expected)?; Ok(()) } pub(in crate::front::wgsl) fn next_ident_with_span(&mut self) -> Result<'a, (&'a str, Span)> { match self.next() { (Token::Word("_"), span) => Err(Box::new(Error::InvalidIdentifierUnderscore(span))), (Token::Word(word), span) => { if word.starts_with("__") { Err(Box::new(Error::ReservedIdentifierPrefix(span))) } else { Ok((word, span)) } } (_, span) => Err(Box::new(Error::Unexpected(span, ExpectedToken::Identifier))), } } pub(in crate::front::wgsl) fn next_ident(&mut self) -> Result<'a, super::ast::Ident<'a>> { self.next_ident_with_span() .and_then(|(word, span)| Self::word_as_ident(word, span)) .map(|(name, span)| super::ast::Ident { name, span }) } fn word_as_ident(word: &'a str, span: Span) -> Result<'a, (&'a str, Span)> { if crate::keywords::wgsl::RESERVED.contains(&word) { Err(Box::new(Error::ReservedKeyword(span))) } else { Ok((word, span)) } } pub(in crate::front::wgsl) fn open_arguments(&mut self) -> Result<'a, ()> { self.expect(Token::Paren('(')) } pub(in crate::front::wgsl) fn next_argument(&mut self) -> Result<'a, bool> { let paren = Token::Paren(')'); if self.next_if(Token::Separator(',')) { Ok(!self.next_if(paren)) } else { self.expect(paren).map(|()| false) } } } #[cfg(test)] #[track_caller] fn sub_test(source: &str, expected_tokens: &[Token]) { sub_test_with(true, source, expected_tokens); } #[cfg(test)] #[track_caller] fn sub_test_with_and_without_doc_comments(source: &str, expected_tokens: &[Token]) { sub_test_with(false, source, expected_tokens); sub_test_with( true, source, expected_tokens .iter() .filter(|v| !matches!(**v, Token::DocComment(_) | Token::ModuleDocComment(_))) .cloned() .collect::>() .as_slice(), ); } #[cfg(test)] #[track_caller] fn sub_test_with(ignore_doc_comments: bool, source: &str, expected_tokens: &[Token]) { let mut lex = Lexer::new(source, ignore_doc_comments); for &token in expected_tokens { assert_eq!(lex.next_with_unignored_doc_comments().0, token); } assert_eq!(lex.next().0, Token::End); } #[test] fn test_numbers() { use half::f16; // WGSL spec examples // // decimal integer sub_test( "0x123 0X123u 1u 123 0 0i 0x3f", &[ Token::Number(Ok(Number::AbstractInt(291))), Token::Number(Ok(Number::U32(291))), Token::Number(Ok(Number::U32(1))), Token::Number(Ok(Number::AbstractInt(123))), Token::Number(Ok(Number::AbstractInt(0))), Token::Number(Ok(Number::I32(0))), Token::Number(Ok(Number::AbstractInt(63))), ], ); // decimal floating point sub_test( "0.e+4f 01. .01 12.34 .0f 0h 1e-3 0xa.fp+2 0x1P+4f 0X.3 0x3p+2h 0X1.fp-4 0x3.2p+2h", &[ Token::Number(Ok(Number::F32(0.))), Token::Number(Ok(Number::AbstractFloat(1.))), Token::Number(Ok(Number::AbstractFloat(0.01))), Token::Number(Ok(Number::AbstractFloat(12.34))), Token::Number(Ok(Number::F32(0.))), Token::Number(Ok(Number::F16(f16::from_f32(0.)))), Token::Number(Ok(Number::AbstractFloat(0.001))), Token::Number(Ok(Number::AbstractFloat(43.75))), Token::Number(Ok(Number::F32(16.))), Token::Number(Ok(Number::AbstractFloat(0.1875))), // https://github.com/gfx-rs/wgpu/issues/7046 Token::Number(Err(NumberError::NotRepresentable)), // Should be 0.75 Token::Number(Ok(Number::AbstractFloat(0.12109375))), // https://github.com/gfx-rs/wgpu/issues/7046 Token::Number(Err(NumberError::NotRepresentable)), // Should be 12.5 ], ); // MIN / MAX // // min / max decimal integer sub_test( "0i 2147483647i 2147483648i", &[ Token::Number(Ok(Number::I32(0))), Token::Number(Ok(Number::I32(i32::MAX))), Token::Number(Err(NumberError::NotRepresentable)), ], ); // min / max decimal unsigned integer sub_test( "0u 4294967295u 4294967296u", &[ Token::Number(Ok(Number::U32(u32::MIN))), Token::Number(Ok(Number::U32(u32::MAX))), Token::Number(Err(NumberError::NotRepresentable)), ], ); // min / max hexadecimal signed integer sub_test( "0x0i 0x7FFFFFFFi 0x80000000i", &[ Token::Number(Ok(Number::I32(0))), Token::Number(Ok(Number::I32(i32::MAX))), Token::Number(Err(NumberError::NotRepresentable)), ], ); // min / max hexadecimal unsigned integer sub_test( "0x0u 0xFFFFFFFFu 0x100000000u", &[ Token::Number(Ok(Number::U32(u32::MIN))), Token::Number(Ok(Number::U32(u32::MAX))), Token::Number(Err(NumberError::NotRepresentable)), ], ); // min/max decimal abstract int sub_test( "0 9223372036854775807 9223372036854775808", &[ Token::Number(Ok(Number::AbstractInt(0))), Token::Number(Ok(Number::AbstractInt(i64::MAX))), Token::Number(Err(NumberError::NotRepresentable)), ], ); // min/max hexadecimal abstract int sub_test( "0 0x7fffffffffffffff 0x8000000000000000", &[ Token::Number(Ok(Number::AbstractInt(0))), Token::Number(Ok(Number::AbstractInt(i64::MAX))), Token::Number(Err(NumberError::NotRepresentable)), ], ); /// ≈ 2^-126 * 2^−23 (= 2^−149) const SMALLEST_POSITIVE_SUBNORMAL_F32: f32 = 1e-45; /// ≈ 2^-126 * (1 − 2^−23) const LARGEST_SUBNORMAL_F32: f32 = 1.1754942e-38; /// ≈ 2^-126 const SMALLEST_POSITIVE_NORMAL_F32: f32 = f32::MIN_POSITIVE; /// ≈ 1 − 2^−24 const LARGEST_F32_LESS_THAN_ONE: f32 = 0.99999994; /// ≈ 1 + 2^−23 const SMALLEST_F32_LARGER_THAN_ONE: f32 = 1.0000001; /// ≈ 2^127 * (2 − 2^−23) const LARGEST_NORMAL_F32: f32 = f32::MAX; // decimal floating point sub_test( "1e-45f 1.1754942e-38f 1.17549435e-38f 0.99999994f 1.0000001f 3.40282347e+38f", &[ Token::Number(Ok(Number::F32(SMALLEST_POSITIVE_SUBNORMAL_F32))), Token::Number(Ok(Number::F32(LARGEST_SUBNORMAL_F32))), Token::Number(Ok(Number::F32(SMALLEST_POSITIVE_NORMAL_F32))), Token::Number(Ok(Number::F32(LARGEST_F32_LESS_THAN_ONE))), Token::Number(Ok(Number::F32(SMALLEST_F32_LARGER_THAN_ONE))), Token::Number(Ok(Number::F32(LARGEST_NORMAL_F32))), ], ); sub_test( "3.40282367e+38f", &[ Token::Number(Err(NumberError::NotRepresentable)), // ≈ 2^128 ], ); // hexadecimal floating point sub_test( "0x1p-149f 0x7FFFFFp-149f 0x1p-126f 0xFFFFFFp-24f 0x800001p-23f 0xFFFFFFp+104f", &[ Token::Number(Ok(Number::F32(SMALLEST_POSITIVE_SUBNORMAL_F32))), Token::Number(Ok(Number::F32(LARGEST_SUBNORMAL_F32))), Token::Number(Ok(Number::F32(SMALLEST_POSITIVE_NORMAL_F32))), Token::Number(Ok(Number::F32(LARGEST_F32_LESS_THAN_ONE))), Token::Number(Ok(Number::F32(SMALLEST_F32_LARGER_THAN_ONE))), Token::Number(Ok(Number::F32(LARGEST_NORMAL_F32))), ], ); sub_test( "0x1p128f 0x1.000001p0f", &[ Token::Number(Err(NumberError::NotRepresentable)), // = 2^128 Token::Number(Err(NumberError::NotRepresentable)), ], ); } #[test] fn double_floats() { sub_test( "0x1.2p4lf 0x1p8lf 0.0625lf 625e-4lf 10lf 10l", &[ Token::Number(Ok(Number::F64(18.0))), Token::Number(Ok(Number::F64(256.0))), Token::Number(Ok(Number::F64(0.0625))), Token::Number(Ok(Number::F64(0.0625))), Token::Number(Ok(Number::F64(10.0))), Token::Number(Ok(Number::AbstractInt(10))), Token::Word("l"), ], ) } #[test] fn test_tokens() { sub_test("id123_OK", &[Token::Word("id123_OK")]); sub_test( "92No", &[ Token::Number(Ok(Number::AbstractInt(92))), Token::Word("No"), ], ); sub_test( "2u3o", &[ Token::Number(Ok(Number::U32(2))), Token::Number(Ok(Number::AbstractInt(3))), Token::Word("o"), ], ); sub_test( "2.4f44po", &[ Token::Number(Ok(Number::F32(2.4))), Token::Number(Ok(Number::AbstractInt(44))), Token::Word("po"), ], ); sub_test( "Δέλτα réflexion Кызыл 𐰓𐰏𐰇 朝焼け سلام 검정 שָׁלוֹם गुलाबी փիրուզ", &[ Token::Word("Δέλτα"), Token::Word("réflexion"), Token::Word("Кызыл"), Token::Word("𐰓𐰏𐰇"), Token::Word("朝焼け"), Token::Word("سلام"), Token::Word("검정"), Token::Word("שָׁלוֹם"), Token::Word("गुलाबी"), Token::Word("փիրուզ"), ], ); sub_test("æNoø", &[Token::Word("æNoø")]); sub_test("No¾", &[Token::Word("No"), Token::Unknown('¾')]); sub_test("No好", &[Token::Word("No好")]); sub_test("_No", &[Token::Word("_No")]); sub_test_with_and_without_doc_comments( "*/*/***/*//=/*****//", &[ Token::Operation('*'), Token::AssignmentOperation('/'), Token::DocComment("/*****/"), Token::Operation('/'), ], ); // Type suffixes are only allowed on hex float literals // if you provided an exponent. sub_test( "0x1.2f 0x1.2f 0x1.2h 0x1.2H 0x1.2lf", &[ // The 'f' suffixes are taken as a hex digit: // the fractional part is 0x2f / 256. Token::Number(Ok(Number::AbstractFloat(1.0 + 0x2f as f64 / 256.0))), Token::Number(Ok(Number::AbstractFloat(1.0 + 0x2f as f64 / 256.0))), Token::Number(Ok(Number::AbstractFloat(1.125))), Token::Word("h"), Token::Number(Ok(Number::AbstractFloat(1.125))), Token::Word("H"), Token::Number(Ok(Number::AbstractFloat(1.125))), Token::Word("lf"), ], ) } #[test] fn test_variable_decl() { sub_test( "@group(0 ) var< uniform> texture: texture_multisampled_2d ;", &[ Token::Attribute, Token::Word("group"), Token::Paren('('), Token::Number(Ok(Number::AbstractInt(0))), Token::Paren(')'), Token::Word("var"), Token::TemplateArgsStart, Token::Word("uniform"), Token::TemplateArgsEnd, Token::Word("texture"), Token::Separator(':'), Token::Word("texture_multisampled_2d"), Token::TemplateArgsStart, Token::Word("f32"), Token::TemplateArgsEnd, Token::Separator(';'), ], ); sub_test( "var buffer: array;", &[ Token::Word("var"), Token::TemplateArgsStart, Token::Word("storage"), Token::Separator(','), Token::Word("read_write"), Token::TemplateArgsEnd, Token::Word("buffer"), Token::Separator(':'), Token::Word("array"), Token::TemplateArgsStart, Token::Word("u32"), Token::TemplateArgsEnd, Token::Separator(';'), ], ); } #[test] fn test_template_list() { sub_test( "AD", &[ Token::Word("A"), Token::Paren('<'), Token::Word("B"), Token::LogicalOperation('|'), Token::Word("C"), Token::Paren('>'), Token::Word("D"), ], ); sub_test( "A(B(E))", &[ Token::Word("A"), Token::Paren('('), Token::Word("B"), Token::TemplateArgsStart, Token::Word("C"), Token::Separator(','), Token::Word("D"), Token::TemplateArgsEnd, Token::Paren('('), Token::Word("E"), Token::Paren(')'), Token::Paren(')'), ], ); sub_test( "arrayB)>", &[ Token::Word("array"), Token::TemplateArgsStart, Token::Word("i32"), Token::Separator(','), Token::Word("select"), Token::Paren('('), Token::Number(Ok(Number::AbstractInt(2))), Token::Separator(','), Token::Number(Ok(Number::AbstractInt(3))), Token::Separator(','), Token::Word("A"), Token::Paren('>'), Token::Word("B"), Token::Paren(')'), Token::TemplateArgsEnd, ], ); sub_test( "A[BD", &[ Token::Word("A"), Token::Paren('['), Token::Word("B"), Token::Paren('<'), Token::Word("C"), Token::Paren(']'), Token::Paren('>'), Token::Word("D"), ], ); sub_test( "A", &[ Token::Word("A"), Token::TemplateArgsStart, Token::Word("B"), Token::ShiftOperation('<'), Token::Word("C"), Token::TemplateArgsEnd, ], ); sub_test( "A<(B>=C)>", &[ Token::Word("A"), Token::TemplateArgsStart, Token::Paren('('), Token::Word("B"), Token::LogicalOperation('>'), Token::Word("C"), Token::Paren(')'), Token::TemplateArgsEnd, ], ); sub_test( "A=C>", &[ Token::Word("A"), Token::TemplateArgsStart, Token::Word("B"), Token::TemplateArgsEnd, Token::Operation('='), Token::Word("C"), Token::Paren('>'), ], ); } #[test] fn test_comments() { sub_test("// Single comment", &[]); sub_test( "/* multi line comment */", &[], ); sub_test( "/* multi line comment */ // and another", &[], ); } #[test] fn test_doc_comments() { sub_test_with_and_without_doc_comments( "/// Single comment", &[Token::DocComment("/// Single comment")], ); sub_test_with_and_without_doc_comments( "/** multi line comment */", &[Token::DocComment( "/** multi line comment */", )], ); sub_test_with_and_without_doc_comments( "/** multi line comment */ /// and another", &[ Token::DocComment( "/** multi line comment */", ), Token::DocComment("/// and another"), ], ); } #[test] fn test_doc_comment_nested() { sub_test_with_and_without_doc_comments( "/** a comment with nested one /** nested comment */ */ const a : i32 = 2;", &[ Token::DocComment( "/** a comment with nested one /** nested comment */ */", ), Token::Word("const"), Token::Word("a"), Token::Separator(':'), Token::Word("i32"), Token::Operation('='), Token::Number(Ok(Number::AbstractInt(2))), Token::Separator(';'), ], ); } #[test] fn test_doc_comment_long_character() { sub_test_with_and_without_doc_comments( "/// π/2 /// D(𝐡) = ─────────────────────────────────────────────────── /// παₜα_b((𝐡 ⋅ 𝐭)² / αₜ²) + (𝐡 ⋅ 𝐛)² / α_b² +` const a : i32 = 2;", &[ Token::DocComment("/// π/2"), Token::DocComment("/// D(𝐡) = ───────────────────────────────────────────────────"), Token::DocComment("/// παₜα_b((𝐡 ⋅ 𝐭)² / αₜ²) + (𝐡 ⋅ 𝐛)² / α_b² +`"), Token::Word("const"), Token::Word("a"), Token::Separator(':'), Token::Word("i32"), Token::Operation('='), Token::Number(Ok(Number::AbstractInt(2))), Token::Separator(';'), ], ); } #[test] fn test_doc_comments_module() { sub_test_with_and_without_doc_comments( "//! Comment Module //! Another one. /*! Different module comment */ /// Trying to break module comment // Trying to break module comment again //! After a regular comment is ok. /*! Different module comment again */ //! After a break is supported. const //! After anything else is not.", &[ Token::ModuleDocComment("//! Comment Module"), Token::ModuleDocComment("//! Another one."), Token::ModuleDocComment("/*! Different module comment */"), Token::DocComment("/// Trying to break module comment"), Token::ModuleDocComment("//! After a regular comment is ok."), Token::ModuleDocComment("/*! Different module comment again */"), Token::ModuleDocComment("//! After a break is supported."), Token::Word("const"), Token::ModuleDocComment("//! After anything else is not."), ], ); } ================================================ FILE: naga/src/front/wgsl/parse/mod.rs ================================================ use alloc::{boxed::Box, vec::Vec}; use directive::enable_extension::ImplementedEnableExtension; use crate::diagnostic_filter::{ self, DiagnosticFilter, DiagnosticFilterMap, DiagnosticFilterNode, FilterableTriggeringRule, ShouldConflictOnFullDuplicate, StandardFilterableTriggeringRule, }; use crate::front::wgsl::error::{DiagnosticAttributeNotSupportedPosition, Error, ExpectedToken}; use crate::front::wgsl::parse::directive::enable_extension::{EnableExtension, EnableExtensions}; use crate::front::wgsl::parse::directive::language_extension::LanguageExtension; use crate::front::wgsl::parse::directive::DirectiveKind; use crate::front::wgsl::parse::lexer::{Lexer, Token, TokenSpan}; use crate::front::wgsl::parse::number::Number; use crate::front::wgsl::Result; use crate::front::SymbolTable; use crate::{Arena, FastHashSet, FastIndexSet, Handle, ShaderStage, Span}; pub mod ast; pub mod conv; pub mod directive; pub mod lexer; pub mod number; /// State for constructing an AST expression. /// /// Not to be confused with [`lower::ExpressionContext`], which is for producing /// Naga IR from the AST we produce here. /// /// [`lower::ExpressionContext`]: super::lower::ExpressionContext struct ExpressionContext<'input, 'temp, 'out> { /// The [`TranslationUnit::expressions`] arena to which we should contribute /// expressions. /// /// [`TranslationUnit::expressions`]: ast::TranslationUnit::expressions expressions: &'out mut Arena>, /// A map from identifiers in scope to the locals/arguments they represent. /// /// The handles refer to the [`locals`] arena; see that field's /// documentation for details. /// /// [`locals`]: ExpressionContext::locals local_table: &'temp mut SymbolTable<&'input str, Handle>, /// Local variable and function argument arena for the function we're building. /// /// Note that the [`ast::Local`] here is actually a zero-sized type. This /// `Arena`'s only role is to assign a unique `Handle` to each local /// identifier, and track its definition's span for use in diagnostics. All /// the detailed information about locals - names, types, etc. - is kept in /// the [`LocalDecl`] statements we parsed from their declarations. For /// arguments, that information is kept in [`arguments`]. /// /// In the AST, when an [`Ident`] expression refers to a local variable or /// argument, its [`IdentExpr`] holds the referent's `Handle` in this /// arena. /// /// During lowering, [`LocalDecl`] statements add entries to a per-function /// table that maps `Handle` values to their Naga representations, /// accessed via [`StatementContext::local_table`] and /// [`LocalExpressionContext::local_table`]. This table is then consulted when /// lowering subsequent [`Ident`] expressions. /// /// [`LocalDecl`]: ast::StatementKind::LocalDecl /// [`arguments`]: ast::Function::arguments /// [`Ident`]: ast::Expression::Ident /// [`IdentExpr`]: ast::IdentExpr /// [`StatementContext::local_table`]: super::lower::StatementContext::local_table /// [`LocalExpressionContext::local_table`]: super::lower::LocalExpressionContext::local_table locals: &'out mut Arena, /// Identifiers used by the current global declaration that have no local definition. /// /// This becomes the [`GlobalDecl`]'s [`dependencies`] set. /// /// Note that we don't know at parse time what kind of [`GlobalDecl`] the /// name refers to. We can't look up names until we've seen the entire /// translation unit. /// /// [`GlobalDecl`]: ast::GlobalDecl /// [`dependencies`]: ast::GlobalDecl::dependencies unresolved: &'out mut FastIndexSet>, } impl<'a> ExpressionContext<'a, '_, '_> { fn parse_binary_op( &mut self, lexer: &mut Lexer<'a>, classifier: impl Fn(Token<'a>) -> Option, mut parser: impl FnMut(&mut Lexer<'a>, &mut Self) -> Result<'a, Handle>>, ) -> Result<'a, Handle>> { let start = lexer.start_byte_offset(); let mut accumulator = parser(lexer, self)?; while let Some(op) = classifier(lexer.peek().0) { let _ = lexer.next(); let left = accumulator; let right = parser(lexer, self)?; accumulator = self.expressions.append( ast::Expression::Binary { op, left, right }, lexer.span_from(start), ); } Ok(accumulator) } fn declare_local(&mut self, name: ast::Ident<'a>) -> Result<'a, Handle> { let handle = self.locals.append(ast::Local, name.span); if let Some(old) = self.local_table.add(name.name, handle) { Err(Box::new(Error::Redefinition { previous: self.locals.get_span(old), current: name.span, })) } else { Ok(handle) } } } /// Which grammar rule we are in the midst of parsing. /// /// This is used for error checking. `Parser` maintains a stack of /// these and (occasionally) checks that it is being pushed and popped /// as expected. #[derive(Copy, Clone, Debug, PartialEq)] enum Rule { Attribute, VariableDecl, FunctionDecl, Block, Statement, PrimaryExpr, SingularExpr, UnaryExpr, GeneralExpr, Directive, GenericExpr, EnclosedExpr, LhsExpr, } struct ParsedAttribute { value: Option, } impl Default for ParsedAttribute { fn default() -> Self { Self { value: None } } } impl ParsedAttribute { fn set(&mut self, value: T, name_span: Span) -> Result<'static, ()> { if self.value.is_some() { return Err(Box::new(Error::RepeatedAttribute(name_span))); } self.value = Some(value); Ok(()) } } #[derive(Default)] struct BindingParser<'a> { location: ParsedAttribute>>, built_in: ParsedAttribute, interpolation: ParsedAttribute, sampling: ParsedAttribute, invariant: ParsedAttribute, blend_src: ParsedAttribute>>, per_primitive: ParsedAttribute<()>, } impl<'a> BindingParser<'a> { fn parse( &mut self, parser: &mut Parser, lexer: &mut Lexer<'a>, name: &'a str, name_span: Span, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, ()> { match name { "location" => { lexer.expect(Token::Paren('('))?; self.location .set(parser.expression(lexer, ctx)?, name_span)?; lexer.next_if(Token::Separator(',')); lexer.expect(Token::Paren(')'))?; } "builtin" => { lexer.expect(Token::Paren('('))?; let (raw, span) = lexer.next_ident_with_span()?; self.built_in.set( conv::map_built_in(&lexer.enable_extensions, raw, span)?, name_span, )?; lexer.next_if(Token::Separator(',')); lexer.expect(Token::Paren(')'))?; } "interpolate" => { lexer.expect(Token::Paren('('))?; let (raw, span) = lexer.next_ident_with_span()?; self.interpolation .set(conv::map_interpolation(raw, span)?, name_span)?; if lexer.next_if(Token::Separator(',')) { let (raw, span) = lexer.next_ident_with_span()?; self.sampling .set(conv::map_sampling(raw, span)?, name_span)?; } lexer.next_if(Token::Separator(',')); lexer.expect(Token::Paren(')'))?; } "invariant" => { self.invariant.set(true, name_span)?; } "blend_src" => { lexer.require_enable_extension( ImplementedEnableExtension::DualSourceBlending, name_span, )?; lexer.expect(Token::Paren('('))?; self.blend_src .set(parser.expression(lexer, ctx)?, name_span)?; lexer.next_if(Token::Separator(',')); lexer.expect(Token::Paren(')'))?; } "per_primitive" => { lexer.require_enable_extension( ImplementedEnableExtension::WgpuMeshShader, name_span, )?; self.per_primitive.set((), name_span)?; } _ => return Err(Box::new(Error::UnknownAttribute(name_span))), } Ok(()) } fn finish(self, span: Span) -> Result<'a, Option>> { match ( self.location.value, self.built_in.value, self.interpolation.value, self.sampling.value, self.invariant.value.unwrap_or_default(), self.blend_src.value, self.per_primitive.value, ) { (None, None, None, None, false, None, None) => Ok(None), (Some(location), None, interpolation, sampling, false, blend_src, per_primitive) => { // Before handing over the completed `Module`, we call // `apply_default_interpolation` to ensure that the interpolation and // sampling have been explicitly specified on all vertex shader output and fragment // shader input user bindings, so leaving them potentially `None` here is fine. Ok(Some(ast::Binding::Location { location, interpolation, sampling, blend_src, per_primitive: per_primitive.is_some(), })) } (None, Some(crate::BuiltIn::Position { .. }), None, None, invariant, None, None) => { Ok(Some(ast::Binding::BuiltIn(crate::BuiltIn::Position { invariant, }))) } (None, Some(built_in), None, None, false, None, None) => { Ok(Some(ast::Binding::BuiltIn(built_in))) } (_, _, _, _, _, _, _) => Err(Box::new(Error::InconsistentBinding(span))), } } } /// Configuration for the whole parser run. pub struct Options { /// Controls whether the parser should parse doc comments. pub parse_doc_comments: bool, /// Capabilities to enable during parsing. pub capabilities: crate::valid::Capabilities, } impl Options { /// Creates a new default [`Options`]. pub const fn new() -> Self { Options { parse_doc_comments: false, capabilities: crate::valid::Capabilities::all(), } } } pub struct Parser { rules: Vec<(Rule, usize)>, recursion_depth: u32, } impl Parser { pub const fn new() -> Self { Parser { rules: Vec::new(), recursion_depth: 0, } } fn reset(&mut self) { self.rules.clear(); self.recursion_depth = 0; } fn push_rule_span(&mut self, rule: Rule, lexer: &mut Lexer<'_>) { self.rules.push((rule, lexer.start_byte_offset())); } fn pop_rule_span(&mut self, lexer: &Lexer<'_>) -> Span { let (_, initial) = self.rules.pop().unwrap(); lexer.span_from(initial) } fn peek_rule_span(&mut self, lexer: &Lexer<'_>) -> Span { let &(_, initial) = self.rules.last().unwrap(); lexer.span_from(initial) } fn race_rules(&self, rule0: Rule, rule1: Rule) -> Option { Some( self.rules .iter() .rev() .find(|&x| x.0 == rule0 || x.0 == rule1)? .0, ) } fn track_recursion<'a, F, R>(&mut self, f: F) -> Result<'a, R> where F: FnOnce(&mut Self) -> Result<'a, R>, { self.recursion_depth += 1; if self.recursion_depth >= 256 { return Err(Box::new(Error::Internal("Parser recursion limit exceeded"))); } let ret = f(self); self.recursion_depth -= 1; ret } fn switch_value<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, ast::SwitchValue<'a>> { if lexer.next_if(Token::Word("default")) { return Ok(ast::SwitchValue::Default); } let expr = self.expression(lexer, ctx)?; Ok(ast::SwitchValue::Expr(expr)) } /// Expects `name` to be consumed (not in lexer). fn arguments<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, Vec>>> { self.push_rule_span(Rule::EnclosedExpr, lexer); lexer.open_arguments()?; let mut arguments = Vec::new(); loop { if !arguments.is_empty() { if !lexer.next_argument()? { break; } } else if lexer.next_if(Token::Paren(')')) { break; } let arg = self.expression(lexer, ctx)?; arguments.push(arg); } self.pop_rule_span(lexer); Ok(arguments) } fn enclosed_expression<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, Handle>> { self.push_rule_span(Rule::EnclosedExpr, lexer); let expr = self.expression(lexer, ctx)?; self.pop_rule_span(lexer); Ok(expr) } fn ident_expr<'a>( &mut self, name: &'a str, name_span: Span, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> ast::IdentExpr<'a> { match ctx.local_table.lookup(name) { Some(&local) => ast::IdentExpr::Local(local), None => { ctx.unresolved.insert(ast::Dependency { ident: name, usage: name_span, }); ast::IdentExpr::Unresolved(name) } } } fn primary_expression<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, token: TokenSpan<'a>, ) -> Result<'a, Handle>> { self.push_rule_span(Rule::PrimaryExpr, lexer); const fn literal_ray_flag<'b>(flag: crate::RayFlag) -> ast::Expression<'b> { ast::Expression::Literal(ast::Literal::Number(Number::U32(flag.bits()))) } const fn literal_ray_intersection<'b>( intersection: crate::RayQueryIntersection, ) -> ast::Expression<'b> { ast::Expression::Literal(ast::Literal::Number(Number::U32(intersection as u32))) } let expr = match token { (Token::Paren('('), _) => { let expr = self.enclosed_expression(lexer, ctx)?; lexer.expect(Token::Paren(')'))?; self.pop_rule_span(lexer); return Ok(expr); } (Token::Word("true"), _) => ast::Expression::Literal(ast::Literal::Bool(true)), (Token::Word("false"), _) => ast::Expression::Literal(ast::Literal::Bool(false)), (Token::Number(res), span) => { let num = res.map_err(|err| Error::BadNumber(span, err))?; if let Some(enable_extension) = num.requires_enable_extension() { lexer.require_enable_extension(enable_extension, span)?; } ast::Expression::Literal(ast::Literal::Number(num)) } (Token::Word("RAY_FLAG_NONE"), _) => literal_ray_flag(crate::RayFlag::empty()), (Token::Word("RAY_FLAG_FORCE_OPAQUE"), _) => { literal_ray_flag(crate::RayFlag::FORCE_OPAQUE) } (Token::Word("RAY_FLAG_FORCE_NO_OPAQUE"), _) => { literal_ray_flag(crate::RayFlag::FORCE_NO_OPAQUE) } (Token::Word("RAY_FLAG_TERMINATE_ON_FIRST_HIT"), _) => { literal_ray_flag(crate::RayFlag::TERMINATE_ON_FIRST_HIT) } (Token::Word("RAY_FLAG_SKIP_CLOSEST_HIT_SHADER"), _) => { literal_ray_flag(crate::RayFlag::SKIP_CLOSEST_HIT_SHADER) } (Token::Word("RAY_FLAG_CULL_BACK_FACING"), _) => { literal_ray_flag(crate::RayFlag::CULL_BACK_FACING) } (Token::Word("RAY_FLAG_CULL_FRONT_FACING"), _) => { literal_ray_flag(crate::RayFlag::CULL_FRONT_FACING) } (Token::Word("RAY_FLAG_CULL_OPAQUE"), _) => { literal_ray_flag(crate::RayFlag::CULL_OPAQUE) } (Token::Word("RAY_FLAG_CULL_NO_OPAQUE"), _) => { literal_ray_flag(crate::RayFlag::CULL_NO_OPAQUE) } (Token::Word("RAY_FLAG_SKIP_TRIANGLES"), _) => { literal_ray_flag(crate::RayFlag::SKIP_TRIANGLES) } (Token::Word("RAY_FLAG_SKIP_AABBS"), _) => literal_ray_flag(crate::RayFlag::SKIP_AABBS), (Token::Word("RAY_QUERY_INTERSECTION_NONE"), _) => { literal_ray_intersection(crate::RayQueryIntersection::None) } (Token::Word("RAY_QUERY_INTERSECTION_TRIANGLE"), _) => { literal_ray_intersection(crate::RayQueryIntersection::Triangle) } (Token::Word("RAY_QUERY_INTERSECTION_GENERATED"), _) => { literal_ray_intersection(crate::RayQueryIntersection::Generated) } (Token::Word("RAY_QUERY_INTERSECTION_AABB"), _) => { literal_ray_intersection(crate::RayQueryIntersection::Aabb) } (Token::Word(word), span) => { let ident = self.template_elaborated_ident(word, span, lexer, ctx)?; if let Token::Paren('(') = lexer.peek().0 { let arguments = self.arguments(lexer, ctx)?; ast::Expression::Call(ast::CallPhrase { function: ident, arguments, }) } else { ast::Expression::Ident(ident) } } other => { return Err(Box::new(Error::Unexpected( other.1, ExpectedToken::PrimaryExpression, ))) } }; self.pop_rule_span(lexer); let span = lexer.span_with_start(token.1); let expr = ctx.expressions.append(expr, span); Ok(expr) } fn component_or_swizzle_specifier<'a>( &mut self, expr_start: Span, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, expr: Handle>, ) -> Result<'a, Handle>> { let mut expr = expr; loop { let expression = match lexer.peek().0 { Token::Separator('.') => { let _ = lexer.next(); let field = lexer.next_ident()?; ast::Expression::Member { base: expr, field } } Token::Paren('[') => { let _ = lexer.next(); let index = self.enclosed_expression(lexer, ctx)?; lexer.expect(Token::Paren(']'))?; ast::Expression::Index { base: expr, index } } _ => break, }; let span = lexer.span_with_start(expr_start); expr = ctx.expressions.append(expression, span); } Ok(expr) } /// Parse a `unary_expression`. fn unary_expression<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, Handle>> { self.push_rule_span(Rule::UnaryExpr, lexer); enum UnaryOp { Negate, LogicalNot, BitwiseNot, Deref, AddrOf, } let mut ops = Vec::new(); let mut expr; loop { match lexer.next() { (Token::Operation('-'), span) => { ops.push((UnaryOp::Negate, span)); } (Token::Operation('!'), span) => { ops.push((UnaryOp::LogicalNot, span)); } (Token::Operation('~'), span) => { ops.push((UnaryOp::BitwiseNot, span)); } (Token::Operation('*'), span) => { ops.push((UnaryOp::Deref, span)); } (Token::Operation('&'), span) => { ops.push((UnaryOp::AddrOf, span)); } token => { expr = self.singular_expression(lexer, ctx, token)?; break; } }; } for (op, span) in ops.into_iter().rev() { let e = match op { UnaryOp::Negate => ast::Expression::Unary { op: crate::UnaryOperator::Negate, expr, }, UnaryOp::LogicalNot => ast::Expression::Unary { op: crate::UnaryOperator::LogicalNot, expr, }, UnaryOp::BitwiseNot => ast::Expression::Unary { op: crate::UnaryOperator::BitwiseNot, expr, }, UnaryOp::Deref => ast::Expression::Deref(expr), UnaryOp::AddrOf => ast::Expression::AddrOf(expr), }; let span = lexer.span_with_start(span); expr = ctx.expressions.append(e, span); } self.pop_rule_span(lexer); Ok(expr) } /// Parse a `lhs_expression`. /// /// LHS expressions only support the `&` and `*` operators and /// the `[]` and `.` postfix selectors. fn lhs_expression<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, token: Option>, expected_token: ExpectedToken<'a>, ) -> Result<'a, Handle>> { self.track_recursion(|this| { this.push_rule_span(Rule::LhsExpr, lexer); let token = token.unwrap_or_else(|| lexer.next()); let expr = match token { (Token::Operation('*'), _) => { let expr = this.lhs_expression(lexer, ctx, None, ExpectedToken::LhsExpression)?; let expr = ast::Expression::Deref(expr); let span = this.peek_rule_span(lexer); ctx.expressions.append(expr, span) } (Token::Operation('&'), _) => { let expr = this.lhs_expression(lexer, ctx, None, ExpectedToken::LhsExpression)?; let expr = ast::Expression::AddrOf(expr); let span = this.peek_rule_span(lexer); ctx.expressions.append(expr, span) } (Token::Paren('('), span) => { let expr = this.lhs_expression(lexer, ctx, None, ExpectedToken::LhsExpression)?; lexer.expect(Token::Paren(')'))?; this.component_or_swizzle_specifier(span, lexer, ctx, expr)? } (Token::Word(word), span) => { let ident = this.ident_expr(word, span, ctx); let ident = ast::TemplateElaboratedIdent { ident, ident_span: span, template_list: Vec::new(), template_list_span: Span::UNDEFINED, }; let ident = ctx.expressions.append(ast::Expression::Ident(ident), span); this.component_or_swizzle_specifier(span, lexer, ctx, ident)? } (_, span) => { return Err(Box::new(Error::Unexpected(span, expected_token))); } }; this.pop_rule_span(lexer); Ok(expr) }) } /// Parse a `singular_expression`. fn singular_expression<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, token: TokenSpan<'a>, ) -> Result<'a, Handle>> { self.push_rule_span(Rule::SingularExpr, lexer); let primary_expr = self.primary_expression(lexer, ctx, token)?; let singular_expr = self.component_or_swizzle_specifier(token.1, lexer, ctx, primary_expr)?; self.pop_rule_span(lexer); Ok(singular_expr) } fn equality_expression<'a>( &mut self, lexer: &mut Lexer<'a>, context: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, Handle>> { // equality_expression context.parse_binary_op( lexer, |token| match token { Token::LogicalOperation('=') => Some(crate::BinaryOperator::Equal), Token::LogicalOperation('!') => Some(crate::BinaryOperator::NotEqual), _ => None, }, // relational_expression |lexer, context| { let enclosing = self.race_rules(Rule::GenericExpr, Rule::EnclosedExpr); context.parse_binary_op( lexer, match enclosing { Some(Rule::GenericExpr) => |token| match token { Token::LogicalOperation('<') => Some(crate::BinaryOperator::LessEqual), _ => None, }, _ => |token| match token { Token::Paren('<') => Some(crate::BinaryOperator::Less), Token::Paren('>') => Some(crate::BinaryOperator::Greater), Token::LogicalOperation('<') => Some(crate::BinaryOperator::LessEqual), Token::LogicalOperation('>') => { Some(crate::BinaryOperator::GreaterEqual) } _ => None, }, }, // shift_expression |lexer, context| { context.parse_binary_op( lexer, match enclosing { Some(Rule::GenericExpr) => |token| match token { Token::ShiftOperation('<') => { Some(crate::BinaryOperator::ShiftLeft) } _ => None, }, _ => |token| match token { Token::ShiftOperation('<') => { Some(crate::BinaryOperator::ShiftLeft) } Token::ShiftOperation('>') => { Some(crate::BinaryOperator::ShiftRight) } _ => None, }, }, // additive_expression |lexer, context| { context.parse_binary_op( lexer, |token| match token { Token::Operation('+') => Some(crate::BinaryOperator::Add), Token::Operation('-') => { Some(crate::BinaryOperator::Subtract) } _ => None, }, // multiplicative_expression |lexer, context| { context.parse_binary_op( lexer, |token| match token { Token::Operation('*') => { Some(crate::BinaryOperator::Multiply) } Token::Operation('/') => { Some(crate::BinaryOperator::Divide) } Token::Operation('%') => { Some(crate::BinaryOperator::Modulo) } _ => None, }, |lexer, context| self.unary_expression(lexer, context), ) }, ) }, ) }, ) }, ) } fn expression<'a>( &mut self, lexer: &mut Lexer<'a>, context: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, Handle>> { self.push_rule_span(Rule::GeneralExpr, lexer); // logical_or_expression let handle = context.parse_binary_op( lexer, |token| match token { Token::LogicalOperation('|') => Some(crate::BinaryOperator::LogicalOr), _ => None, }, // logical_and_expression |lexer, context| { context.parse_binary_op( lexer, |token| match token { Token::LogicalOperation('&') => Some(crate::BinaryOperator::LogicalAnd), _ => None, }, // inclusive_or_expression |lexer, context| { context.parse_binary_op( lexer, |token| match token { Token::Operation('|') => Some(crate::BinaryOperator::InclusiveOr), _ => None, }, // exclusive_or_expression |lexer, context| { context.parse_binary_op( lexer, |token| match token { Token::Operation('^') => { Some(crate::BinaryOperator::ExclusiveOr) } _ => None, }, // and_expression |lexer, context| { context.parse_binary_op( lexer, |token| match token { Token::Operation('&') => { Some(crate::BinaryOperator::And) } _ => None, }, |lexer, context| { self.equality_expression(lexer, context) }, ) }, ) }, ) }, ) }, )?; self.pop_rule_span(lexer); Ok(handle) } fn optionally_typed_ident<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, (ast::Ident<'a>, Option>)> { let name = lexer.next_ident()?; let ty = if lexer.next_if(Token::Separator(':')) { Some(self.type_specifier(lexer, ctx)?) } else { None }; Ok((name, ty)) } /// 'var' _disambiguate_template template_list? optionally_typed_ident fn variable_decl<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, ast::GlobalVariable<'a>> { self.push_rule_span(Rule::VariableDecl, lexer); let (template_list, _) = self.maybe_template_list(lexer, ctx)?; let (name, ty) = self.optionally_typed_ident(lexer, ctx)?; let init = if lexer.next_if(Token::Operation('=')) { let handle = self.expression(lexer, ctx)?; Some(handle) } else { None }; lexer.expect(Token::Separator(';'))?; self.pop_rule_span(lexer); Ok(ast::GlobalVariable { name, template_list, binding: None, ty, init, doc_comments: Vec::new(), memory_decorations: crate::MemoryDecorations::empty(), }) } fn struct_body<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, Vec>> { let mut members = Vec::new(); let mut member_names = FastHashSet::default(); lexer.expect(Token::Paren('{'))?; let mut ready = true; while !lexer.next_if(Token::Paren('}')) { if !ready { return Err(Box::new(Error::Unexpected( lexer.next().1, ExpectedToken::Token(Token::Separator(',')), ))); } let doc_comments = lexer.accumulate_doc_comments(); let (mut size, mut align) = (ParsedAttribute::default(), ParsedAttribute::default()); self.push_rule_span(Rule::Attribute, lexer); let mut bind_parser = BindingParser::default(); while lexer.next_if(Token::Attribute) { match lexer.next_ident_with_span()? { ("size", name_span) => { lexer.expect(Token::Paren('('))?; let expr = self.expression(lexer, ctx)?; lexer.next_if(Token::Separator(',')); lexer.expect(Token::Paren(')'))?; size.set(expr, name_span)?; } ("align", name_span) => { lexer.expect(Token::Paren('('))?; let expr = self.expression(lexer, ctx)?; lexer.next_if(Token::Separator(',')); lexer.expect(Token::Paren(')'))?; align.set(expr, name_span)?; } (word, word_span) => bind_parser.parse(self, lexer, word, word_span, ctx)?, } } let bind_span = self.pop_rule_span(lexer); let binding = bind_parser.finish(bind_span)?; let name = lexer.next_ident()?; lexer.expect(Token::Separator(':'))?; let ty = self.type_specifier(lexer, ctx)?; ready = lexer.next_if(Token::Separator(',')); members.push(ast::StructMember { name, ty, binding, size: size.value, align: align.value, doc_comments, }); if !member_names.insert(name.name) { return Err(Box::new(Error::Redefinition { previous: members .iter() .find(|x| x.name.name == name.name) .map(|x| x.name.span) .unwrap(), current: name.span, })); } } Ok(members) } fn maybe_template_list<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, (Vec>>, Span)> { let start = lexer.start_byte_offset(); if lexer.next_if(Token::TemplateArgsStart) { let mut args = Vec::new(); args.push(self.expression(lexer, ctx)?); while lexer.next_if(Token::Separator(',')) && lexer.peek().0 != Token::TemplateArgsEnd { args.push(self.expression(lexer, ctx)?); } lexer.expect(Token::TemplateArgsEnd)?; let span = lexer.span_from(start); Ok((args, span)) } else { Ok((Vec::new(), Span::UNDEFINED)) } } fn template_elaborated_ident<'a>( &mut self, word: &'a str, span: Span, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, ast::TemplateElaboratedIdent<'a>> { let ident = self.ident_expr(word, span, ctx); let (template_list, template_list_span) = self.maybe_template_list(lexer, ctx)?; Ok(ast::TemplateElaboratedIdent { ident, ident_span: span, template_list, template_list_span, }) } fn type_specifier<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, ast::TemplateElaboratedIdent<'a>> { let (name, span) = lexer.next_ident_with_span()?; self.template_elaborated_ident(name, span, lexer, ctx) } /// Parses assignment, increment and decrement statements /// /// This does not consume or require a final `;` token. In the update /// expression of a C-style `for` loop header, there is no terminating `;`. fn variable_updating_statement<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, block: &mut ast::Block<'a>, token: TokenSpan<'a>, expected_token: ExpectedToken<'a>, ) -> Result<'a, ()> { match token { (Token::Word("_"), span) => { lexer.expect(Token::Operation('='))?; let expr = self.expression(lexer, ctx)?; let span = lexer.span_with_start(span); block.stmts.push(ast::Statement { kind: ast::StatementKind::Phony(expr), span, }); return Ok(()); } _ => {} } let target = self.lhs_expression(lexer, ctx, Some(token), expected_token)?; let (op, value) = match lexer.next() { (Token::Operation('='), _) => { let value = self.expression(lexer, ctx)?; (None, value) } (Token::AssignmentOperation(c), _) => { use crate::BinaryOperator as Bo; let op = match c { '<' => Bo::ShiftLeft, '>' => Bo::ShiftRight, '+' => Bo::Add, '-' => Bo::Subtract, '*' => Bo::Multiply, '/' => Bo::Divide, '%' => Bo::Modulo, '&' => Bo::And, '|' => Bo::InclusiveOr, '^' => Bo::ExclusiveOr, // Note: `consume_token` shouldn't produce any other assignment ops _ => unreachable!(), }; let value = self.expression(lexer, ctx)?; (Some(op), value) } op_token @ (Token::IncrementOperation | Token::DecrementOperation, _) => { let op = match op_token.0 { Token::IncrementOperation => ast::StatementKind::Increment, Token::DecrementOperation => ast::StatementKind::Decrement, _ => unreachable!(), }; let span = lexer.span_with_start(token.1); block.stmts.push(ast::Statement { kind: op(target), span, }); return Ok(()); } (_, span) => return Err(Box::new(Error::Unexpected(span, ExpectedToken::Assignment))), }; let span = lexer.span_with_start(token.1); block.stmts.push(ast::Statement { kind: ast::StatementKind::Assign { target, op, value }, span, }); Ok(()) } /// Parse a function call statement. /// /// This assumes that `token` has been consumed from the lexer. /// /// This does not consume or require a final `;` token. In the update /// expression of a C-style `for` loop header, there is no terminating `;`. fn maybe_func_call_statement<'a>( &mut self, lexer: &mut Lexer<'a>, context: &mut ExpressionContext<'a, '_, '_>, block: &mut ast::Block<'a>, token: TokenSpan<'a>, ) -> Result<'a, bool> { let (name, name_span) = match token { (Token::Word(name), span) => (name, span), _ => return Ok(false), }; let ident = self.template_elaborated_ident(name, name_span, lexer, context)?; if ident.template_list.is_empty() && !matches!(lexer.peek(), (Token::Paren('('), _)) { return Ok(false); } self.push_rule_span(Rule::SingularExpr, lexer); let arguments = self.arguments(lexer, context)?; let span = lexer.span_with_start(name_span); block.stmts.push(ast::Statement { kind: ast::StatementKind::Call(ast::CallPhrase { function: ident, arguments, }), span, }); self.pop_rule_span(lexer); Ok(true) } /// Parses func_call_statement and variable_updating_statement /// /// This does not consume or require a final `;` token. In the update /// expression of a C-style `for` loop header, there is no terminating `;`. fn func_call_or_variable_updating_statement<'a>( &mut self, lexer: &mut Lexer<'a>, context: &mut ExpressionContext<'a, '_, '_>, block: &mut ast::Block<'a>, token: TokenSpan<'a>, expected_token: ExpectedToken<'a>, ) -> Result<'a, ()> { if !self.maybe_func_call_statement(lexer, context, block, token)? { self.variable_updating_statement(lexer, context, block, token, expected_token)?; } Ok(()) } /// Parses variable_or_value_statement, func_call_statement and variable_updating_statement. /// /// This is equivalent to the `for_init` production in the WGSL spec, /// but it's also used for parsing these forms when they appear within a block, /// hence the longer name. /// /// This does not consume the following `;` token. fn variable_or_value_or_func_call_or_variable_updating_statement<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, block: &mut ast::Block<'a>, token: TokenSpan<'a>, expected_token: ExpectedToken<'a>, ) -> Result<'a, ()> { let local_decl = match token { (Token::Word("let"), _) => { let (name, given_ty) = self.optionally_typed_ident(lexer, ctx)?; lexer.expect(Token::Operation('='))?; let expr_id = self.expression(lexer, ctx)?; let handle = ctx.declare_local(name)?; ast::LocalDecl::Let(ast::Let { name, ty: given_ty, init: expr_id, handle, }) } (Token::Word("const"), _) => { let (name, given_ty) = self.optionally_typed_ident(lexer, ctx)?; lexer.expect(Token::Operation('='))?; let expr_id = self.expression(lexer, ctx)?; let handle = ctx.declare_local(name)?; ast::LocalDecl::Const(ast::LocalConst { name, ty: given_ty, init: expr_id, handle, }) } (Token::Word("var"), _) => { if lexer.next_if(Token::TemplateArgsStart) { let (class_str, span) = lexer.next_ident_with_span()?; if class_str != "function" { return Err(Box::new(Error::InvalidLocalVariableAddressSpace(span))); } lexer.expect(Token::TemplateArgsEnd)?; } let (name, ty) = self.optionally_typed_ident(lexer, ctx)?; let init = if lexer.next_if(Token::Operation('=')) { let init = self.expression(lexer, ctx)?; Some(init) } else { None }; let handle = ctx.declare_local(name)?; ast::LocalDecl::Var(ast::LocalVariable { name, ty, init, handle, }) } token => { return self.func_call_or_variable_updating_statement( lexer, ctx, block, token, expected_token, ); } }; let span = lexer.span_with_start(token.1); block.stmts.push(ast::Statement { kind: ast::StatementKind::LocalDecl(local_decl), span, }); Ok(()) } fn statement<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, block: &mut ast::Block<'a>, brace_nesting_level: u8, ) -> Result<'a, ()> { self.track_recursion(|this| { this.push_rule_span(Rule::Statement, lexer); // We peek here instead of eagerly getting the next token since // `Parser::block` expects its first token to be `{`. // // Most callers have a single path leading to the start of the block; // `statement` is the only exception where there are multiple choices. match lexer.peek() { (token, _) if is_start_of_compound_statement(token) => { let (inner, span) = this.block(lexer, ctx, brace_nesting_level)?; block.stmts.push(ast::Statement { kind: ast::StatementKind::Block(inner), span, }); this.pop_rule_span(lexer); return Ok(()); } _ => {} } let kind = match lexer.next() { (Token::Separator(';'), _) => { this.pop_rule_span(lexer); return Ok(()); } (Token::Word("return"), _) => { let value = if lexer.peek().0 != Token::Separator(';') { let handle = this.expression(lexer, ctx)?; Some(handle) } else { None }; lexer.expect(Token::Separator(';'))?; ast::StatementKind::Return { value } } (Token::Word("if"), _) => { let condition = this.expression(lexer, ctx)?; let accept = this.block(lexer, ctx, brace_nesting_level)?.0; let mut elsif_stack = Vec::new(); let mut elseif_span_start = lexer.start_byte_offset(); let mut reject = loop { if !lexer.next_if(Token::Word("else")) { break ast::Block::default(); } if !lexer.next_if(Token::Word("if")) { // ... else { ... } break this.block(lexer, ctx, brace_nesting_level)?.0; } // ... else if (...) { ... } let other_condition = this.expression(lexer, ctx)?; let other_block = this.block(lexer, ctx, brace_nesting_level)?; elsif_stack.push((elseif_span_start, other_condition, other_block)); elseif_span_start = lexer.start_byte_offset(); }; // reverse-fold the else-if blocks //Note: we may consider uplifting this to the IR for (other_span_start, other_cond, other_block) in elsif_stack.into_iter().rev() { let sub_stmt = ast::StatementKind::If { condition: other_cond, accept: other_block.0, reject, }; reject = ast::Block::default(); let span = lexer.span_from(other_span_start); reject.stmts.push(ast::Statement { kind: sub_stmt, span, }) } ast::StatementKind::If { condition, accept, reject, } } (Token::Word("switch"), _) => { let selector = this.expression(lexer, ctx)?; let brace_span = lexer.expect_span(Token::Paren('{'))?; let brace_nesting_level = Self::increase_brace_nesting(brace_nesting_level, brace_span)?; let mut cases = Vec::new(); loop { // cases + default match lexer.next() { (Token::Word("case"), _) => { // parse a list of values let value = loop { let value = this.switch_value(lexer, ctx)?; if lexer.next_if(Token::Separator(',')) { // list of values ends with ':' or a compound statement let next_token = lexer.peek().0; if next_token == Token::Separator(':') || is_start_of_compound_statement(next_token) { break value; } } else { break value; } cases.push(ast::SwitchCase { value, body: ast::Block::default(), fall_through: true, }); }; lexer.next_if(Token::Separator(':')); let body = this.block(lexer, ctx, brace_nesting_level)?.0; cases.push(ast::SwitchCase { value, body, fall_through: false, }); } (Token::Word("default"), _) => { lexer.next_if(Token::Separator(':')); let body = this.block(lexer, ctx, brace_nesting_level)?.0; cases.push(ast::SwitchCase { value: ast::SwitchValue::Default, body, fall_through: false, }); } (Token::Paren('}'), _) => break, (_, span) => { return Err(Box::new(Error::Unexpected( span, ExpectedToken::SwitchItem, ))) } } } ast::StatementKind::Switch { selector, cases } } (Token::Word("loop"), _) => this.r#loop(lexer, ctx, brace_nesting_level)?, (Token::Word("while"), _) => { let mut body = ast::Block::default(); let (condition, span) = lexer.capture_span(|lexer| this.expression(lexer, ctx))?; let mut reject = ast::Block::default(); reject.stmts.push(ast::Statement { kind: ast::StatementKind::Break, span, }); body.stmts.push(ast::Statement { kind: ast::StatementKind::If { condition, accept: ast::Block::default(), reject, }, span, }); let (block, span) = this.block(lexer, ctx, brace_nesting_level)?; body.stmts.push(ast::Statement { kind: ast::StatementKind::Block(block), span, }); ast::StatementKind::Loop { body, continuing: ast::Block::default(), break_if: None, } } (Token::Word("for"), _) => { lexer.expect(Token::Paren('('))?; ctx.local_table.push_scope(); if !lexer.next_if(Token::Separator(';')) { let token = lexer.next(); this.variable_or_value_or_func_call_or_variable_updating_statement( lexer, ctx, block, token, ExpectedToken::ForInit, )?; lexer.expect(Token::Separator(';'))?; }; let mut body = ast::Block::default(); if !lexer.next_if(Token::Separator(';')) { let (condition, span) = lexer.capture_span(|lexer| -> Result<'_, _> { let condition = this.expression(lexer, ctx)?; lexer.expect(Token::Separator(';'))?; Ok(condition) })?; let mut reject = ast::Block::default(); reject.stmts.push(ast::Statement { kind: ast::StatementKind::Break, span, }); body.stmts.push(ast::Statement { kind: ast::StatementKind::If { condition, accept: ast::Block::default(), reject, }, span, }); }; let mut continuing = ast::Block::default(); if !lexer.next_if(Token::Paren(')')) { let token = lexer.next(); this.func_call_or_variable_updating_statement( lexer, ctx, &mut continuing, token, ExpectedToken::ForUpdate, )?; lexer.expect(Token::Paren(')'))?; } let (block, span) = this.block(lexer, ctx, brace_nesting_level)?; body.stmts.push(ast::Statement { kind: ast::StatementKind::Block(block), span, }); ctx.local_table.pop_scope(); ast::StatementKind::Loop { body, continuing, break_if: None, } } (Token::Word("break"), span) => { // Check if the next token is an `if`, this indicates // that the user tried to type out a `break if` which // is illegal in this position. let (peeked_token, peeked_span) = lexer.peek(); if let Token::Word("if") = peeked_token { let span = span.until(&peeked_span); return Err(Box::new(Error::InvalidBreakIf(span))); } lexer.expect(Token::Separator(';'))?; ast::StatementKind::Break } (Token::Word("continue"), _) => { lexer.expect(Token::Separator(';'))?; ast::StatementKind::Continue } (Token::Word("discard"), _) => { lexer.expect(Token::Separator(';'))?; ast::StatementKind::Kill } // https://www.w3.org/TR/WGSL/#const-assert-statement (Token::Word("const_assert"), _) => { // parentheses are optional let paren = lexer.next_if(Token::Paren('(')); let condition = this.expression(lexer, ctx)?; if paren { lexer.expect(Token::Paren(')'))?; } lexer.expect(Token::Separator(';'))?; ast::StatementKind::ConstAssert(condition) } token => { this.variable_or_value_or_func_call_or_variable_updating_statement( lexer, ctx, block, token, ExpectedToken::Statement, )?; lexer.expect(Token::Separator(';'))?; this.pop_rule_span(lexer); return Ok(()); } }; let span = this.pop_rule_span(lexer); block.stmts.push(ast::Statement { kind, span }); Ok(()) }) } fn r#loop<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, brace_nesting_level: u8, ) -> Result<'a, ast::StatementKind<'a>> { let mut body = ast::Block::default(); let mut continuing = ast::Block::default(); let mut break_if = None; let brace_span = lexer.expect_span(Token::Paren('{'))?; let brace_nesting_level = Self::increase_brace_nesting(brace_nesting_level, brace_span)?; ctx.local_table.push_scope(); loop { if lexer.next_if(Token::Word("continuing")) { // Branch for the `continuing` block, this must be // the last thing in the loop body // Expect a opening brace to start the continuing block let brace_span = lexer.expect_span(Token::Paren('{'))?; let brace_nesting_level = Self::increase_brace_nesting(brace_nesting_level, brace_span)?; loop { if lexer.next_if(Token::Word("break")) { // Branch for the `break if` statement, this statement // has the form `break if ;` and must be the last // statement in a continuing block // The break must be followed by an `if` to form // the break if lexer.expect(Token::Word("if"))?; let condition = self.expression(lexer, ctx)?; // Set the condition of the break if to the newly parsed // expression break_if = Some(condition); // Expect a semicolon to close the statement lexer.expect(Token::Separator(';'))?; // Expect a closing brace to close the continuing block, // since the break if must be the last statement lexer.expect(Token::Paren('}'))?; // Stop parsing the continuing block break; } else if lexer.next_if(Token::Paren('}')) { // If we encounter a closing brace it means we have reached // the end of the continuing block and should stop processing break; } else { // Otherwise try to parse a statement self.statement(lexer, ctx, &mut continuing, brace_nesting_level)?; } } // Since the continuing block must be the last part of the loop body, // we expect to see a closing brace to end the loop body lexer.expect(Token::Paren('}'))?; break; } if lexer.next_if(Token::Paren('}')) { // If we encounter a closing brace it means we have reached // the end of the loop body and should stop processing break; } // Otherwise try to parse a statement self.statement(lexer, ctx, &mut body, brace_nesting_level)?; } ctx.local_table.pop_scope(); Ok(ast::StatementKind::Loop { body, continuing, break_if, }) } /// compound_statement fn block<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, brace_nesting_level: u8, ) -> Result<'a, (ast::Block<'a>, Span)> { self.push_rule_span(Rule::Block, lexer); ctx.local_table.push_scope(); let mut diagnostic_filters = DiagnosticFilterMap::new(); self.push_rule_span(Rule::Attribute, lexer); while lexer.next_if(Token::Attribute) { let (name, name_span) = lexer.next_ident_with_span()?; if let Some(DirectiveKind::Diagnostic) = DirectiveKind::from_ident(name) { let filter = self.diagnostic_filter(lexer)?; let span = self.peek_rule_span(lexer); diagnostic_filters .add(filter, span, ShouldConflictOnFullDuplicate::Yes) .map_err(|e| Box::new(e.into()))?; } else { return Err(Box::new(Error::Unexpected( name_span, ExpectedToken::DiagnosticAttribute, ))); } } self.pop_rule_span(lexer); if !diagnostic_filters.is_empty() { return Err(Box::new( Error::DiagnosticAttributeNotYetImplementedAtParseSite { site_name_plural: "compound statements", spans: diagnostic_filters.spans().collect(), }, )); } let brace_span = lexer.expect_span(Token::Paren('{'))?; let brace_nesting_level = Self::increase_brace_nesting(brace_nesting_level, brace_span)?; let mut block = ast::Block::default(); while !lexer.next_if(Token::Paren('}')) { self.statement(lexer, ctx, &mut block, brace_nesting_level)?; } ctx.local_table.pop_scope(); let span = self.pop_rule_span(lexer); Ok((block, span)) } fn varying_binding<'a>( &mut self, lexer: &mut Lexer<'a>, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result<'a, Option>> { let mut bind_parser = BindingParser::default(); self.push_rule_span(Rule::Attribute, lexer); while lexer.next_if(Token::Attribute) { let (word, span) = lexer.next_ident_with_span()?; bind_parser.parse(self, lexer, word, span, ctx)?; } let span = self.pop_rule_span(lexer); bind_parser.finish(span) } fn function_decl<'a>( &mut self, lexer: &mut Lexer<'a>, diagnostic_filter_leaf: Option>, must_use: Option, out: &mut ast::TranslationUnit<'a>, dependencies: &mut FastIndexSet>, ) -> Result<'a, ast::Function<'a>> { self.push_rule_span(Rule::FunctionDecl, lexer); // read function name let fun_name = lexer.next_ident()?; let mut locals = Arena::new(); let mut ctx = ExpressionContext { expressions: &mut out.expressions, local_table: &mut SymbolTable::default(), locals: &mut locals, unresolved: dependencies, }; // start a scope that contains arguments as well as the function body ctx.local_table.push_scope(); // Reduce lookup scope to parse the parameter list and return type // avoiding identifier lookup to match newly declared param names. ctx.local_table.reduce_lookup_scope(); // read parameter list let mut arguments = Vec::new(); lexer.expect(Token::Paren('('))?; let mut ready = true; while !lexer.next_if(Token::Paren(')')) { if !ready { return Err(Box::new(Error::Unexpected( lexer.next().1, ExpectedToken::Token(Token::Separator(',')), ))); } let binding = self.varying_binding(lexer, &mut ctx)?; let param_name = lexer.next_ident()?; lexer.expect(Token::Separator(':'))?; let param_type = self.type_specifier(lexer, &mut ctx)?; let handle = ctx.declare_local(param_name)?; arguments.push(ast::FunctionArgument { name: param_name, ty: param_type, binding, handle, }); ready = lexer.next_if(Token::Separator(',')); } // read return type let result = if lexer.next_if(Token::Arrow) { let binding = self.varying_binding(lexer, &mut ctx)?; let ty = self.type_specifier(lexer, &mut ctx)?; let must_use = must_use.is_some(); Some(ast::FunctionResult { ty, binding, must_use, }) } else if let Some(must_use) = must_use { return Err(Box::new(Error::FunctionMustUseReturnsVoid( must_use, self.peek_rule_span(lexer), ))); } else { None }; ctx.local_table.reset_lookup_scope(); // do not use `self.block` here, since we must not push a new scope lexer.expect(Token::Paren('{'))?; let brace_nesting_level = 1; let mut body = ast::Block::default(); while !lexer.next_if(Token::Paren('}')) { self.statement(lexer, &mut ctx, &mut body, brace_nesting_level)?; } ctx.local_table.pop_scope(); let fun = ast::Function { entry_point: None, name: fun_name, arguments, result, body, diagnostic_filter_leaf, doc_comments: Vec::new(), }; // done self.pop_rule_span(lexer); Ok(fun) } fn directive_ident_list<'a>( &self, lexer: &mut Lexer<'a>, handler: impl FnMut(&'a str, Span) -> Result<'a, ()>, ) -> Result<'a, ()> { let mut handler = handler; 'next_arg: loop { let (ident, span) = lexer.next_ident_with_span()?; handler(ident, span)?; let expected_token = match lexer.peek().0 { Token::Separator(',') => { let _ = lexer.next(); if matches!(lexer.peek().0, Token::Word(..)) { continue 'next_arg; } ExpectedToken::AfterIdentListComma } _ => ExpectedToken::AfterIdentListArg, }; if !matches!(lexer.next().0, Token::Separator(';')) { return Err(Box::new(Error::Unexpected(span, expected_token))); } break Ok(()); } } fn global_decl<'a>( &mut self, lexer: &mut Lexer<'a>, out: &mut ast::TranslationUnit<'a>, ) -> Result<'a, ()> { let doc_comments = lexer.accumulate_doc_comments(); // read attributes let mut binding = None; let mut stage = ParsedAttribute::default(); // Span in case we need to report an error for a shader stage missing something (e.g. its workgroup size). // Doesn't need to be set in the vertex and fragment stages because they don't have errors like that. let mut shader_stage_error_span = Span::new(0, 0); let mut workgroup_size = ParsedAttribute::default(); let mut early_depth_test = ParsedAttribute::default(); let (mut bind_index, mut bind_group) = (ParsedAttribute::default(), ParsedAttribute::default()); let mut id = ParsedAttribute::default(); // the payload variable for a mesh shader let mut payload = ParsedAttribute::default(); // the incoming payload from a traceRay call let mut incoming_payload = ParsedAttribute::default(); let mut mesh_output = ParsedAttribute::default(); let mut must_use: ParsedAttribute = ParsedAttribute::default(); let mut memory_decorations = crate::MemoryDecorations::empty(); let mut dependencies = FastIndexSet::default(); let mut ctx = ExpressionContext { expressions: &mut out.expressions, local_table: &mut SymbolTable::default(), locals: &mut Arena::new(), unresolved: &mut dependencies, }; let mut diagnostic_filters = DiagnosticFilterMap::new(); let ensure_no_diag_attrs = |on_what, filters: DiagnosticFilterMap| -> Result<()> { if filters.is_empty() { Ok(()) } else { Err(Box::new(Error::DiagnosticAttributeNotSupported { on_what, spans: filters.spans().collect(), })) } }; self.push_rule_span(Rule::Attribute, lexer); while lexer.next_if(Token::Attribute) { let (name, name_span) = lexer.next_ident_with_span()?; if let Some(DirectiveKind::Diagnostic) = DirectiveKind::from_ident(name) { let filter = self.diagnostic_filter(lexer)?; let span = self.peek_rule_span(lexer); diagnostic_filters .add(filter, span, ShouldConflictOnFullDuplicate::Yes) .map_err(|e| Box::new(e.into()))?; continue; } match name { "binding" => { lexer.expect(Token::Paren('('))?; bind_index.set(self.expression(lexer, &mut ctx)?, name_span)?; lexer.next_if(Token::Separator(',')); lexer.expect(Token::Paren(')'))?; } "group" => { lexer.expect(Token::Paren('('))?; bind_group.set(self.expression(lexer, &mut ctx)?, name_span)?; lexer.next_if(Token::Separator(',')); lexer.expect(Token::Paren(')'))?; } "id" => { lexer.expect(Token::Paren('('))?; id.set(self.expression(lexer, &mut ctx)?, name_span)?; lexer.next_if(Token::Separator(',')); lexer.expect(Token::Paren(')'))?; } "vertex" => { stage.set(ShaderStage::Vertex, name_span)?; } "fragment" => { stage.set(ShaderStage::Fragment, name_span)?; } "compute" => { stage.set(ShaderStage::Compute, name_span)?; shader_stage_error_span = name_span; } "task" => { lexer.require_enable_extension( ImplementedEnableExtension::WgpuMeshShader, name_span, )?; stage.set(ShaderStage::Task, name_span)?; shader_stage_error_span = name_span; } "mesh" => { lexer.require_enable_extension( ImplementedEnableExtension::WgpuMeshShader, name_span, )?; stage.set(ShaderStage::Mesh, name_span)?; shader_stage_error_span = name_span; lexer.expect(Token::Paren('('))?; mesh_output.set(lexer.next_ident_with_span()?, name_span)?; lexer.expect(Token::Paren(')'))?; } "ray_generation" => { lexer.require_enable_extension( ImplementedEnableExtension::WgpuRayTracingPipeline, name_span, )?; stage.set(ShaderStage::RayGeneration, name_span)?; shader_stage_error_span = name_span; } "any_hit" => { lexer.require_enable_extension( ImplementedEnableExtension::WgpuRayTracingPipeline, name_span, )?; stage.set(ShaderStage::AnyHit, name_span)?; shader_stage_error_span = name_span; } "closest_hit" => { lexer.require_enable_extension( ImplementedEnableExtension::WgpuRayTracingPipeline, name_span, )?; stage.set(ShaderStage::ClosestHit, name_span)?; shader_stage_error_span = name_span; } "miss" => { lexer.require_enable_extension( ImplementedEnableExtension::WgpuRayTracingPipeline, name_span, )?; stage.set(ShaderStage::Miss, name_span)?; shader_stage_error_span = name_span; } "payload" => { lexer.require_enable_extension( ImplementedEnableExtension::WgpuMeshShader, name_span, )?; lexer.expect(Token::Paren('('))?; payload.set(lexer.next_ident_with_span()?, name_span)?; lexer.expect(Token::Paren(')'))?; } "incoming_payload" => { lexer.require_enable_extension( ImplementedEnableExtension::WgpuRayTracingPipeline, name_span, )?; lexer.expect(Token::Paren('('))?; incoming_payload.set(lexer.next_ident_with_span()?, name_span)?; lexer.expect(Token::Paren(')'))?; } "workgroup_size" => { lexer.expect(Token::Paren('('))?; let mut new_workgroup_size = [None; 3]; for size in new_workgroup_size.iter_mut() { *size = Some(self.expression(lexer, &mut ctx)?); match lexer.next() { (Token::Paren(')'), _) => break, (Token::Separator(','), _) => { if lexer.next_if(Token::Paren(')')) { break; } } other => { return Err(Box::new(Error::Unexpected( other.1, ExpectedToken::WorkgroupSizeSeparator, ))) } } } workgroup_size.set(new_workgroup_size, name_span)?; } "early_depth_test" => { lexer.expect(Token::Paren('('))?; let (ident, ident_span) = lexer.next_ident_with_span()?; let value = if ident == "force" { crate::EarlyDepthTest::Force } else { crate::EarlyDepthTest::Allow { conservative: conv::map_conservative_depth(ident, ident_span)?, } }; lexer.expect(Token::Paren(')'))?; early_depth_test.set(value, name_span)?; } "must_use" => { must_use.set(name_span, name_span)?; } "coherent" => { memory_decorations |= crate::MemoryDecorations::COHERENT; } "volatile" => { memory_decorations |= crate::MemoryDecorations::VOLATILE; } _ => return Err(Box::new(Error::UnknownAttribute(name_span))), } } let attrib_span = self.pop_rule_span(lexer); match (bind_group.value, bind_index.value) { (Some(group), Some(index)) => { binding = Some(ast::ResourceBinding { group, binding: index, }); } (Some(_), None) => { return Err(Box::new(Error::MissingAttribute("binding", attrib_span))) } (None, Some(_)) => return Err(Box::new(Error::MissingAttribute("group", attrib_span))), (None, None) => {} } // read item let start = lexer.start_byte_offset(); let kind = match lexer.next() { (Token::Separator(';'), _) => { ensure_no_diag_attrs( DiagnosticAttributeNotSupportedPosition::SemicolonInModulePosition, diagnostic_filters, )?; None } (Token::Word(word), directive_span) if DirectiveKind::from_ident(word).is_some() => { return Err(Box::new(Error::DirectiveAfterFirstGlobalDecl { directive_span, })); } (Token::Word("struct"), _) => { ensure_no_diag_attrs("`struct`s".into(), diagnostic_filters)?; let name = lexer.next_ident()?; let members = self.struct_body(lexer, &mut ctx)?; Some(ast::GlobalDeclKind::Struct(ast::Struct { name, members, doc_comments, })) } (Token::Word("alias"), _) => { ensure_no_diag_attrs("`alias`es".into(), diagnostic_filters)?; let name = lexer.next_ident()?; lexer.expect(Token::Operation('='))?; let ty = self.type_specifier(lexer, &mut ctx)?; lexer.expect(Token::Separator(';'))?; Some(ast::GlobalDeclKind::Type(ast::TypeAlias { name, ty })) } (Token::Word("const"), _) => { ensure_no_diag_attrs("`const`s".into(), diagnostic_filters)?; let (name, ty) = self.optionally_typed_ident(lexer, &mut ctx)?; lexer.expect(Token::Operation('='))?; let init = self.expression(lexer, &mut ctx)?; lexer.expect(Token::Separator(';'))?; Some(ast::GlobalDeclKind::Const(ast::Const { name, ty, init, doc_comments, })) } (Token::Word("override"), _) => { ensure_no_diag_attrs("`override`s".into(), diagnostic_filters)?; let (name, ty) = self.optionally_typed_ident(lexer, &mut ctx)?; let init = if lexer.next_if(Token::Operation('=')) { Some(self.expression(lexer, &mut ctx)?) } else { None }; lexer.expect(Token::Separator(';'))?; Some(ast::GlobalDeclKind::Override(ast::Override { name, id: id.value, ty, init, })) } (Token::Word("var"), _) => { ensure_no_diag_attrs("`var`s".into(), diagnostic_filters)?; let mut var = self.variable_decl(lexer, &mut ctx)?; var.binding = binding.take(); var.doc_comments = doc_comments; var.memory_decorations = memory_decorations; Some(ast::GlobalDeclKind::Var(var)) } (Token::Word("fn"), _) => { let diagnostic_filter_leaf = Self::write_diagnostic_filters( &mut out.diagnostic_filters, diagnostic_filters, out.diagnostic_filter_leaf, ); let function = self.function_decl( lexer, diagnostic_filter_leaf, must_use.value, out, &mut dependencies, )?; Some(ast::GlobalDeclKind::Fn(ast::Function { entry_point: if let Some(stage) = stage.value { if stage.compute_like() && workgroup_size.value.is_none() { return Err(Box::new(Error::MissingWorkgroupSize( shader_stage_error_span, ))); } match stage { ShaderStage::AnyHit | ShaderStage::ClosestHit | ShaderStage::Miss => { if incoming_payload.value.is_none() { return Err(Box::new(Error::MissingIncomingPayload( shader_stage_error_span, ))); } } _ => {} } Some(ast::EntryPoint { stage, early_depth_test: early_depth_test.value, workgroup_size: workgroup_size.value, mesh_output_variable: mesh_output.value, task_payload: payload.value, ray_incoming_payload: incoming_payload.value, }) } else { None }, doc_comments, ..function })) } (Token::Word("const_assert"), _) => { ensure_no_diag_attrs("`const_assert`s".into(), diagnostic_filters)?; // parentheses are optional let paren = lexer.next_if(Token::Paren('(')); let condition = self.expression(lexer, &mut ctx)?; if paren { lexer.expect(Token::Paren(')'))?; } lexer.expect(Token::Separator(';'))?; Some(ast::GlobalDeclKind::ConstAssert(condition)) } (Token::End, _) => return Ok(()), other => { return Err(Box::new(Error::Unexpected( other.1, ExpectedToken::GlobalItem, ))) } }; if let Some(kind) = kind { out.decls.append( ast::GlobalDecl { kind, dependencies }, lexer.span_from(start), ); } if !self.rules.is_empty() { log::error!("Reached the end of global decl, but rule stack is not empty"); log::error!("Rules: {:?}", self.rules); return Err(Box::new(Error::Internal("rule stack is not empty"))); }; match binding { None => Ok(()), Some(_) => Err(Box::new(Error::Internal( "we had the attribute but no var?", ))), } } pub fn parse<'a>( &mut self, source: &'a str, options: &Options, ) -> Result<'a, ast::TranslationUnit<'a>> { self.reset(); let mut lexer = Lexer::new(source, !options.parse_doc_comments); let mut tu = ast::TranslationUnit::default(); let mut enable_extensions = EnableExtensions::empty(); let mut diagnostic_filters = DiagnosticFilterMap::new(); // Parse module doc comments. tu.doc_comments = lexer.accumulate_module_doc_comments(); // Parse directives. while let (Token::Word(word), _) = lexer.peek() { if let Some(kind) = DirectiveKind::from_ident(word) { self.push_rule_span(Rule::Directive, &mut lexer); let _ = lexer.next_ident_with_span().unwrap(); match kind { DirectiveKind::Diagnostic => { let diagnostic_filter = self.diagnostic_filter(&mut lexer)?; let span = self.peek_rule_span(&lexer); diagnostic_filters .add(diagnostic_filter, span, ShouldConflictOnFullDuplicate::No) .map_err(|e| Box::new(e.into()))?; lexer.expect(Token::Separator(';'))?; } DirectiveKind::Enable => { self.directive_ident_list(&mut lexer, |ident, span| { let kind = EnableExtension::from_ident(ident, span)?; let extension = match kind { EnableExtension::Implemented(kind) => kind, EnableExtension::Unimplemented(kind) => { return Err(Box::new(Error::EnableExtensionNotYetImplemented { kind, span, })) } }; // Check if the required capability is supported let required_capability = extension.capability(); if !options.capabilities.contains(required_capability) { return Err(Box::new(Error::EnableExtensionNotSupported { kind, span, })); } enable_extensions.add(extension); Ok(()) })?; } DirectiveKind::Requires => { self.directive_ident_list(&mut lexer, |ident, span| { match LanguageExtension::from_ident(ident) { Some(LanguageExtension::Implemented(_kind)) => { // NOTE: No further validation is needed for an extension, so // just throw parsed information away. If we ever want to apply // what we've parsed to diagnostics, maybe we'll want to refer // to enabled extensions later? Ok(()) } Some(LanguageExtension::Unimplemented(kind)) => { Err(Box::new(Error::LanguageExtensionNotYetImplemented { kind, span, })) } None => Err(Box::new(Error::UnknownLanguageExtension(span, ident))), } })?; } } self.pop_rule_span(&lexer); } else { break; } } lexer.enable_extensions = enable_extensions; tu.enable_extensions = enable_extensions; tu.diagnostic_filter_leaf = Self::write_diagnostic_filters(&mut tu.diagnostic_filters, diagnostic_filters, None); loop { match self.global_decl(&mut lexer, &mut tu) { Err(error) => return Err(error), Ok(()) => { if lexer.peek().0 == Token::End { break; } } } } Ok(tu) } fn increase_brace_nesting(brace_nesting_level: u8, brace_span: Span) -> Result<'static, u8> { // From [spec.](https://gpuweb.github.io/gpuweb/wgsl/#limits): // // > § 2.4. Limits // > // > … // > // > Maximum nesting depth of brace-enclosed statements in a function[:] 127 const BRACE_NESTING_MAXIMUM: u8 = 127; if brace_nesting_level + 1 > BRACE_NESTING_MAXIMUM { return Err(Box::new(Error::ExceededLimitForNestedBraces { span: brace_span, limit: BRACE_NESTING_MAXIMUM, })); } Ok(brace_nesting_level + 1) } fn diagnostic_filter<'a>(&self, lexer: &mut Lexer<'a>) -> Result<'a, DiagnosticFilter> { lexer.expect(Token::Paren('('))?; let (severity_control_name, severity_control_name_span) = lexer.next_ident_with_span()?; let new_severity = diagnostic_filter::Severity::from_wgsl_ident(severity_control_name) .ok_or(Error::DiagnosticInvalidSeverity { severity_control_name_span, })?; lexer.expect(Token::Separator(','))?; let (diagnostic_name_token, diagnostic_name_token_span) = lexer.next_ident_with_span()?; let triggering_rule = if lexer.next_if(Token::Separator('.')) { let (ident, _span) = lexer.next_ident_with_span()?; FilterableTriggeringRule::User(Box::new([diagnostic_name_token.into(), ident.into()])) } else { let diagnostic_rule_name = diagnostic_name_token; let diagnostic_rule_name_span = diagnostic_name_token_span; if let Some(triggering_rule) = StandardFilterableTriggeringRule::from_wgsl_ident(diagnostic_rule_name) { FilterableTriggeringRule::Standard(triggering_rule) } else { diagnostic_filter::Severity::Warning.report_wgsl_parse_diag( Box::new(Error::UnknownDiagnosticRuleName(diagnostic_rule_name_span)), lexer.source, )?; FilterableTriggeringRule::Unknown(diagnostic_rule_name.into()) } }; let filter = DiagnosticFilter { triggering_rule, new_severity, }; lexer.next_if(Token::Separator(',')); lexer.expect(Token::Paren(')'))?; Ok(filter) } pub(crate) fn write_diagnostic_filters( arena: &mut Arena, filters: DiagnosticFilterMap, parent: Option>, ) -> Option> { filters .into_iter() .fold(parent, |parent, (triggering_rule, (new_severity, span))| { Some(arena.append( DiagnosticFilterNode { inner: DiagnosticFilter { new_severity, triggering_rule, }, parent, }, span, )) }) } } const fn is_start_of_compound_statement<'a>(token: Token<'a>) -> bool { matches!(token, Token::Attribute | Token::Paren('{')) } ================================================ FILE: naga/src/front/wgsl/parse/number.rs ================================================ use alloc::format; use crate::front::wgsl::error::NumberError; use crate::front::wgsl::parse::directive::enable_extension::ImplementedEnableExtension; use crate::front::wgsl::parse::lexer::Token; use half::f16; /// When using this type assume no Abstract Int/Float for now #[derive(Copy, Clone, Debug, PartialEq)] pub enum Number { /// Abstract Int (-2^63 ≤ i < 2^63) AbstractInt(i64), /// Abstract Float (IEEE-754 binary64) AbstractFloat(f64), /// Concrete i32 I32(i32), /// Concrete u32 U32(u32), /// Concrete i64 I64(i64), /// Concrete u64 U64(u64), /// Concrete f16 F16(f16), /// Concrete f32 F32(f32), /// Concrete f64 F64(f64), } impl Number { pub(super) const fn requires_enable_extension(&self) -> Option { match *self { Number::F16(_) => Some(ImplementedEnableExtension::F16), _ => None, } } } pub(in crate::front::wgsl) fn consume_number(input: &str) -> (Token<'_>, &str) { let (result, rest) = parse(input); (Token::Number(result), rest) } enum Kind { Int(IntKind), Float(FloatKind), } enum IntKind { I32, U32, I64, U64, } #[derive(Debug)] enum FloatKind { F16, F32, F64, } // The following regexes (from the WGSL spec) will be matched: // int_literal: // | / 0 [iu]? / // | / [1-9][0-9]* [iu]? / // | / 0[xX][0-9a-fA-F]+ [iu]? / // decimal_float_literal: // | / 0 [fh] / // | / [1-9][0-9]* [fh] / // | / [0-9]* \.[0-9]+ ([eE][+-]?[0-9]+)? [fh]? / // | / [0-9]+ \.[0-9]* ([eE][+-]?[0-9]+)? [fh]? / // | / [0-9]+ [eE][+-]?[0-9]+ [fh]? / // hex_float_literal: // | / 0[xX][0-9a-fA-F]* \.[0-9a-fA-F]+ ([pP][+-]?[0-9]+ [fh]?)? / // | / 0[xX][0-9a-fA-F]+ \.[0-9a-fA-F]* ([pP][+-]?[0-9]+ [fh]?)? / // | / 0[xX][0-9a-fA-F]+ [pP][+-]?[0-9]+ [fh]? / // You could visualize the regex below via https://debuggex.com to get a rough idea what `parse` is doing // (?:0[xX](?:([0-9a-fA-F]+\.[0-9a-fA-F]*|[0-9a-fA-F]*\.[0-9a-fA-F]+)(?:([pP][+-]?[0-9]+)([fh]?))?|([0-9a-fA-F]+)([pP][+-]?[0-9]+)([fh]?)|([0-9a-fA-F]+)([iu]?))|((?:[0-9]+[eE][+-]?[0-9]+|(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?))([fh]?)|((?:[0-9]|[1-9][0-9]+))([iufh]?)) // Leading signs are handled as unary operators. fn parse(input: &str) -> (Result, &str) { /// returns `true` and consumes `X` bytes from the given byte buffer /// if the given `X` nr of patterns are found at the start of the buffer macro_rules! consume { ($bytes:ident, $($pattern:pat),*) => { match $bytes { &[$($pattern),*, ref rest @ ..] => { $bytes = rest; true }, _ => false, } }; } /// consumes one byte from the given byte buffer /// if one of the given patterns are found at the start of the buffer /// returning the corresponding expr for the matched pattern macro_rules! consume_map { ($bytes:ident, [$( $($pattern:pat_param),* => $to:expr),* $(,)?]) => { match $bytes { $( &[ $($pattern),*, ref rest @ ..] => { $bytes = rest; Some($to) }, )* _ => None, } }; } /// consumes all consecutive bytes matched by the `0-9` pattern from the given byte buffer /// returning the number of consumed bytes macro_rules! consume_dec_digits { ($bytes:ident) => {{ let start_len = $bytes.len(); while let &[b'0'..=b'9', ref rest @ ..] = $bytes { $bytes = rest; } start_len - $bytes.len() }}; } /// consumes all consecutive bytes matched by the `0-9 | a-f | A-F` pattern from the given byte buffer /// returning the number of consumed bytes macro_rules! consume_hex_digits { ($bytes:ident) => {{ let start_len = $bytes.len(); while let &[b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F', ref rest @ ..] = $bytes { $bytes = rest; } start_len - $bytes.len() }}; } macro_rules! consume_float_suffix { ($bytes:ident) => { consume_map!($bytes, [ b'h' => FloatKind::F16, b'f' => FloatKind::F32, b'l', b'f' => FloatKind::F64, ]) }; } /// maps the given `&[u8]` (tail of the initial `input: &str`) to a `&str` macro_rules! rest_to_str { ($bytes:ident) => { &input[input.len() - $bytes.len()..] }; } struct ExtractSubStr<'a>(&'a str); impl<'a> ExtractSubStr<'a> { /// given an `input` and a `start` (tail of the `input`) /// creates a new [`ExtractSubStr`](`Self`) fn start(input: &'a str, start: &'a [u8]) -> Self { let start = input.len() - start.len(); Self(&input[start..]) } /// given an `end` (tail of the initial `input`) /// returns a substring of `input` fn end(&self, end: &'a [u8]) -> &'a str { let end = self.0.len() - end.len(); &self.0[..end] } } let mut bytes = input.as_bytes(); let general_extract = ExtractSubStr::start(input, bytes); if consume!(bytes, b'0', b'x' | b'X') { let digits_extract = ExtractSubStr::start(input, bytes); let consumed = consume_hex_digits!(bytes); if consume!(bytes, b'.') { let consumed_after_period = consume_hex_digits!(bytes); if consumed + consumed_after_period == 0 { return (Err(NumberError::Invalid), rest_to_str!(bytes)); } let significand = general_extract.end(bytes); if consume!(bytes, b'p' | b'P') { consume!(bytes, b'+' | b'-'); let consumed = consume_dec_digits!(bytes); if consumed == 0 { return (Err(NumberError::Invalid), rest_to_str!(bytes)); } let number = general_extract.end(bytes); let kind = consume_float_suffix!(bytes); (parse_hex_float(number, kind), rest_to_str!(bytes)) } else { ( parse_hex_float_missing_exponent(significand, None), rest_to_str!(bytes), ) } } else { if consumed == 0 { return (Err(NumberError::Invalid), rest_to_str!(bytes)); } let significand = general_extract.end(bytes); let digits = digits_extract.end(bytes); let exp_extract = ExtractSubStr::start(input, bytes); if consume!(bytes, b'p' | b'P') { consume!(bytes, b'+' | b'-'); let consumed = consume_dec_digits!(bytes); if consumed == 0 { return (Err(NumberError::Invalid), rest_to_str!(bytes)); } let exponent = exp_extract.end(bytes); let kind = consume_float_suffix!(bytes); ( parse_hex_float_missing_period(significand, exponent, kind), rest_to_str!(bytes), ) } else { let kind = consume_map!(bytes, [ b'i' => IntKind::I32, b'u' => IntKind::U32, b'l', b'i' => IntKind::I64, b'l', b'u' => IntKind::U64, ]); (parse_hex_int(digits, kind), rest_to_str!(bytes)) } } } else { let is_first_zero = bytes.first() == Some(&b'0'); let consumed = consume_dec_digits!(bytes); if consume!(bytes, b'.') { let consumed_after_period = consume_dec_digits!(bytes); if consumed + consumed_after_period == 0 { return (Err(NumberError::Invalid), rest_to_str!(bytes)); } if consume!(bytes, b'e' | b'E') { consume!(bytes, b'+' | b'-'); let consumed = consume_dec_digits!(bytes); if consumed == 0 { return (Err(NumberError::Invalid), rest_to_str!(bytes)); } } let number = general_extract.end(bytes); let kind = consume_float_suffix!(bytes); (parse_dec_float(number, kind), rest_to_str!(bytes)) } else { if consumed == 0 { return (Err(NumberError::Invalid), rest_to_str!(bytes)); } if consume!(bytes, b'e' | b'E') { consume!(bytes, b'+' | b'-'); let consumed = consume_dec_digits!(bytes); if consumed == 0 { return (Err(NumberError::Invalid), rest_to_str!(bytes)); } let number = general_extract.end(bytes); let kind = consume_float_suffix!(bytes); (parse_dec_float(number, kind), rest_to_str!(bytes)) } else { // make sure the multi-digit numbers don't start with zero if consumed > 1 && is_first_zero { return (Err(NumberError::Invalid), rest_to_str!(bytes)); } let digits = general_extract.end(bytes); let kind = consume_map!(bytes, [ b'i' => Kind::Int(IntKind::I32), b'u' => Kind::Int(IntKind::U32), b'l', b'i' => Kind::Int(IntKind::I64), b'l', b'u' => Kind::Int(IntKind::U64), b'h' => Kind::Float(FloatKind::F16), b'f' => Kind::Float(FloatKind::F32), b'l', b'f' => Kind::Float(FloatKind::F64), ]); (parse_dec(digits, kind), rest_to_str!(bytes)) } } } } fn parse_hex_float_missing_exponent( // format: 0[xX] ( [0-9a-fA-F]+\.[0-9a-fA-F]* | [0-9a-fA-F]*\.[0-9a-fA-F]+ ) significand: &str, kind: Option, ) -> Result { let hexf_input = format!("{}{}", significand, "p0"); parse_hex_float(&hexf_input, kind) } fn parse_hex_float_missing_period( // format: 0[xX] [0-9a-fA-F]+ significand: &str, // format: [pP][+-]?[0-9]+ exponent: &str, kind: Option, ) -> Result { let hexf_input = format!("{significand}.{exponent}"); parse_hex_float(&hexf_input, kind) } fn parse_hex_int( // format: [0-9a-fA-F]+ digits: &str, kind: Option, ) -> Result { parse_int(digits, kind, 16) } fn parse_dec( // format: ( [0-9] | [1-9][0-9]+ ) digits: &str, kind: Option, ) -> Result { match kind { None => parse_int(digits, None, 10), Some(Kind::Int(kind)) => parse_int(digits, Some(kind), 10), Some(Kind::Float(kind)) => parse_dec_float(digits, Some(kind)), } } // Float parsing notes // The following chapters of IEEE 754-2019 are relevant: // // 7.4 Overflow (largest finite number is exceeded by what would have been // the rounded floating-point result were the exponent range unbounded) // // 7.5 Underflow (tiny non-zero result is detected; // for decimal formats tininess is detected before rounding when a non-zero result // computed as though both the exponent range and the precision were unbounded // would lie strictly between 2^−126) // // 7.6 Inexact (rounded result differs from what would have been computed // were both exponent range and precision unbounded) // The WGSL spec requires us to error: // on overflow for decimal floating point literals // on overflow and inexact for hexadecimal floating point literals // (underflow is not mentioned) // hexf_parse errors on overflow, underflow, inexact // rust std lib float from str handles overflow, underflow, inexact transparently (rounds and will not error) // Therefore we only check for overflow manually for decimal floating point literals // input format: 0[xX] ( [0-9a-fA-F]+\.[0-9a-fA-F]* | [0-9a-fA-F]*\.[0-9a-fA-F]+ ) [pP][+-]?[0-9]+ fn parse_hex_float(input: &str, kind: Option) -> Result { match kind { None => match hexf_parse::parse_hexf64(input, false) { Ok(num) => Ok(Number::AbstractFloat(num)), // can only be ParseHexfErrorKind::Inexact but we can't check since it's private _ => Err(NumberError::NotRepresentable), }, // TODO: f16 is not supported by hexf_parse Some(FloatKind::F16) => Err(NumberError::NotRepresentable), Some(FloatKind::F32) => match hexf_parse::parse_hexf32(input, false) { Ok(num) => Ok(Number::F32(num)), // can only be ParseHexfErrorKind::Inexact but we can't check since it's private _ => Err(NumberError::NotRepresentable), }, Some(FloatKind::F64) => match hexf_parse::parse_hexf64(input, false) { Ok(num) => Ok(Number::F64(num)), // can only be ParseHexfErrorKind::Inexact but we can't check since it's private _ => Err(NumberError::NotRepresentable), }, } } // input format: ( [0-9]+\.[0-9]* | [0-9]*\.[0-9]+ ) ([eE][+-]?[0-9]+)? // | [0-9]+ [eE][+-]?[0-9]+ fn parse_dec_float(input: &str, kind: Option) -> Result { match kind { None => { let num = input.parse::().unwrap(); // will never fail num.is_finite() .then_some(Number::AbstractFloat(num)) .ok_or(NumberError::NotRepresentable) } Some(FloatKind::F32) => { let num = input.parse::().unwrap(); // will never fail num.is_finite() .then_some(Number::F32(num)) .ok_or(NumberError::NotRepresentable) } Some(FloatKind::F64) => { let num = input.parse::().unwrap(); // will never fail num.is_finite() .then_some(Number::F64(num)) .ok_or(NumberError::NotRepresentable) } Some(FloatKind::F16) => { let num = input.parse::().unwrap(); // will never fail num.is_finite() .then_some(Number::F16(num)) .ok_or(NumberError::NotRepresentable) } } } fn parse_int(input: &str, kind: Option, radix: u32) -> Result { fn map_err(e: core::num::ParseIntError) -> NumberError { match *e.kind() { core::num::IntErrorKind::PosOverflow | core::num::IntErrorKind::NegOverflow => { NumberError::NotRepresentable } _ => unreachable!(), } } match kind { None => match i64::from_str_radix(input, radix) { Ok(num) => Ok(Number::AbstractInt(num)), Err(e) => Err(map_err(e)), }, Some(IntKind::I32) => match i32::from_str_radix(input, radix) { Ok(num) => Ok(Number::I32(num)), Err(e) => Err(map_err(e)), }, Some(IntKind::U32) => match u32::from_str_radix(input, radix) { Ok(num) => Ok(Number::U32(num)), Err(e) => Err(map_err(e)), }, Some(IntKind::I64) => match i64::from_str_radix(input, radix) { Ok(num) => Ok(Number::I64(num)), Err(e) => Err(map_err(e)), }, Some(IntKind::U64) => match u64::from_str_radix(input, radix) { Ok(num) => Ok(Number::U64(num)), Err(e) => Err(map_err(e)), }, } } ================================================ FILE: naga/src/front/wgsl/tests.rs ================================================ use alloc::format; use super::parse_str; #[test] fn parse_comment() { parse_str( "// //// ///////////////////////////////////////////////////////// asda //////////////////// dad ////////// / ///////////////////////////////////////////////////////////////////////////////////////////////////// // ", ) .unwrap(); } #[test] fn parse_types() { parse_str("const a : i32 = 2;").unwrap(); parse_str("const a : u64 = 2lu;").unwrap(); assert!(parse_str("const a : x32 = 2;").is_err()); parse_str("var t: texture_2d;").unwrap(); parse_str("var t: texture_cube_array;").unwrap(); parse_str("var t: texture_multisampled_2d;").unwrap(); parse_str("var t: texture_storage_1d;").unwrap(); parse_str("var t: texture_storage_3d;").unwrap(); } #[test] fn parse_type_inference() { parse_str( " fn foo() { let a = 2u; let b: u32 = a; var x = 3.; var y = vec2(1, 2); }", ) .unwrap(); assert!(parse_str( " fn foo() { let c : i32 = 2.0; }", ) .is_err()); } #[test] fn parse_type_cast() { parse_str( " const a : i32 = 2; fn main() { var x: f32 = f32(a); x = f32(i32(a + 1) / 2); } ", ) .unwrap(); parse_str( " fn main() { let x: vec2 = vec2(1.0, 2.0); let y: vec2 = vec2(x); } ", ) .unwrap(); parse_str( " fn main() { let x: vec2 = vec2(0.0); } ", ) .unwrap(); assert!(parse_str( " fn main() { let x: vec2 = vec2(0.0, 0.0); } ", ) .is_err()); } #[test] fn parse_type_coercion() { parse_str( " fn foo(bar: f32) {} fn main() { foo(0); } ", ) .unwrap(); assert!(parse_str( " fn foo(bar: i32) {} fn main() { foo(0.0); } ", ) .is_err()); } #[test] fn parse_struct() { parse_str( " struct Foo { x: i32 } struct Bar { @size(16) x: vec2, @align(16) y: f32, @size(32) @align(128) z: vec3, }; struct Empty {} var s: Foo; ", ) .unwrap(); } #[test] fn parse_standard_fun() { parse_str( " fn main() { var x: i32 = min(max(1, 2), 3); } ", ) .unwrap(); } #[test] fn parse_statement() { parse_str( " fn main() { ; {} {;} } ", ) .unwrap(); parse_str( " fn foo() {} fn bar() { foo(); } ", ) .unwrap(); } #[test] fn parse_if() { parse_str( " fn main() { if true { discard; } else {} if 0 != 1 {} if false { return; } else if true { return; } else {} } ", ) .unwrap(); } #[test] fn parse_parentheses_if() { parse_str( " fn main() { if (true) { discard; } else {} if (0 != 1) {} if (false) { return; } else if (true) { return; } else {} } ", ) .unwrap(); } #[test] fn parse_loop() { parse_str( " fn main() { var i: i32 = 0; loop { if i == 1 { break; } continuing { i = 1; } } loop { if i == 0 { continue; } break; } } ", ) .unwrap(); parse_str( " fn main() { var found: bool = false; var i: i32 = 0; while !found { if i == 10 { found = true; } i = i + 1; } } ", ) .unwrap(); parse_str( " fn main() { while true { break; } } ", ) .unwrap(); parse_str( " fn main() { var a: i32 = 0; for(var i: i32 = 0; i < 4; i = i + 1) { a = a + 2; } } ", ) .unwrap(); parse_str( " fn main() { for(;;) { break; } } ", ) .unwrap(); } #[test] fn parse_switch() { parse_str( " fn main() { var pos: f32; switch (3) { case 0, 1: { pos = 0.0; } case 2: { pos = 1.0; } default: { pos = 3.0; } } } ", ) .unwrap(); } #[test] fn parse_switch_optional_colon_in_case() { parse_str( " fn main() { var pos: f32; switch (3) { case 0, 1 { pos = 0.0; } case 2 { pos = 1.0; } default { pos = 3.0; } } } ", ) .unwrap(); } #[test] fn parse_switch_default_in_case() { parse_str( " fn main() { var pos: f32; switch (3) { case 0, 1: { pos = 0.0; } case 2: {} case default, 3: { pos = 3.0; } } } ", ) .unwrap(); } #[test] fn parse_parentheses_switch() { parse_str( " fn main() { var pos: i32; switch pos + 1 { default: { pos = 3; } } } ", ) .unwrap(); } #[test] fn parse_texture_load() { parse_str( " var t: texture_3d; fn foo() { let r: vec4 = textureLoad(t, vec3(0u, 1u, 2u), 1); } ", ) .unwrap(); parse_str( " var t: texture_2d_array; fn foo() { let r: vec4 = textureLoad(t, vec2(10, 20), 2, 3); } ", ) .unwrap(); parse_str( " var t: texture_storage_1d; fn foo() { let r: vec4 = textureLoad(t, 10); } ", ) .unwrap(); } #[test] fn parse_texture_store() { parse_str( " var t: texture_storage_2d; fn foo() { textureStore(t, vec2(10, 20), vec4(0.0, 1.0, 2.0, 3.0)); } ", ) .unwrap(); } #[test] fn parse_texture_query() { parse_str( " var t: texture_multisampled_2d; fn foo() { let dim = textureDimensions(t); let samples = textureNumSamples(t); } ", ) .unwrap(); parse_str( " var t: texture_2d_array; fn foo() { let dim = textureDimensions(t); let levels = textureNumLevels(t); let layers = textureNumLayers(t); } ", ) .unwrap(); } #[test] fn parse_postfix() { parse_str( "fn foo() { let x: f32 = vec4(1.0, 2.0, 3.0, 4.0).xyz.rgbr.aaaa.wz.g; let y: f32 = fract(vec2(0.5, x)).x; }", ) .unwrap(); let err = parse_str( "fn foo() { let v = mat4x4().x; }", ) .unwrap_err(); assert_eq!(err.message(), "invalid field accessor `x`"); } #[test] fn parse_expressions() { parse_str("fn foo() { let x: f32 = select(0.0, 1.0, true); let y: vec2 = select(vec2(1.0, 1.0), vec2(x, x), vec2((x < 0.5), (x > 0.5))); let z: bool = !(0.0 == 1.0); }").unwrap(); } #[test] fn parse_assignment_statements() { parse_str( " struct Foo { x: i32 }; fn foo() { var x: u32 = 0u; x++; x--; x = 1u; x += 1u; var v: vec2 = vec2(1.0, 1.0); v[0] += 1.0; (v)[0] += 1.0; var s: Foo = Foo(0); s.x -= 1; (s.x) -= 1; (s).x -= 1; _ = 5u; }", ) .unwrap(); let error = parse_str( "fn foo() { x|x++; }", ) .unwrap_err(); assert_eq!( error.message(), "expected assignment or increment/decrement, found \"|\"", ); } #[test] fn parse_local_var_address_space() { parse_str( " fn foo() { var a = true; var b: i32 = 5; var c = 10; }", ) .unwrap(); let error = parse_str( "fn foo() { var x: i32 = 5; }", ) .unwrap_err(); assert_eq!( error.message(), "invalid address space for local variable: `private`", ); let error = parse_str( "fn foo() { var x: i32 = 5; }", ) .unwrap_err(); assert_eq!( error.message(), "invalid address space for local variable: `storage`", ); } #[test] fn binary_expression_mixed_scalar_and_vector_operands() { for (operand, expect_splat) in [ ('<', false), ('>', false), ('&', false), ('|', false), ('+', true), ('-', true), ('*', false), ('/', true), ('%', true), ] { let module = parse_str(&format!( " @fragment fn main(@location(0) some_vec: vec3) -> @location(0) vec4 {{ if (all(1.0 {operand} some_vec)) {{ return vec4(0.0); }} return vec4(1.0); }} " )) .unwrap(); let expressions = &&module.entry_points[0].function.expressions; let found_expressions = expressions .iter() .filter(|&(_, e)| { if let crate::Expression::Binary { left, .. } = *e { matches!( (expect_splat, &expressions[left]), (false, &crate::Expression::Literal(crate::Literal::F32(..))) | (true, &crate::Expression::Splat { .. }) ) } else { false } }) .count(); assert_eq!( found_expressions, 1, "expected `{operand}` expression {} splat", if expect_splat { "with" } else { "without" } ); } let module = parse_str( "@fragment fn main(mat: mat3x3) { let vec = vec3(1.0, 1.0, 1.0); let result = mat / vec; }", ) .unwrap(); let expressions = &&module.entry_points[0].function.expressions; let found_splat = expressions.iter().any(|(_, e)| { if let crate::Expression::Binary { left, .. } = *e { matches!(&expressions[left], &crate::Expression::Splat { .. }) } else { false } }); assert!(!found_splat, "'mat / vec' should not be splatted"); } #[test] fn parse_pointers() { parse_str( "fn foo(a: ptr) -> f32 { return *a; } fn bar() { var x: f32 = 1.0; let px = &x; let py = foo(px); }", ) .unwrap(); } #[test] fn parse_struct_instantiation() { parse_str( " struct Foo { a: f32, b: vec3, } @fragment fn fs_main() { var foo: Foo = Foo(0.0, vec3(0.0, 1.0, 42.0)); } ", ) .unwrap(); } #[test] fn parse_array_length() { parse_str( " struct Foo { data: array } // this is used as both input and output for convenience @group(0) @binding(0) var foo: Foo; @group(0) @binding(1) var bar: array; fn baz() { var x: u32 = arrayLength(foo.data); var y: u32 = arrayLength(bar); } ", ) .unwrap(); } #[test] fn parse_storage_buffers() { parse_str( " @group(0) @binding(0) var foo: array; ", ) .unwrap(); parse_str( " @group(0) @binding(0) var foo: array; ", ) .unwrap(); parse_str( " @group(0) @binding(0) var foo: array; ", ) .unwrap(); parse_str( " @group(0) @binding(0) var foo: array; ", ) .unwrap(); } #[test] fn parse_alias() { parse_str( " alias Vec4 = vec4; ", ) .unwrap(); } #[test] fn shadowing_predeclared_types() { parse_str( " fn test(f32: vec2f) -> vec2f { return f32; } ", ) .unwrap(); parse_str( " fn test(vec2: vec2f) -> vec2f { return vec2; } ", ) .unwrap(); parse_str( " alias vec2f = vec2u; fn test(v: vec2f) -> vec2u { return v; } ", ) .unwrap(); parse_str( " struct vec2f { inner: vec2 }; fn test(v: vec2f) -> vec2 { return v.inner; } ", ) .unwrap(); } #[test] fn parse_texture_load_store_expecting_four_args() { for (func, texture) in [ ( "textureStore", "texture_storage_2d_array", ), ("textureLoad", "texture_2d_array"), ] { let error = parse_str(&format!( " @group(0) @binding(0) var tex_los_res: {texture}; @compute @workgroup_size(1) fn main(@builtin(global_invocation_id) id: vec3) {{ var color = vec4(1, 1, 1, 1); {func}(tex_los_res, id, color); }} " )) .unwrap_err(); assert_eq!( error.message(), "wrong number of arguments: expected 4, found 3" ); } } #[test] fn parse_repeated_attributes() { use crate::{ front::wgsl::{error::Error, Frontend}, Span, }; let template_vs = "@vertex fn vs() -> __REPLACE__ vec4 { return vec4(0.0); }"; let template_struct = "struct A { __REPLACE__ data: vec3 }"; let template_resource = "__REPLACE__ var tex_los_res: texture_2d_array;"; let template_stage = "__REPLACE__ fn vs() -> vec4 { return vec4(0.0); }"; for (attribute, template) in [ ("align(16)", template_struct), ("binding(0)", template_resource), ("builtin(position)", template_vs), ("compute", template_stage), ("fragment", template_stage), ("group(0)", template_resource), ("interpolate(flat)", template_vs), ("invariant", template_vs), ("location(0)", template_vs), ("size(16)", template_struct), ("vertex", template_stage), ("early_depth_test(less_equal)", template_resource), ("workgroup_size(1)", template_stage), ] { let shader = template.replace("__REPLACE__", &format!("@{attribute} @{attribute}")); let name_length = attribute.rfind('(').unwrap_or(attribute.len()) as u32; let span_start = shader.rfind(attribute).unwrap() as u32; let span_end = span_start + name_length; let expected_span = Span::new(span_start, span_end); let result = Frontend::new().inner(&shader); assert!(matches!( *result.unwrap_err(), Error::RepeatedAttribute(span) if span == expected_span )); } } #[test] fn parse_missing_workgroup_size() { use crate::{ front::wgsl::{error::Error, Frontend}, Span, }; let shader = "@compute fn vs() -> vec4 { return vec4(0.0); }"; let result = Frontend::new().inner(shader); assert!(matches!( *result.unwrap_err(), Error::MissingWorkgroupSize(span) if span == Span::new(1, 8) )); } mod diagnostic_filter { use crate::front::wgsl::assert_parse_err; #[test] fn intended_global_directive() { let shader = "@diagnostic(off, my.lint);"; assert_parse_err( shader, "\ error: `@diagnostic(…)` attribute(s) on semicolons are not supported ┌─ wgsl:1:1 │ 1 │ @diagnostic(off, my.lint); │ ^^^^^^^^^^^^^^^^^^^^^^^^^ │ = note: `@diagnostic(…)` attributes are only permitted on `fn`s, some statements, and `switch`/`loop` bodies. = note: If you meant to declare a diagnostic filter that applies to the entire module, move this line to the top of the file and remove the `@` symbol. " ); } mod parse_sites_not_yet_supported { use crate::front::wgsl::assert_parse_err; #[test] fn user_rules() { let shader = " fn myfunc() { if (true) @diagnostic(off, my.lint) { // ^^^^^^^^^^^^^^^^^^^^^^^^^ not yet supported, should report an error } } "; assert_parse_err(shader, "\ error: `@diagnostic(…)` attribute(s) not yet implemented ┌─ wgsl:3:15 │ 3 │ if (true) @diagnostic(off, my.lint) { │ ^^^^^^^^^^^^^^^^^^^^^^^^^ can't use this on compound statements (yet) │ = note: Let Naga maintainers know that you ran into this at , so they can prioritize it! "); } #[test] fn unknown_rules() { let shader = " fn myfunc() { if (true) @diagnostic(off, wat_is_this) { // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ should emit a warning } } "; assert_parse_err(shader, "\ error: `@diagnostic(…)` attribute(s) not yet implemented ┌─ wgsl:3:12 │ 3 │ if (true) @diagnostic(off, wat_is_this) { │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't use this on compound statements (yet) │ = note: Let Naga maintainers know that you ran into this at , so they can prioritize it! "); } } mod directive_conflict { use crate::front::wgsl::assert_parse_err; #[test] fn user_rules() { let shader = " diagnostic(off, my.lint); diagnostic(warning, my.lint); "; assert_parse_err(shader, "\ error: found conflicting `diagnostic(…)` rule(s) ┌─ wgsl:2:1 │ 2 │ diagnostic(off, my.lint); │ ^^^^^^^^^^^^^^^^^^^^^^^^ first rule 3 │ diagnostic(warning, my.lint); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ second rule │ = note: Multiple `diagnostic(…)` rules with the same rule name conflict unless they are directives and the severity is the same. = note: You should delete the rule you don't want. "); } #[test] fn unknown_rules() { let shader = " diagnostic(off, wat_is_this); diagnostic(warning, wat_is_this); "; assert_parse_err(shader, "\ error: found conflicting `diagnostic(…)` rule(s) ┌─ wgsl:2:1 │ 2 │ diagnostic(off, wat_is_this); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ first rule 3 │ diagnostic(warning, wat_is_this); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ second rule │ = note: Multiple `diagnostic(…)` rules with the same rule name conflict unless they are directives and the severity is the same. = note: You should delete the rule you don't want. "); } } mod attribute_conflict { use crate::front::wgsl::assert_parse_err; #[test] fn user_rules() { let shader = " diagnostic(off, my.lint); diagnostic(warning, my.lint); "; assert_parse_err(shader, "\ error: found conflicting `diagnostic(…)` rule(s) ┌─ wgsl:2:1 │ 2 │ diagnostic(off, my.lint); │ ^^^^^^^^^^^^^^^^^^^^^^^^ first rule 3 │ diagnostic(warning, my.lint); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ second rule │ = note: Multiple `diagnostic(…)` rules with the same rule name conflict unless they are directives and the severity is the same. = note: You should delete the rule you don't want. "); } #[test] fn unknown_rules() { let shader = " diagnostic(off, wat_is_this); diagnostic(warning, wat_is_this); "; assert_parse_err(shader, "\ error: found conflicting `diagnostic(…)` rule(s) ┌─ wgsl:2:1 │ 2 │ diagnostic(off, wat_is_this); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ first rule 3 │ diagnostic(warning, wat_is_this); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ second rule │ = note: Multiple `diagnostic(…)` rules with the same rule name conflict unless they are directives and the severity is the same. = note: You should delete the rule you don't want. "); } } } mod template { use crate::front::wgsl::assert_parse_err; #[test] fn missing_template_end() { assert_parse_err( " fn storage() {} var s: u32; ", "\ error: identifier `storage` resolves to a declaration ┌─ wgsl:3:5 │ 3 │ var s: u32; │ ^^^^^^^ needs to resolve to a predeclared enumerant ", ); } #[test] fn unexpected_expr_as_enumerant() { assert_parse_err( " var<1 + 1> s: u32; ", "\ error: unexpected expression ┌─ wgsl:2:5 │ 2 │ var<1 + 1> s: u32; │ ^^^^^ needs to be an identifier resolving to a predeclared enumerant ", ); } #[test] fn unused_exprs_for_template() { assert_parse_err( " var s: u32; ", "\ error: unused expressions for template ┌─ wgsl:2:26 │ 2 │ var s: u32; │ ^^^^^^ ^^^^^^ unused │ │\x20\x20\x20\x20\x20\x20\x20\x20 │ unused ", ); } #[test] fn unused_template_list_for_fn() { assert_parse_err( " fn inner_test() {} fn test() { inner_test(); } ", "\ error: unused expressions for template ┌─ wgsl:4:16 │ 4 │ inner_test(); │ ^^^^^^^^^^^^^^^^^^^ unused ", ); } #[test] fn unused_template_list_for_struct() { assert_parse_err( " struct test_struct {} fn test() { _ = test_struct(); } ", "\ error: unused expressions for template ┌─ wgsl:4:21 │ 4 │ _ = test_struct(); │ ^^^^^^^^^^^^^^^^^^^ unused ", ); } #[test] fn unused_template_list_for_alias() { assert_parse_err( " alias test_alias = f32; fn test() { _ = test_alias(); } ", "\ error: unused expressions for template ┌─ wgsl:4:20 │ 4 │ _ = test_alias(); │ ^^^^^^^^^^^^^^^^^^^ unused ", ); } #[test] fn unexpected_template() { assert_parse_err( " fn vertex() -> vec4 { return vec4; } ", "\ error: unexpected template ┌─ wgsl:3:12 │ 3 │ return vec4; │ ^^^^^^^^^ expected identifier ", ); } #[test] fn expected_template_arg() { assert_parse_err( " fn test() { bitcast(8); } ", "\ error: `bitcast` needs a template argument specified: `T`, a type ┌─ wgsl:3:5 │ 3 │ bitcast(8); │ ^^^^^^^ is missing a template argument ", ); } } ================================================ FILE: naga/src/ir/block.rs ================================================ use alloc::vec::Vec; use core::ops::{Deref, DerefMut, RangeBounds}; use crate::{Span, Statement}; /// A code block is a vector of statements, with maybe a vector of spans. #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "serialize", serde(transparent))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Block { body: Vec, #[cfg_attr(feature = "serialize", serde(skip))] span_info: Vec, } impl Block { pub const fn new() -> Self { Self { body: Vec::new(), span_info: Vec::new(), } } pub fn from_vec(body: Vec) -> Self { let span_info = core::iter::repeat_n(Span::default(), body.len()).collect(); Self { body, span_info } } pub fn with_capacity(capacity: usize) -> Self { Self { body: Vec::with_capacity(capacity), span_info: Vec::with_capacity(capacity), } } #[allow(unused_variables)] pub fn push(&mut self, end: Statement, span: Span) { self.body.push(end); self.span_info.push(span); } pub fn extend(&mut self, item: Option<(Statement, Span)>) { if let Some((end, span)) = item { self.push(end, span) } } pub fn extend_block(&mut self, other: Self) { self.span_info.extend(other.span_info); self.body.extend(other.body); } pub fn append(&mut self, other: &mut Self) { self.span_info.append(&mut other.span_info); self.body.append(&mut other.body); } pub fn cull + Clone>(&mut self, range: R) { self.span_info.drain(range.clone()); self.body.drain(range); } pub fn splice + Clone>(&mut self, range: R, other: Self) { self.span_info.splice(range.clone(), other.span_info); self.body.splice(range, other.body); } pub fn span_into_iter(self) -> impl Iterator { let Block { body, span_info } = self; body.into_iter().zip(span_info) } pub fn span_iter(&self) -> impl Iterator { let span_iter = self.span_info.iter(); self.body.iter().zip(span_iter) } pub fn span_iter_mut(&mut self) -> impl Iterator)> { let span_iter = self.span_info.iter_mut().map(Some); self.body.iter_mut().zip(span_iter) } pub const fn is_empty(&self) -> bool { self.body.is_empty() } pub const fn len(&self) -> usize { self.body.len() } } impl Deref for Block { type Target = [Statement]; fn deref(&self) -> &[Statement] { &self.body } } impl DerefMut for Block { fn deref_mut(&mut self) -> &mut [Statement] { &mut self.body } } impl<'a> IntoIterator for &'a Block { type Item = &'a Statement; type IntoIter = core::slice::Iter<'a, Statement>; fn into_iter(self) -> core::slice::Iter<'a, Statement> { self.iter() } } #[cfg(feature = "deserialize")] impl<'de> serde::Deserialize<'de> for Block { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { Ok(Self::from_vec(Vec::deserialize(deserializer)?)) } } impl From> for Block { fn from(body: Vec) -> Self { Self::from_vec(body) } } ================================================ FILE: naga/src/ir/mod.rs ================================================ /*! The Intermediate Representation shared by all frontends and backends. The central structure of the IR, and the crate, is [`Module`]. A `Module` contains: - [`Function`]s, which have arguments, a return type, local variables, and a body, - [`EntryPoint`]s, which are specialized functions that can serve as the entry point for pipeline stages like vertex shading or fragment shading, - [`Constant`]s and [`GlobalVariable`]s used by `EntryPoint`s and `Function`s, and - [`Type`]s used by the above. The body of an `EntryPoint` or `Function` is represented using two types: - An [`Expression`] produces a value, but has no side effects or control flow. `Expressions` include variable references, unary and binary operators, and so on. - A [`Statement`] can have side effects and structured control flow. `Statement`s do not produce a value, other than by storing one in some designated place. `Statements` include blocks, conditionals, and loops, but also operations that have side effects, like stores and function calls. `Statement`s form a tree, with pointers into the DAG of `Expression`s. Restricting side effects to statements simplifies analysis and code generation. A Naga backend can generate code to evaluate an `Expression` however and whenever it pleases, as long as it is certain to observe the side effects of all previously executed `Statement`s. Many `Statement` variants use the [`Block`] type, which is `Vec`, with optional span info, representing a series of statements executed in order. The body of an `EntryPoint`s or `Function` is a `Block`, and `Statement` has a [`Block`][Statement::Block] variant. ## Function Calls Naga's representation of function calls is unusual. Most languages treat function calls as expressions, but because calls may have side effects, Naga represents them as a kind of statement, [`Statement::Call`]. If the function returns a value, a call statement designates a particular [`Expression::CallResult`] expression to represent its return value, for use by subsequent statements and expressions. ## `Expression` evaluation time It is essential to know when an [`Expression`] should be evaluated, because its value may depend on previous [`Statement`]s' effects. But whereas the order of execution for a tree of `Statement`s is apparent from its structure, it is not so clear for `Expressions`, since an expression may be referred to by any number of `Statement`s and other `Expression`s. Naga's rules for when `Expression`s are evaluated are as follows: - [`Literal`], [`Constant`], and [`ZeroValue`] expressions are considered to be implicitly evaluated before execution begins. - [`FunctionArgument`] and [`LocalVariable`] expressions are considered implicitly evaluated upon entry to the function to which they belong. Function arguments cannot be assigned to, and `LocalVariable` expressions produce a *pointer to* the variable's value (for use with [`Load`] and [`Store`]). Neither varies while the function executes, so it suffices to consider these expressions evaluated once on entry. - Similarly, [`GlobalVariable`] expressions are considered implicitly evaluated before execution begins, since their value does not change while code executes, for one of two reasons: - Most `GlobalVariable` expressions produce a pointer to the variable's value, for use with [`Load`] and [`Store`], as `LocalVariable` expressions do. Although the variable's value may change, its address does not. - A `GlobalVariable` expression referring to a global in the [`AddressSpace::Handle`] address space produces the value directly, not a pointer. Such global variables hold opaque types like shaders or images, and cannot be assigned to. - A [`CallResult`] expression that is the `result` of a [`Statement::Call`], representing the call's return value, is evaluated when the `Call` statement is executed. - Similarly, an [`AtomicResult`] expression that is the `result` of an [`Atomic`] statement, representing the result of the atomic operation, is evaluated when the `Atomic` statement is executed. - A [`RayQueryProceedResult`] expression, which is a boolean indicating if the ray query is finished, is evaluated when the [`RayQuery`] statement whose [`Proceed::result`] points to it is executed. - All other expressions are evaluated when the (unique) [`Statement::Emit`] statement that covers them is executed. Now, strictly speaking, not all `Expression` variants actually care when they're evaluated. For example, you can evaluate a [`BinaryOperator::Add`] expression any time you like, as long as you give it the right operands. It's really only a very small set of expressions that are affected by timing: - [`Load`], [`ImageSample`], and [`ImageLoad`] expressions are influenced by stores to the variables or images they access, and must execute at the proper time relative to them. - [`Derivative`] expressions are sensitive to control flow uniformity: they must not be moved out of an area of uniform control flow into a non-uniform area. - More generally, any expression that's used by more than one other expression or statement should probably be evaluated only once, and then stored in a variable to be cited at each point of use. Naga tries to help back ends handle all these cases correctly in a somewhat circuitous way. The [`ModuleInfo`] structure returned by [`Validator::validate`] provides a reference count for each expression in each function in the module. Naturally, any expression with a reference count of two or more deserves to be evaluated and stored in a temporary variable at the point that the `Emit` statement covering it is executed. But if we selectively lower the reference count threshold to _one_ for the sensitive expression types listed above, so that we _always_ generate a temporary variable and save their value, then the same code that manages multiply referenced expressions will take care of introducing temporaries for time-sensitive expressions as well. The `Expression::bake_ref_count` method (private to the back ends) is meant to help with this. ## `Expression` scope Each `Expression` has a *scope*, which is the region of the function within which it can be used by `Statement`s and other `Expression`s. It is a validation error to use an `Expression` outside its scope. An expression's scope is defined as follows: - The scope of a [`Constant`], [`GlobalVariable`], [`FunctionArgument`] or [`LocalVariable`] expression covers the entire `Function` in which it occurs. - The scope of an expression evaluated by an [`Emit`] statement covers the subsequent expressions in that `Emit`, the subsequent statements in the `Block` to which that `Emit` belongs (if any) and their sub-statements (if any). - The `result` expression of a [`Call`] or [`Atomic`] statement has a scope covering the subsequent statements in the `Block` in which the statement occurs (if any) and their sub-statements (if any). For example, this implies that an expression evaluated by some statement in a nested `Block` is not available in the `Block`'s parents. Such a value would need to be stored in a local variable to be carried upwards in the statement tree. ## Constant expressions A Naga *constant expression* is one of the following [`Expression`] variants, whose operands (if any) are also constant expressions: - [`Literal`] - [`Constant`], for [`Constant`]s - [`ZeroValue`], for fixed-size types - [`Compose`] - [`Access`] - [`AccessIndex`] - [`Splat`] - [`Swizzle`] - [`Unary`] - [`Binary`] - [`Select`] - [`Relational`] - [`Math`] - [`As`] A constant expression can be evaluated at module translation time. ## Override expressions A Naga *override expression* is the same as a [constant expression], except that it is also allowed to reference other [`Override`]s. An override expression can be evaluated at pipeline creation time. [`AtomicResult`]: Expression::AtomicResult [`RayQueryProceedResult`]: Expression::RayQueryProceedResult [`CallResult`]: Expression::CallResult [`Constant`]: Expression::Constant [`ZeroValue`]: Expression::ZeroValue [`Literal`]: Expression::Literal [`Derivative`]: Expression::Derivative [`FunctionArgument`]: Expression::FunctionArgument [`GlobalVariable`]: Expression::GlobalVariable [`ImageLoad`]: Expression::ImageLoad [`ImageSample`]: Expression::ImageSample [`Load`]: Expression::Load [`LocalVariable`]: Expression::LocalVariable [`Atomic`]: Statement::Atomic [`Call`]: Statement::Call [`Emit`]: Statement::Emit [`Store`]: Statement::Store [`RayQuery`]: Statement::RayQuery [`Proceed::result`]: RayQueryFunction::Proceed::result [`Validator::validate`]: crate::valid::Validator::validate [`ModuleInfo`]: crate::valid::ModuleInfo [`Literal`]: Expression::Literal [`ZeroValue`]: Expression::ZeroValue [`Compose`]: Expression::Compose [`Access`]: Expression::Access [`AccessIndex`]: Expression::AccessIndex [`Splat`]: Expression::Splat [`Swizzle`]: Expression::Swizzle [`Unary`]: Expression::Unary [`Binary`]: Expression::Binary [`Select`]: Expression::Select [`Relational`]: Expression::Relational [`Math`]: Expression::Math [`As`]: Expression::As [constant expression]: #constant-expressions */ mod block; use alloc::{boxed::Box, string::String, vec::Vec}; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; use half::f16; #[cfg(feature = "deserialize")] use serde::Deserialize; #[cfg(feature = "serialize")] use serde::Serialize; use crate::arena::{Arena, Handle, Range, UniqueArena}; use crate::diagnostic_filter::DiagnosticFilterNode; use crate::{FastIndexMap, NamedExpressions}; pub use block::Block; /// Explicitly allows early depth/stencil tests. /// /// Normally, depth/stencil tests are performed after fragment shading. However, as an optimization, /// most drivers will move the depth/stencil tests before fragment shading if this does not /// have any observable consequences. This optimization is disabled under the following /// circumstances: /// - `discard` is called in the fragment shader. /// - The fragment shader writes to the depth buffer. /// - The fragment shader writes to any storage bindings. /// /// When `EarlyDepthTest` is set, it is allowed to perform an early depth/stencil test even if the /// above conditions are not met. When [`EarlyDepthTest::Force`] is used, depth/stencil tests /// **must** be performed before fragment shading. /// /// To force early depth/stencil tests in a shader: /// - GLSL: `layout(early_fragment_tests) in;` /// - HLSL: `Attribute earlydepthstencil` /// - SPIR-V: `ExecutionMode EarlyFragmentTests` /// - WGSL: `@early_depth_test(force)` /// /// This may also be enabled in a shader by specifying a [`ConservativeDepth`]. /// /// For more, see: /// - /// - /// - #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum EarlyDepthTest { /// Requires depth/stencil tests to be performed before fragment shading. /// /// This will disable depth/stencil tests after fragment shading, so discarding the fragment /// or overwriting the fragment depth will have no effect. Force, /// Allows an additional depth/stencil test to be performed before fragment shading. /// /// It is up to the driver to decide whether early tests are performed. Unlike `Force`, this /// does not disable depth/stencil tests after fragment shading. Allow { /// Specifies restrictions on how the depth value can be modified within the fragment /// shader. /// /// This may be taken into account when deciding whether to perform early tests. conservative: ConservativeDepth, }, } /// Enables adjusting depth without disabling early Z. /// /// To use in a shader: /// - GLSL: `layout (depth_) out float gl_FragDepth;` /// - `depth_any` option behaves as if the layout qualifier was not present. /// - HLSL: `SV_DepthGreaterEqual`/`SV_DepthLessEqual`/`SV_Depth` /// - SPIR-V: `ExecutionMode Depth` /// - WGSL: `@early_depth_test(greater_equal/less_equal/unchanged)` /// /// For more, see: /// - /// - /// - #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum ConservativeDepth { /// Shader may rewrite depth only with a value greater than calculated. GreaterEqual, /// Shader may rewrite depth smaller than one that would have been written without the modification. LessEqual, /// Shader may not rewrite depth value. Unchanged, } /// Stage of the programmable pipeline. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum ShaderStage { /// A vertex shader, in a render pipeline. Vertex, /// A task shader, in a mesh render pipeline. Task, /// A mesh shader, in a mesh render pipeline. Mesh, /// A fragment shader, in a render pipeline. Fragment, /// Compute pipeline shader. Compute, /// A ray generation shader, in a ray tracing pipeline. RayGeneration, /// A miss shader, in a ray tracing pipeline. Miss, /// A any hit shader, in a ray tracing pipeline. AnyHit, /// A closest hit shader, in a ray tracing pipeline. ClosestHit, } /// Addressing space of variables. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum AddressSpace { /// Function locals. Function, /// Private data, per invocation, mutable. Private, /// Workgroup shared data, mutable. WorkGroup, /// Uniform buffer data. Uniform, /// Storage buffer data, potentially mutable. Storage { access: StorageAccess }, /// Opaque handles, such as samplers and images. Handle, /// Immediate data. /// /// A [`Module`] may contain at most one [`GlobalVariable`] in /// this address space. Its contents are provided not by a buffer /// but by `SetImmediates` pass commands, allowing the CPU to /// establish different values for each draw/dispatch. /// /// `Immediate` variables may not contain `f16` values, even if /// the [`SHADER_FLOAT16`] capability is enabled. /// /// Backends generally place tight limits on the size of /// `Immediate` variables. /// /// [`SHADER_FLOAT16`]: crate::valid::Capabilities::SHADER_FLOAT16 Immediate, /// Task shader to mesh shader payload TaskPayload, /// Ray tracing payload, for inputting in TraceRays RayPayload, /// Ray tracing payload, for entrypoints invoked by a TraceRays call /// /// Each entrypoint may reference only one variable in this scope, as /// only one may be passed as a payload. IncomingRayPayload, } /// Built-in inputs and outputs. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum BuiltIn { // This must be at the top so that it gets sorted to the top. PrimitiveIndex is considered a non SV // by FXC so it must appear before any other SVs. /// Read in fragment shaders, written in mesh shaders, read in any and closest hit shaders. PrimitiveIndex, /// Written in vertex/mesh shaders, read in fragment shaders Position { invariant: bool }, /// Read in task, mesh, vertex, and fragment shaders ViewIndex, /// Read in vertex shaders BaseInstance, /// Read in vertex shaders BaseVertex, /// Written in vertex & mesh shaders ClipDistances, /// Written in vertex & mesh shaders CullDistance, /// Read in vertex, any- and closest-hit shaders InstanceIndex, /// Written in vertex & mesh shaders PointSize, /// Read in vertex shaders VertexIndex, /// Read in vertex & task shaders, or mesh shaders in pipelines without task shaders DrawIndex, /// Written in fragment shaders FragDepth, /// Read in fragment shaders PointCoord, /// Read in fragment shaders FrontFacing, /// Read in fragment shaders Barycentric { perspective: bool }, /// Read in fragment shaders SampleIndex, /// Read or written in fragment shaders SampleMask, /// Read in compute, task, and mesh shaders GlobalInvocationId, /// Read in compute, task, and mesh shaders LocalInvocationId, /// Read in compute, task, and mesh shaders LocalInvocationIndex, /// Read in compute, task, and mesh shaders WorkGroupId, /// Read in compute, task, and mesh shaders WorkGroupSize, /// Read in compute, task, and mesh shaders NumWorkGroups, /// Read in compute, task, and mesh shaders NumSubgroups, /// Read in compute, task, and mesh shaders SubgroupId, /// Read in compute, fragment, task, and mesh shaders SubgroupSize, /// Read in compute, fragment, task, and mesh shaders SubgroupInvocationId, /// Written in task shaders MeshTaskSize, /// Written in mesh shaders CullPrimitive, /// Written in mesh shaders PointIndex, /// Written in mesh shaders LineIndices, /// Written in mesh shaders TriangleIndices, /// Written to a workgroup variable in mesh shaders VertexCount, /// Written to a workgroup variable in mesh shaders Vertices, /// Written to a workgroup variable in mesh shaders PrimitiveCount, /// Written to a workgroup variable in mesh shaders Primitives, /// Read in all ray tracing pipeline shaders, the id within the number of /// rays that this current ray is. RayInvocationId, /// Read in all ray tracing pipeline shaders, the number of rays created. NumRayInvocations, /// Read in closest hit and any hit shaders, the custom data in the tlas /// instance InstanceCustomData, /// Read in closest hit and any hit shaders, the index of the geometry in /// the blas. GeometryIndex, /// Read in closest hit, any hit, and miss shaders, the origin of the ray. WorldRayOrigin, /// Read in closest hit, any hit, and miss shaders, the direction of the /// ray. WorldRayDirection, /// Read in closest hit and any hit shaders, the direction of the ray in /// object space. ObjectRayOrigin, /// Read in closest hit and any hit shaders, the direction of the ray in /// object space. ObjectRayDirection, /// Read in closest hit, any hit, and miss shaders, the t min provided by /// in the ray desc. RayTmin, /// Read in closest hit, any hit, and miss shaders, the final bounds at which /// a hit is accepted (the closest committed hit if there is one otherwise, t /// max provided in the ray desc). RayTCurrentMax, /// Read in closest hit and any hit shaders, the matrix for converting from /// object space to world space ObjectToWorld, /// Read in closest hit and any hit shaders, the matrix for converting from /// world space to object space WorldToObject, /// Read in closest hit and any hit shaders, the type of hit as provided by /// the intersection function if any, otherwise this is 254 (0xFE) for a /// front facing triangle and 255 (0xFF) for a back facing triangle HitKind, } /// Number of bytes per scalar. pub type Bytes = u8; /// Number of components in a vector. #[repr(u8)] #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum VectorSize { /// 2D vector Bi = 2, /// 3D vector Tri = 3, /// 4D vector Quad = 4, } impl VectorSize { pub const MAX: usize = Self::Quad as usize; } impl From for u8 { fn from(size: VectorSize) -> u8 { size as u8 } } impl From for u32 { fn from(size: VectorSize) -> u32 { size as u32 } } /// Number of components in a cooperative vector. #[repr(u8)] #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum CooperativeSize { Eight = 8, Sixteen = 16, } /// Primitive type for a scalar. #[repr(u8)] #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum ScalarKind { /// Signed integer type. Sint, /// Unsigned integer type. Uint, /// Floating point type. Float, /// Boolean type. Bool, /// WGSL abstract integer type. /// /// These are forbidden by validation, and should never reach backends. AbstractInt, /// Abstract floating-point type. /// /// These are forbidden by validation, and should never reach backends. AbstractFloat, } /// Role of a cooperative variable in the equation "A * B + C" #[repr(u8)] #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum CooperativeRole { A, B, C, } /// Characteristics of a scalar type. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct Scalar { /// How the value's bits are to be interpreted. pub kind: ScalarKind, /// This size of the value in bytes. pub width: Bytes, } /// Size of an array. #[repr(u8)] #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum ArraySize { /// The array size is constant. Constant(core::num::NonZeroU32), /// The array size is an override-expression. Pending(Handle), /// The array size can change at runtime. Dynamic, } /// The interpolation qualifier of a binding or struct field. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum Interpolation { /// The value will be interpolated in a perspective-correct fashion. /// Also known as "smooth" in glsl. Perspective, /// Indicates that linear, non-perspective, correct /// interpolation must be used. /// Also known as "no_perspective" in glsl. Linear, /// Indicates that no interpolation will be performed. Flat, /// Indicates the fragment input binding holds an array of per-vertex values. /// This is typically used with barycentrics. PerVertex, } /// The sampling qualifiers of a binding or struct field. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum Sampling { /// Interpolate the value at the center of the pixel. Center, /// Interpolate the value at a point that lies within all samples covered by /// the fragment within the current primitive. In multisampling, use a /// single value for all samples in the primitive. Centroid, /// Interpolate the value at each sample location. In multisampling, invoke /// the fragment shader once per sample. Sample, /// Use the value provided by the first vertex of the current primitive. First, /// Use the value provided by the first or last vertex of the current primitive. The exact /// choice is implementation-dependent. Either, } /// Member of a user-defined structure. // Clone is used only for error reporting and is not intended for end users #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct StructMember { pub name: Option, /// Type of the field. pub ty: Handle, /// For I/O structs, defines the binding. pub binding: Option, /// Offset from the beginning from the struct. pub offset: u32, } /// The number of dimensions an image has. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum ImageDimension { /// 1D image D1, /// 2D image D2, /// 3D image D3, /// Cube map Cube, } bitflags::bitflags! { /// Flags describing an image. #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct StorageAccess: u32 { /// Storage can be used as a source for load ops. const LOAD = 0x1; /// Storage can be used as a target for store ops. const STORE = 0x2; /// Storage can be used as a target for atomic ops. const ATOMIC = 0x4; } } bitflags::bitflags! { /// Memory decorations for global variables. #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct MemoryDecorations: u8 { /// Reads and writes are automatically visible to other invocations /// without explicit barriers. const COHERENT = 0x1; /// The variable may be modified by something external to the shader, /// preventing certain compiler optimizations. const VOLATILE = 0x2; } } /// Image storage format. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum StorageFormat { // 8-bit formats R8Unorm, R8Snorm, R8Uint, R8Sint, // 16-bit formats R16Uint, R16Sint, R16Float, Rg8Unorm, Rg8Snorm, Rg8Uint, Rg8Sint, // 32-bit formats R32Uint, R32Sint, R32Float, Rg16Uint, Rg16Sint, Rg16Float, Rgba8Unorm, Rgba8Snorm, Rgba8Uint, Rgba8Sint, Bgra8Unorm, // Packed 32-bit formats Rgb10a2Uint, Rgb10a2Unorm, Rg11b10Ufloat, // 64-bit formats R64Uint, Rg32Uint, Rg32Sint, Rg32Float, Rgba16Uint, Rgba16Sint, Rgba16Float, // 128-bit formats Rgba32Uint, Rgba32Sint, Rgba32Float, // Normalized 16-bit per channel formats R16Unorm, R16Snorm, Rg16Unorm, Rg16Snorm, Rgba16Unorm, Rgba16Snorm, } /// Sub-class of the image type. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum ImageClass { /// Regular sampled image. Sampled { /// Kind of values to sample. kind: ScalarKind, /// Multi-sampled image. /// /// A multi-sampled image holds several samples per texel. Multi-sampled /// images cannot have mipmaps. multi: bool, }, /// Depth comparison image. Depth { /// Multi-sampled depth image. multi: bool, }, /// External texture. External, /// Storage image. Storage { format: StorageFormat, access: StorageAccess, }, } /// A data type declared in the module. #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct Type { /// The name of the type, if any. pub name: Option, /// Inner structure that depends on the kind of the type. pub inner: TypeInner, } /// Enum with additional information, depending on the kind of type. /// /// Comparison using `==` is not reliable in the case of [`Pointer`], /// [`ValuePointer`], or [`Struct`] variants. For these variants, /// use [`TypeInner::non_struct_equivalent`] or [`compare_types`]. /// /// [`compare_types`]: crate::proc::compare_types /// [`ValuePointer`]: TypeInner::ValuePointer /// [`Pointer`]: TypeInner::Pointer /// [`Struct`]: TypeInner::Struct #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum TypeInner { /// Number of integral or floating-point kind. Scalar(Scalar), /// Vector of numbers. Vector { size: VectorSize, scalar: Scalar }, /// Matrix of numbers. Matrix { columns: VectorSize, rows: VectorSize, scalar: Scalar, }, /// Matrix that is cooperatively processed by all the threads /// in an opaque mapping. CooperativeMatrix { columns: CooperativeSize, rows: CooperativeSize, scalar: Scalar, role: CooperativeRole, }, /// Atomic scalar. Atomic(Scalar), /// Pointer to another type. /// /// Pointers to scalars and vectors should be treated as equivalent to /// [`ValuePointer`] types. Use either [`TypeInner::non_struct_equivalent`] /// or [`compare_types`] to compare types in a way that treats pointers /// correctly. /// /// ## Pointers to non-`SIZED` types /// /// The `base` type of a pointer may be a non-[`SIZED`] type like a /// dynamically-sized [`Array`], or a [`Struct`] whose last member is a /// dynamically sized array. Such pointers occur as the types of /// [`GlobalVariable`] or [`AccessIndex`] expressions referring to /// dynamically-sized arrays. /// /// However, among pointers to non-`SIZED` types, only pointers to `Struct`s /// are [`DATA`]. Pointers to dynamically sized `Array`s cannot be passed as /// arguments, stored in variables, or held in arrays or structures. Their /// only use is as the types of `AccessIndex` expressions. /// /// [`SIZED`]: crate::valid::TypeFlags::SIZED /// [`DATA`]: crate::valid::TypeFlags::DATA /// [`Array`]: TypeInner::Array /// [`Struct`]: TypeInner::Struct /// [`ValuePointer`]: TypeInner::ValuePointer /// [`GlobalVariable`]: Expression::GlobalVariable /// [`AccessIndex`]: Expression::AccessIndex /// [`compare_types`]: crate::proc::compare_types Pointer { base: Handle, space: AddressSpace, }, /// Pointer to a scalar or vector. /// /// A `ValuePointer` type is equivalent to a `Pointer` whose `base` is a /// `Scalar` or `Vector` type. This is for use in [`TypeResolution::Value`] /// variants; see the documentation for [`TypeResolution`] for details. /// /// Use [`TypeInner::non_struct_equivalent`] or [`compare_types`] to compare /// types that could be pointers, to ensure that `Pointer` and /// `ValuePointer` types are recognized as equivalent. /// /// [`TypeResolution`]: crate::proc::TypeResolution /// [`TypeResolution::Value`]: crate::proc::TypeResolution::Value /// [`compare_types`]: crate::proc::compare_types ValuePointer { size: Option, scalar: Scalar, space: AddressSpace, }, /// Homogeneous list of elements. /// /// The `base` type must be a [`SIZED`], [`DATA`] type. /// /// ## Dynamically sized arrays /// /// An `Array` is [`SIZED`] unless its `size` is [`Dynamic`]. /// Dynamically-sized arrays may only appear in a few situations: /// /// - They may appear as the type of a [`GlobalVariable`], or as the last /// member of a [`Struct`]. /// /// - They may appear as the base type of a [`Pointer`]. An /// [`AccessIndex`] expression referring to a struct's final /// unsized array member would have such a pointer type. However, such /// pointer types may only appear as the types of such intermediate /// expressions. They are not [`DATA`], and cannot be stored in /// variables, held in arrays or structs, or passed as parameters. /// /// [`SIZED`]: crate::valid::TypeFlags::SIZED /// [`DATA`]: crate::valid::TypeFlags::DATA /// [`Dynamic`]: ArraySize::Dynamic /// [`Struct`]: TypeInner::Struct /// [`Pointer`]: TypeInner::Pointer /// [`AccessIndex`]: Expression::AccessIndex Array { base: Handle, size: ArraySize, stride: u32, }, /// User-defined structure. /// /// There must always be at least one member. /// /// A `Struct` type is [`DATA`], and the types of its members must be /// `DATA` as well. /// /// Member types must be [`SIZED`], except for the final member of a /// struct, which may be a dynamically sized [`Array`]. The /// `Struct` type itself is `SIZED` when all its members are `SIZED`. /// /// Two structure types with different names are not equivalent. Because /// this variant does not contain the name, it is not possible to use it /// to compare struct types. Use [`compare_types`] to compare two types /// that may be structs. /// /// [`DATA`]: crate::valid::TypeFlags::DATA /// [`SIZED`]: crate::∅TypeFlags::SIZED /// [`Array`]: TypeInner::Array /// [`compare_types`]: crate::proc::compare_types Struct { members: Vec, //TODO: should this be unaligned? span: u32, }, /// Possibly multidimensional array of texels. Image { dim: ImageDimension, arrayed: bool, //TODO: consider moving `multisampled: bool` out class: ImageClass, }, /// Can be used to sample values from images. Sampler { comparison: bool }, /// Opaque object representing an acceleration structure of geometry. AccelerationStructure { vertex_return: bool }, /// Locally used handle for ray queries. RayQuery { vertex_return: bool }, /// Array of bindings. /// /// A `BindingArray` represents an array where each element draws its value /// from a separate bound resource. The array's element type `base` may be /// [`Image`], [`Sampler`], or any type that would be permitted for a global /// in the [`Uniform`] or [`Storage`] address spaces. Only global variables /// may be binding arrays; on the host side, their values are provided by /// [`TextureViewArray`], [`SamplerArray`], or [`BufferArray`] /// bindings. /// /// Since each element comes from a distinct resource, a binding array of /// images could have images of varying sizes (but not varying dimensions; /// they must all have the same `Image` type). Or, a binding array of /// buffers could have elements that are dynamically sized arrays, each with /// a different length. /// /// Binding arrays are in the same address spaces as their underlying type. /// As such, referring to an array of images produces an [`Image`] value /// directly (as opposed to a pointer). The only operation permitted on /// `BindingArray` values is indexing, which works transparently: indexing /// a binding array of samplers yields a [`Sampler`], indexing a pointer to the /// binding array of storage buffers produces a pointer to the storage struct. /// /// Unlike textures and samplers, binding arrays are not [`ARGUMENT`], so /// they cannot be passed as arguments to functions. /// /// Naga's WGSL front end supports binding arrays with the type syntax /// `binding_array`. /// /// [`Image`]: TypeInner::Image /// [`Sampler`]: TypeInner::Sampler /// [`Uniform`]: AddressSpace::Uniform /// [`Storage`]: AddressSpace::Storage /// [`TextureViewArray`]: https://docs.rs/wgpu/latest/wgpu/enum.BindingResource.html#variant.TextureViewArray /// [`SamplerArray`]: https://docs.rs/wgpu/latest/wgpu/enum.BindingResource.html#variant.SamplerArray /// [`BufferArray`]: https://docs.rs/wgpu/latest/wgpu/enum.BindingResource.html#variant.BufferArray /// [`DATA`]: crate::valid::TypeFlags::DATA /// [`ARGUMENT`]: crate::valid::TypeFlags::ARGUMENT /// [naga#1864]: https://github.com/gfx-rs/naga/issues/1864 BindingArray { base: Handle, size: ArraySize }, } #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum Literal { /// May not be NaN or infinity. F64(f64), /// May not be NaN or infinity. F32(f32), /// May not be NaN or infinity. F16(f16), U32(u32), I32(i32), U64(u64), I64(i64), Bool(bool), AbstractInt(i64), AbstractFloat(f64), } /// Pipeline-overridable constant. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct Override { pub name: Option, /// Pipeline Constant ID. pub id: Option, pub ty: Handle, /// The default value of the pipeline-overridable constant. /// /// This [`Handle`] refers to [`Module::global_expressions`], not /// any [`Function::expressions`] arena. pub init: Option>, } /// Constant value. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct Constant { pub name: Option, pub ty: Handle, /// The value of the constant. /// /// This [`Handle`] refers to [`Module::global_expressions`], not /// any [`Function::expressions`] arena. pub init: Handle, } /// Describes how an input/output variable is to be bound. #[derive(Clone, Debug, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum Binding { /// Built-in shader variable. BuiltIn(BuiltIn), /// Indexed location. /// /// This is a value passed to a [`Fragment`] shader from a [`Vertex`] or /// [`Mesh`] shader. /// /// Values passed from the [`Vertex`] stage to the [`Fragment`] stage must /// have their `interpolation` defaulted (i.e. not `None`) by the front end /// as appropriate for that language. /// /// For other stages, we permit interpolations even though they're ignored. /// When a front end is parsing a struct type, it usually doesn't know what /// stages will be using it for IO, so it's easiest if it can apply the /// defaults to anything with a `Location` binding, just in case. /// /// For anything other than floating-point scalars and vectors, the /// interpolation must be `Flat`. /// /// [`Vertex`]: crate::ShaderStage::Vertex /// [`Mesh`]: crate::ShaderStage::Mesh /// [`Fragment`]: crate::ShaderStage::Fragment Location { location: u32, interpolation: Option, sampling: Option, /// Optional `blend_src` index used for dual source blending. /// See blend_src: Option, /// Whether the binding is a per-primitive binding for use with mesh shaders. /// /// This must be `true` if this binding is a mesh shader primitive output, or such /// an output's corresponding fragment shader input. It must be `false` otherwise. /// /// A stage's outputs must all have unique `location` numbers, regardless of /// whether they are per-primitive; a mesh shader's per-vertex and per-primitive /// outputs share the same location numbering space. /// /// Per-primitive values are not interpolated at all and are not dependent on the /// vertices or pixel location. For example, it may be used to store a /// non-interpolated normal vector. per_primitive: bool, }, } /// Pipeline binding information for global resources. #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct ResourceBinding { /// The bind group index. pub group: u32, /// Binding number within the group. pub binding: u32, } /// Variable defined at module level. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct GlobalVariable { /// Name of the variable, if any. pub name: Option, /// How this variable is to be stored. pub space: AddressSpace, /// For resources, defines the binding point. pub binding: Option, /// The type of this variable. pub ty: Handle, /// Initial value for this variable. /// /// This refers to an [`Expression`] in [`Module::global_expressions`]. pub init: Option>, /// Memory decorations for this variable. /// /// These are meaningful for storage address space variables in SPIR-V, /// where they map to SPIR-V memory decorations on the variable. /// /// In WGSL, these can be set with attributes like `@coherent` or `@volatile`. pub memory_decorations: MemoryDecorations, } /// Variable defined at function level. #[derive(Clone, Debug)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct LocalVariable { /// Name of the variable, if any. pub name: Option, /// The type of this variable. pub ty: Handle, /// Initial value for this variable. /// /// This handle refers to an expression in this `LocalVariable`'s function's /// [`expressions`] arena, but it is required to be an evaluated override /// expression. /// /// [`expressions`]: Function::expressions pub init: Option>, } /// Operation that can be applied on a single value. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum UnaryOperator { Negate, LogicalNot, BitwiseNot, } /// Operation that can be applied on two values. /// /// ## Arithmetic type rules /// /// The arithmetic operations `Add`, `Subtract`, `Multiply`, `Divide`, and /// `Modulo` can all be applied to [`Scalar`] types other than [`Bool`], or /// [`Vector`]s thereof. Both operands must have the same type. /// /// `Add` and `Subtract` can also be applied to [`Matrix`] values. Both operands /// must have the same type. /// /// `Multiply` supports additional cases: /// /// - A [`Matrix`] or [`Vector`] can be multiplied by a scalar [`Float`], /// either on the left or the right. /// /// - A [`Matrix`] on the left can be multiplied by a [`Vector`] on the right /// if the matrix has as many columns as the vector has components /// (`matCxR * VecC`). /// /// - A [`Vector`] on the left can be multiplied by a [`Matrix`] on the right /// if the matrix has as many rows as the vector has components /// (`VecR * matCxR`). /// /// - Two matrices can be multiplied if the left operand has as many columns /// as the right operand has rows (`matNxR * matCxN`). /// /// In all the above `Multiply` cases, the byte widths of the underlying scalar /// types of both operands must be the same. /// /// Note that `Multiply` supports mixed vector and scalar operations directly, /// whereas the other arithmetic operations require an explicit [`Splat`] for /// mixed-type use. /// /// [`Scalar`]: TypeInner::Scalar /// [`Vector`]: TypeInner::Vector /// [`Matrix`]: TypeInner::Matrix /// [`Float`]: ScalarKind::Float /// [`Bool`]: ScalarKind::Bool /// [`Splat`]: Expression::Splat #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum BinaryOperator { Add, Subtract, Multiply, Divide, /// Equivalent of the WGSL's `%` operator or SPIR-V's `OpFRem` Modulo, Equal, NotEqual, Less, LessEqual, Greater, GreaterEqual, And, ExclusiveOr, InclusiveOr, LogicalAnd, LogicalOr, ShiftLeft, /// Right shift carries the sign of signed integers only. ShiftRight, } /// Function on an atomic value. /// /// Note: these do not include load/store, which use the existing /// [`Expression::Load`] and [`Statement::Store`]. /// /// All `Handle` values here refer to an expression in /// [`Function::expressions`]. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum AtomicFunction { Add, Subtract, And, ExclusiveOr, InclusiveOr, Min, Max, Exchange { compare: Option> }, } /// Hint at which precision to compute a derivative. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum DerivativeControl { Coarse, Fine, None, } /// Axis on which to compute a derivative. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum DerivativeAxis { X, Y, Width, } /// Built-in shader function for testing relation between values. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum RelationalFunction { All, Any, IsNan, IsInf, } /// Built-in shader function for math. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum MathFunction { // comparison Abs, Min, Max, Clamp, Saturate, // trigonometry Cos, Cosh, Sin, Sinh, Tan, Tanh, Acos, Asin, Atan, Atan2, Asinh, Acosh, Atanh, Radians, Degrees, // decomposition Ceil, Floor, Round, Fract, Trunc, Modf, Frexp, Ldexp, // exponent Exp, Exp2, Log, Log2, Pow, // geometry Dot, Dot4I8Packed, Dot4U8Packed, Outer, Cross, Distance, Length, Normalize, FaceForward, Reflect, Refract, // computational Sign, Fma, Mix, Step, SmoothStep, Sqrt, InverseSqrt, Inverse, Transpose, Determinant, QuantizeToF16, // bits CountTrailingZeros, CountLeadingZeros, CountOneBits, ReverseBits, ExtractBits, InsertBits, FirstTrailingBit, FirstLeadingBit, // data packing Pack4x8snorm, Pack4x8unorm, Pack2x16snorm, Pack2x16unorm, Pack2x16float, Pack4xI8, Pack4xU8, Pack4xI8Clamp, Pack4xU8Clamp, // data unpacking Unpack4x8snorm, Unpack4x8unorm, Unpack2x16snorm, Unpack2x16unorm, Unpack2x16float, Unpack4xI8, Unpack4xU8, } /// Sampling modifier to control the level of detail. /// /// All `Handle` values here refer to an expression in /// [`Function::expressions`]. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum SampleLevel { Auto, Zero, Exact(Handle), Bias(Handle), Gradient { x: Handle, y: Handle, }, } /// Type of an image query. /// /// All `Handle` values here refer to an expression in /// [`Function::expressions`]. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum ImageQuery { /// Get the size at the specified level. /// /// The return value is a `u32` for 1D images, and a `vecN` /// for an image with dimensions N > 2. Size { /// If `None`, the base level is considered. level: Option>, }, /// Get the number of mipmap levels, a `u32`. NumLevels, /// Get the number of array layers, a `u32`. NumLayers, /// Get the number of samples, a `u32`. NumSamples, } /// Component selection for a vector swizzle. #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum SwizzleComponent { X = 0, Y = 1, Z = 2, W = 3, } /// The specific behavior of a [`SubgroupGather`] statement. /// /// All `Handle` values here refer to an expression in /// [`Function::expressions`]. /// /// [`SubgroupGather`]: Statement::SubgroupGather #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum GatherMode { /// All gather from the active lane with the smallest index BroadcastFirst, /// All gather from the same lane at the index given by the expression Broadcast(Handle), /// Each gathers from a different lane at the index given by the expression Shuffle(Handle), /// Each gathers from their lane plus the shift given by the expression ShuffleDown(Handle), /// Each gathers from their lane minus the shift given by the expression ShuffleUp(Handle), /// Each gathers from their lane xored with the given by the expression ShuffleXor(Handle), /// All gather from the same quad lane at the index given by the expression QuadBroadcast(Handle), /// Each gathers from the opposite quad lane along the given direction QuadSwap(Direction), } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum Direction { X = 0, Y = 1, Diagonal = 2, } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum SubgroupOperation { All = 0, Any = 1, Add = 2, Mul = 3, Min = 4, Max = 5, And = 6, Or = 7, Xor = 8, } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum CollectiveOperation { Reduce = 0, InclusiveScan = 1, ExclusiveScan = 2, } bitflags::bitflags! { /// Memory barrier flags. #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct Barrier: u32 { /// Barrier affects all [`AddressSpace::Storage`] accesses. const STORAGE = 1 << 0; /// Barrier affects all [`AddressSpace::WorkGroup`] accesses. const WORK_GROUP = 1 << 1; /// Barrier synchronizes execution across all invocations within a subgroup that execute this instruction. const SUB_GROUP = 1 << 2; /// Barrier synchronizes texture memory accesses in a workgroup. const TEXTURE = 1 << 3; } } #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct CooperativeData { pub pointer: Handle, pub stride: Handle, pub row_major: bool, } /// An expression that can be evaluated to obtain a value. /// /// This is a Single Static Assignment (SSA) scheme similar to SPIR-V. /// /// When an `Expression` variant holds `Handle` fields, they refer /// to another expression in the same arena, unless explicitly noted otherwise. /// One `Arena` may only refer to a different arena indirectly, via /// [`Constant`] or [`Override`] expressions, which hold handles for their /// respective types. /// /// [`Constant`]: Expression::Constant /// [`Override`]: Expression::Override #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum Expression { /// Literal. Literal(Literal), /// Constant value. Constant(Handle), /// Pipeline-overridable constant. Override(Handle), /// Zero value of a type. ZeroValue(Handle), /// Composite expression. Compose { ty: Handle, components: Vec>, }, /// Array access with a computed index. /// /// ## Typing rules /// /// The `base` operand must be some composite type: [`Vector`], [`Matrix`], /// [`Array`], a [`Pointer`] to one of those, or a [`ValuePointer`] with a /// `size`. /// /// The `index` operand must be an integer, signed or unsigned. /// /// Indexing a [`Vector`] or [`Array`] produces a value of its element type. /// Indexing a [`Matrix`] produces a [`Vector`]. /// /// Indexing a [`Pointer`] to any of the above produces a pointer to the /// element/component type, in the same [`space`]. In the case of [`Array`], /// the result is an actual [`Pointer`], but for vectors and matrices, there /// may not be any type in the arena representing the component's type, so /// those produce [`ValuePointer`] types equivalent to the appropriate /// [`Pointer`]. /// /// ## Dynamic indexing restrictions /// /// To accommodate restrictions in some of the shader languages that Naga /// targets, it is not permitted to subscript a matrix with a dynamically /// computed index unless that matrix appears behind a pointer. In other /// words, if the inner type of `base` is [`Matrix`], then `index` must be a /// constant. But if the type of `base` is a [`Pointer`] to an matrix, then /// the index may be any expression of integer type. /// /// You can use the [`Expression::is_dynamic_index`] method to determine /// whether a given index expression requires matrix base operands to be /// behind a pointer. /// /// (It would be simpler to always require the use of `AccessIndex` when /// subscripting matrices that are not behind pointers, but to accommodate /// existing front ends, Naga also permits `Access`, with a restricted /// `index`.) /// /// [`Vector`]: TypeInner::Vector /// [`Matrix`]: TypeInner::Matrix /// [`Array`]: TypeInner::Array /// [`Pointer`]: TypeInner::Pointer /// [`space`]: TypeInner::Pointer::space /// [`ValuePointer`]: TypeInner::ValuePointer /// [`Float`]: ScalarKind::Float Access { base: Handle, index: Handle, }, /// Access the same types as [`Access`], plus [`Struct`] with a known index. /// /// [`Access`]: Expression::Access /// [`Struct`]: TypeInner::Struct AccessIndex { base: Handle, index: u32, }, /// Splat scalar into a vector. Splat { size: VectorSize, value: Handle, }, /// Vector swizzle. Swizzle { size: VectorSize, vector: Handle, pattern: [SwizzleComponent; 4], }, /// Reference a function parameter, by its index. /// /// A `FunctionArgument` expression evaluates to the argument's value. FunctionArgument(u32), /// Reference a global variable. /// /// If the given `GlobalVariable`'s [`space`] is [`AddressSpace::Handle`], /// then the variable stores some opaque type like a sampler or an image, /// and a `GlobalVariable` expression referring to it produces the /// variable's value directly. /// /// For any other address space, a `GlobalVariable` expression produces a /// pointer to the variable's value. You must use a [`Load`] expression to /// retrieve its value, or a [`Store`] statement to assign it a new value. /// /// [`space`]: GlobalVariable::space /// [`Load`]: Expression::Load /// [`Store`]: Statement::Store GlobalVariable(Handle), /// Reference a local variable. /// /// A `LocalVariable` expression evaluates to a pointer to the variable's value. /// You must use a [`Load`](Expression::Load) expression to retrieve its value, /// or a [`Store`](Statement::Store) statement to assign it a new value. LocalVariable(Handle), /// Load a value indirectly. /// /// For [`TypeInner::Atomic`] the result is a corresponding scalar. /// For other types behind the `pointer`, the result is `T`. Load { pointer: Handle }, /// Sample a point from a sampled or a depth image. ImageSample { image: Handle, sampler: Handle, /// If Some(), this operation is a gather operation /// on the selected component. gather: Option, coordinate: Handle, array_index: Option>, /// This must be a const-expression. offset: Option>, level: SampleLevel, depth_ref: Option>, /// Whether the sampling operation should clamp each component of /// `coordinate` to the range `[half_texel, 1 - half_texel]`, regardless /// of `sampler`. clamp_to_edge: bool, }, /// Load a texel from an image. /// /// For most images, this returns a four-element vector of the same /// [`ScalarKind`] as the image. If the format of the image does not have /// four components, default values are provided: the first three components /// (typically R, G, and B) default to zero, and the final component /// (typically alpha) defaults to one. /// /// However, if the image's [`class`] is [`Depth`], then this returns a /// [`Float`] scalar value. /// /// [`ScalarKind`]: ScalarKind /// [`class`]: TypeInner::Image::class /// [`Depth`]: ImageClass::Depth /// [`Float`]: ScalarKind::Float ImageLoad { /// The image to load a texel from. This must have type [`Image`]. (This /// will necessarily be a [`GlobalVariable`] or [`FunctionArgument`] /// expression, since no other expressions are allowed to have that /// type.) /// /// [`Image`]: TypeInner::Image /// [`GlobalVariable`]: Expression::GlobalVariable /// [`FunctionArgument`]: Expression::FunctionArgument image: Handle, /// The coordinate of the texel we wish to load. This must be a scalar /// for [`D1`] images, a [`Bi`] vector for [`D2`] images, and a [`Tri`] /// vector for [`D3`] images. (Array indices, sample indices, and /// explicit level-of-detail values are supplied separately.) Its /// component type must be [`Sint`]. /// /// [`D1`]: ImageDimension::D1 /// [`D2`]: ImageDimension::D2 /// [`D3`]: ImageDimension::D3 /// [`Bi`]: VectorSize::Bi /// [`Tri`]: VectorSize::Tri /// [`Sint`]: ScalarKind::Sint coordinate: Handle, /// The index into an arrayed image. If the [`arrayed`] flag in /// `image`'s type is `true`, then this must be `Some(expr)`, where /// `expr` is a [`Sint`] scalar. Otherwise, it must be `None`. /// /// [`arrayed`]: TypeInner::Image::arrayed /// [`Sint`]: ScalarKind::Sint array_index: Option>, /// A sample index, for multisampled [`Sampled`] and [`Depth`] images. /// /// [`Sampled`]: ImageClass::Sampled /// [`Depth`]: ImageClass::Depth sample: Option>, /// A level of detail, for mipmapped images. /// /// This must be present when accessing non-multisampled /// [`Sampled`] and [`Depth`] images, even if only the /// full-resolution level is present (in which case the only /// valid level is zero). /// /// [`Sampled`]: ImageClass::Sampled /// [`Depth`]: ImageClass::Depth level: Option>, }, /// Query information from an image. ImageQuery { image: Handle, query: ImageQuery, }, /// Apply an unary operator. Unary { op: UnaryOperator, expr: Handle, }, /// Apply a binary operator. Binary { op: BinaryOperator, left: Handle, right: Handle, }, /// Select between two values based on a condition. /// /// Note that, because expressions have no side effects, it is unobservable /// whether the non-selected branch is evaluated. Select { /// Boolean expression condition: Handle, accept: Handle, reject: Handle, }, /// Compute the derivative on an axis. Derivative { axis: DerivativeAxis, ctrl: DerivativeControl, expr: Handle, }, /// Call a relational function. Relational { fun: RelationalFunction, argument: Handle, }, /// Call a math function Math { fun: MathFunction, arg: Handle, arg1: Option>, arg2: Option>, arg3: Option>, }, /// Cast a simple type to another kind. As { /// Source expression, which can only be a scalar or a vector. expr: Handle, /// Target scalar kind. kind: ScalarKind, /// If provided, converts to the specified byte width. /// Otherwise, bitcast. convert: Option, }, /// Result of calling another function. CallResult(Handle), /// Result of an atomic operation. /// /// This expression must be referred to by the [`result`] field of exactly one /// [`Atomic`][stmt] statement somewhere in the same function. Let `T` be the /// scalar type contained by the [`Atomic`][type] value that the statement /// operates on. /// /// If `comparison` is `false`, then `ty` must be the scalar type `T`. /// /// If `comparison` is `true`, then `ty` must be a [`Struct`] with two members: /// /// - A member named `old_value`, whose type is `T`, and /// /// - A member named `exchanged`, of type [`BOOL`]. /// /// [`result`]: Statement::Atomic::result /// [stmt]: Statement::Atomic /// [type]: TypeInner::Atomic /// [`Struct`]: TypeInner::Struct /// [`BOOL`]: Scalar::BOOL AtomicResult { ty: Handle, comparison: bool }, /// Result of a [`WorkGroupUniformLoad`] statement. /// /// [`WorkGroupUniformLoad`]: Statement::WorkGroupUniformLoad WorkGroupUniformLoadResult { /// The type of the result ty: Handle, }, /// Get the length of an array. /// The expression must resolve to a pointer to an array with a dynamic size. /// /// This doesn't match the semantics of spirv's `OpArrayLength`, which must be passed /// a pointer to a structure containing a runtime array in its' last field. ArrayLength(Handle), /// Get the Positions of the triangle hit by the [`RayQuery`] /// /// [`RayQuery`]: Statement::RayQuery RayQueryVertexPositions { query: Handle, committed: bool, }, /// Result of a [`Proceed`] [`RayQuery`] statement. /// /// [`Proceed`]: RayQueryFunction::Proceed /// [`RayQuery`]: Statement::RayQuery RayQueryProceedResult, /// Return an intersection found by `query`. /// /// If `committed` is true, return the committed result available when RayQueryGetIntersection { query: Handle, committed: bool, }, /// Result of a [`SubgroupBallot`] statement. /// /// [`SubgroupBallot`]: Statement::SubgroupBallot SubgroupBallotResult, /// Result of a [`SubgroupCollectiveOperation`] or [`SubgroupGather`] statement. /// /// [`SubgroupCollectiveOperation`]: Statement::SubgroupCollectiveOperation /// [`SubgroupGather`]: Statement::SubgroupGather SubgroupOperationResult { ty: Handle }, /// Load a cooperative primitive from memory. CooperativeLoad { columns: CooperativeSize, rows: CooperativeSize, role: CooperativeRole, data: CooperativeData, }, /// Compute `a * b + c` CooperativeMultiplyAdd { a: Handle, b: Handle, c: Handle, }, } /// The value of the switch case. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum SwitchValue { I32(i32), U32(u32), Default, } /// A case for a switch statement. // Clone is used only for error reporting and is not intended for end users #[derive(Clone, Debug)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct SwitchCase { /// Value, upon which the case is considered true. pub value: SwitchValue, /// Body of the case. pub body: Block, /// If true, the control flow continues to the next case in the list, /// or default. pub fall_through: bool, } /// An operation that a [`RayQuery` statement] applies to its [`query`] operand. /// /// [`RayQuery` statement]: Statement::RayQuery /// [`query`]: Statement::RayQuery::query #[derive(Clone, Debug)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum RayQueryFunction { /// Initialize the `RayQuery` object. Initialize { /// The acceleration structure within which this query should search for hits. /// /// The expression must be an [`AccelerationStructure`]. /// /// [`AccelerationStructure`]: TypeInner::AccelerationStructure acceleration_structure: Handle, #[allow(rustdoc::private_intra_doc_links)] /// A struct of detailed parameters for the ray query. /// /// This expression should have the struct type given in /// [`SpecialTypes::ray_desc`]. This is available in the WGSL /// front end as the `RayDesc` type. descriptor: Handle, }, /// Start or continue the query given by the statement's [`query`] operand. /// /// After executing this statement, the `result` expression is a /// [`Bool`] scalar indicating whether there are more intersection /// candidates to consider. /// /// [`query`]: Statement::RayQuery::query /// [`Bool`]: ScalarKind::Bool Proceed { result: Handle, }, /// Add a candidate generated intersection to be included /// in the determination of the closest hit for a ray query. GenerateIntersection { hit_t: Handle, }, /// Confirm a triangle intersection to be included in the determination of /// the closest hit for a ray query. ConfirmIntersection, Terminate, } //TODO: consider removing `Clone`. It's not valid to clone `Statement::Emit` anyway. /// Instructions which make up an executable block. /// /// `Handle` and `Range` values in `Statement` variants /// refer to expressions in [`Function::expressions`], unless otherwise noted. // Clone is used only for error reporting and is not intended for end users #[derive(Clone, Debug)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum Statement { /// Emit a range of expressions, visible to all statements that follow in this block. /// /// See the [module-level documentation][emit] for details. /// /// [emit]: index.html#expression-evaluation-time Emit(Range), /// A block containing more statements, to be executed sequentially. Block(Block), /// Conditionally executes one of two blocks, based on the value of the condition. /// /// Naga IR does not have "phi" instructions. If you need to use /// values computed in an `accept` or `reject` block after the `If`, /// store them in a [`LocalVariable`]. If { condition: Handle, //bool accept: Block, reject: Block, }, /// Conditionally executes one of multiple blocks, based on the value of the selector. /// /// Each case must have a distinct [`value`], exactly one of which must be /// [`Default`]. The `Default` may appear at any position, and covers all /// values not explicitly appearing in other cases. A `Default` appearing in /// the midst of the list of cases does not shadow the cases that follow. /// /// Some backend languages don't support fallthrough (HLSL due to FXC, /// WGSL), and may translate fallthrough cases in the IR by duplicating /// code. However, all backend languages do support cases selected by /// multiple values, like `case 1: case 2: case 3: { ... }`. This is /// represented in the IR as a series of fallthrough cases with empty /// bodies, except for the last. /// /// Naga IR does not have "phi" instructions. If you need to use /// values computed in a [`SwitchCase::body`] block after the `Switch`, /// store them in a [`LocalVariable`]. /// /// [`value`]: SwitchCase::value /// [`body`]: SwitchCase::body /// [`Default`]: SwitchValue::Default Switch { selector: Handle, cases: Vec, }, /// Executes a block repeatedly. /// /// Each iteration of the loop executes the `body` block, followed by the /// `continuing` block. /// /// Executing a [`Break`], [`Return`] or [`Kill`] statement exits the loop. /// /// A [`Continue`] statement in `body` jumps to the `continuing` block. The /// `continuing` block is meant to be used to represent structures like the /// third expression of a C-style `for` loop head, to which `continue` /// statements in the loop's body jump. /// /// The `continuing` block and its substatements must not contain `Return` /// or `Kill` statements, or any `Break` or `Continue` statements targeting /// this loop. (It may have `Break` and `Continue` statements targeting /// loops or switches nested within the `continuing` block.) Expressions /// emitted in `body` are in scope in `continuing`. /// /// If present, `break_if` is an expression which is evaluated after the /// continuing block. Expressions emitted in `body` or `continuing` are /// considered to be in scope. If the expression's value is true, control /// continues after the `Loop` statement, rather than branching back to the /// top of body as usual. The `break_if` expression corresponds to a "break /// if" statement in WGSL, or a loop whose back edge is an /// `OpBranchConditional` instruction in SPIR-V. /// /// Naga IR does not have "phi" instructions. If you need to use /// values computed in a `body` or `continuing` block after the /// `Loop`, store them in a [`LocalVariable`]. /// /// [`Break`]: Statement::Break /// [`Continue`]: Statement::Continue /// [`Kill`]: Statement::Kill /// [`Return`]: Statement::Return /// [`break if`]: Self::Loop::break_if Loop { body: Block, continuing: Block, break_if: Option>, }, /// Exits the innermost enclosing [`Loop`] or [`Switch`]. /// /// A `Break` statement may only appear within a [`Loop`] or [`Switch`] /// statement. It may not break out of a [`Loop`] from within the loop's /// `continuing` block. /// /// [`Loop`]: Statement::Loop /// [`Switch`]: Statement::Switch Break, /// Skips to the `continuing` block of the innermost enclosing [`Loop`]. /// /// A `Continue` statement may only appear within the `body` block of the /// innermost enclosing [`Loop`] statement. It must not appear within that /// loop's `continuing` block. /// /// [`Loop`]: Statement::Loop Continue, /// Returns from the function (possibly with a value). /// /// `Return` statements are forbidden within the `continuing` block of a /// [`Loop`] statement. /// /// [`Loop`]: Statement::Loop Return { value: Option> }, /// Aborts the current shader execution. /// /// `Kill` statements are forbidden within the `continuing` block of a /// [`Loop`] statement. /// /// [`Loop`]: Statement::Loop Kill, /// Synchronize invocations within the work group. /// The `Barrier` flags control which memory accesses should be synchronized. /// If empty, this becomes purely an execution barrier. ControlBarrier(Barrier), /// Synchronize invocations within the work group. /// The `Barrier` flags control which memory accesses should be synchronized. MemoryBarrier(Barrier), /// Stores a value at an address. /// /// For [`TypeInner::Atomic`] type behind the pointer, the value /// has to be a corresponding scalar. /// For other types behind the `pointer`, the value is `T`. /// /// This statement is a barrier for any operations on the /// `Expression::LocalVariable` or `Expression::GlobalVariable` /// that is the destination of an access chain, started /// from the `pointer`. Store { pointer: Handle, value: Handle, }, /// Stores a texel value to an image. /// /// The `image`, `coordinate`, and `array_index` fields have the same /// meanings as the corresponding operands of an [`ImageLoad`] expression; /// see that documentation for details. Storing into multisampled images or /// images with mipmaps is not supported, so there are no `level` or /// `sample` operands. /// /// This statement is a barrier for any operations on the corresponding /// [`Expression::GlobalVariable`] for this image. /// /// [`ImageLoad`]: Expression::ImageLoad ImageStore { image: Handle, coordinate: Handle, array_index: Option>, value: Handle, }, /// Atomic function. Atomic { /// Pointer to an atomic value. /// /// This must be a [`Pointer`] to an [`Atomic`] value. The atomic's /// scalar type may be [`I32`] or [`U32`]. /// /// If [`SHADER_INT64_ATOMIC_MIN_MAX`] or [`SHADER_INT64_ATOMIC_ALL_OPS`] are /// enabled, this may also be [`I64`] or [`U64`]. /// /// If [`SHADER_FLOAT32_ATOMIC`] is enabled, this may be [`F32`]. /// /// [`Pointer`]: TypeInner::Pointer /// [`Atomic`]: TypeInner::Atomic /// [`I32`]: Scalar::I32 /// [`U32`]: Scalar::U32 /// [`SHADER_INT64_ATOMIC_MIN_MAX`]: crate::valid::Capabilities::SHADER_INT64_ATOMIC_MIN_MAX /// [`SHADER_INT64_ATOMIC_ALL_OPS`]: crate::valid::Capabilities::SHADER_INT64_ATOMIC_ALL_OPS /// [`SHADER_FLOAT32_ATOMIC`]: crate::valid::Capabilities::SHADER_FLOAT32_ATOMIC /// [`I64`]: Scalar::I64 /// [`U64`]: Scalar::U64 /// [`F32`]: Scalar::F32 pointer: Handle, /// Function to run on the atomic value. /// /// If [`pointer`] refers to a 64-bit atomic value, then: /// /// - The [`SHADER_INT64_ATOMIC_ALL_OPS`] capability allows any [`AtomicFunction`] /// value here. /// /// - The [`SHADER_INT64_ATOMIC_MIN_MAX`] capability allows /// [`AtomicFunction::Min`] and [`AtomicFunction::Max`] /// in the [`Storage`] address space here. /// /// - If neither of those capabilities are present, then 64-bit scalar /// atomics are not allowed. /// /// If [`pointer`] refers to a 32-bit floating-point atomic value, then: /// /// - The [`SHADER_FLOAT32_ATOMIC`] capability allows [`AtomicFunction::Add`], /// [`AtomicFunction::Subtract`], and [`AtomicFunction::Exchange { compare: None }`] /// in the [`Storage`] address space here. /// /// [`AtomicFunction::Exchange { compare: None }`]: AtomicFunction::Exchange /// [`pointer`]: Statement::Atomic::pointer /// [`Storage`]: AddressSpace::Storage /// [`SHADER_INT64_ATOMIC_MIN_MAX`]: crate::valid::Capabilities::SHADER_INT64_ATOMIC_MIN_MAX /// [`SHADER_INT64_ATOMIC_ALL_OPS`]: crate::valid::Capabilities::SHADER_INT64_ATOMIC_ALL_OPS /// [`SHADER_FLOAT32_ATOMIC`]: crate::valid::Capabilities::SHADER_FLOAT32_ATOMIC fun: AtomicFunction, /// Value to use in the function. /// /// This must be a scalar of the same type as [`pointer`]'s atomic's scalar type. /// /// [`pointer`]: Statement::Atomic::pointer value: Handle, /// [`AtomicResult`] expression representing this function's result. /// /// If [`fun`] is [`Exchange { compare: None }`], this must be `Some`, /// as otherwise that operation would be equivalent to a simple [`Store`] /// to the atomic. /// /// Otherwise, this may be `None` if the return value of the operation is not needed. /// /// If `pointer` refers to a 64-bit atomic value, [`SHADER_INT64_ATOMIC_MIN_MAX`] /// is enabled, and [`SHADER_INT64_ATOMIC_ALL_OPS`] is not, this must be `None`. /// /// [`AtomicResult`]: crate::Expression::AtomicResult /// [`fun`]: Statement::Atomic::fun /// [`Store`]: Statement::Store /// [`Exchange { compare: None }`]: AtomicFunction::Exchange /// [`SHADER_INT64_ATOMIC_MIN_MAX`]: crate::valid::Capabilities::SHADER_INT64_ATOMIC_MIN_MAX /// [`SHADER_INT64_ATOMIC_ALL_OPS`]: crate::valid::Capabilities::SHADER_INT64_ATOMIC_ALL_OPS result: Option>, }, /// Performs an atomic operation on a texel value of an image. /// /// Doing atomics on images with mipmaps is not supported, so there is no /// `level` operand. ImageAtomic { /// The image to perform an atomic operation on. This must have type /// [`Image`]. (This will necessarily be a [`GlobalVariable`] or /// [`FunctionArgument`] expression, since no other expressions are /// allowed to have that type.) /// /// [`Image`]: TypeInner::Image /// [`GlobalVariable`]: Expression::GlobalVariable /// [`FunctionArgument`]: Expression::FunctionArgument image: Handle, /// The coordinate of the texel we wish to load. This must be a scalar /// for [`D1`] images, a [`Bi`] vector for [`D2`] images, and a [`Tri`] /// vector for [`D3`] images. (Array indices, sample indices, and /// explicit level-of-detail values are supplied separately.) Its /// component type must be [`Sint`]. /// /// [`D1`]: ImageDimension::D1 /// [`D2`]: ImageDimension::D2 /// [`D3`]: ImageDimension::D3 /// [`Bi`]: VectorSize::Bi /// [`Tri`]: VectorSize::Tri /// [`Sint`]: ScalarKind::Sint coordinate: Handle, /// The index into an arrayed image. If the [`arrayed`] flag in /// `image`'s type is `true`, then this must be `Some(expr)`, where /// `expr` is a [`Sint`] scalar. Otherwise, it must be `None`. /// /// [`arrayed`]: TypeInner::Image::arrayed /// [`Sint`]: ScalarKind::Sint array_index: Option>, /// The kind of atomic operation to perform on the texel. fun: AtomicFunction, /// The value with which to perform the atomic operation. value: Handle, }, /// Load uniformly from a uniform pointer in the workgroup address space. /// /// Corresponds to the [`workgroupUniformLoad`](https://www.w3.org/TR/WGSL/#workgroupUniformLoad-builtin) /// built-in function of wgsl, and has the same barrier semantics WorkGroupUniformLoad { /// This must be of type [`Pointer`] in the [`WorkGroup`] address space /// /// [`Pointer`]: TypeInner::Pointer /// [`WorkGroup`]: AddressSpace::WorkGroup pointer: Handle, /// The [`WorkGroupUniformLoadResult`] expression representing this load's result. /// /// [`WorkGroupUniformLoadResult`]: Expression::WorkGroupUniformLoadResult result: Handle, }, /// Calls a function. /// /// If the `result` is `Some`, the corresponding expression has to be /// `Expression::CallResult`, and this statement serves as a barrier for any /// operations on that expression. Call { function: Handle, arguments: Vec>, result: Option>, }, RayQuery { /// The [`RayQuery`] object this statement operates on. /// /// [`RayQuery`]: TypeInner::RayQuery query: Handle, /// The specific operation we're performing on `query`. fun: RayQueryFunction, }, /// A ray tracing pipeline shader intrinsic. RayPipelineFunction(RayPipelineFunction), /// Calculate a bitmask using a boolean from each active thread in the subgroup SubgroupBallot { /// The [`SubgroupBallotResult`] expression representing this load's result. /// /// [`SubgroupBallotResult`]: Expression::SubgroupBallotResult result: Handle, /// The value from this thread to store in the ballot predicate: Option>, }, /// Gather a value from another active thread in the subgroup SubgroupGather { /// Specifies which thread to gather from mode: GatherMode, /// The value to broadcast over argument: Handle, /// The [`SubgroupOperationResult`] expression representing this load's result. /// /// [`SubgroupOperationResult`]: Expression::SubgroupOperationResult result: Handle, }, /// Compute a collective operation across all active threads in the subgroup SubgroupCollectiveOperation { /// What operation to compute op: SubgroupOperation, /// How to combine the results collective_op: CollectiveOperation, /// The value to compute over argument: Handle, /// The [`SubgroupOperationResult`] expression representing this load's result. /// /// [`SubgroupOperationResult`]: Expression::SubgroupOperationResult result: Handle, }, /// Store a cooperative primitive into memory. CooperativeStore { target: Handle, data: CooperativeData, }, } /// A function argument. #[derive(Clone, Debug)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct FunctionArgument { /// Name of the argument, if any. pub name: Option, /// Type of the argument. pub ty: Handle, /// For entry points, an argument has to have a binding /// unless it's a structure. pub binding: Option, } /// A function result. #[derive(Clone, Debug)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct FunctionResult { /// Type of the result. pub ty: Handle, /// For entry points, the result has to have a binding /// unless it's a structure. pub binding: Option, } /// A function defined in the module. #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct Function { /// Name of the function, if any. pub name: Option, /// Information about function argument. pub arguments: Vec, /// The result of this function, if any. pub result: Option, /// Local variables defined and used in the function. pub local_variables: Arena, /// Expressions used inside this function. /// /// Unless explicitly stated otherwise, if an [`Expression`] is in this /// arena, then its subexpressions are in this arena too. In other words, /// every `Handle` in this arena refers to an [`Expression`] in /// this arena too. /// /// The main ways this arena refers to [`Module::global_expressions`] are: /// /// - [`Constant`], [`Override`], and [`GlobalVariable`] expressions hold /// handles for their respective types, whose initializer expressions are /// in [`Module::global_expressions`]. /// /// - Various expressions hold [`Type`] handles, and [`Type`]s may refer to /// global expressions, for things like array lengths. /// /// An [`Expression`] must occur before all other [`Expression`]s that use /// its value. /// /// [`Constant`]: Expression::Constant /// [`Override`]: Expression::Override /// [`GlobalVariable`]: Expression::GlobalVariable pub expressions: Arena, /// Map of expressions that have associated variable names pub named_expressions: NamedExpressions, /// Block of instructions comprising the body of the function. pub body: Block, /// The leaf of all diagnostic filter rules tree (stored in [`Module::diagnostic_filters`]) /// parsed on this function. /// /// In WGSL, this corresponds to `@diagnostic(…)` attributes. /// /// See [`DiagnosticFilterNode`] for details on how the tree is represented and used in /// validation. pub diagnostic_filter_leaf: Option>, } /// The main function for a pipeline stage. /// /// An [`EntryPoint`] is a [`Function`] that serves as the main function for a /// graphics or compute pipeline stage. For example, an `EntryPoint` whose /// [`stage`] is [`ShaderStage::Vertex`] can serve as a graphics pipeline's /// vertex shader. /// /// Since an entry point is called directly by the graphics or compute pipeline, /// not by other WGSL functions, you must specify what the pipeline should pass /// as the entry point's arguments, and what values it will return. For example, /// a vertex shader needs a vertex's attributes as its arguments, but if it's /// used for instanced draw calls, it will also want to know the instance id. /// The vertex shader's return value will usually include an output vertex /// position, and possibly other attributes to be interpolated and passed along /// to a fragment shader. /// /// To specify this, the arguments and result of an `EntryPoint`'s [`function`] /// must each have a [`Binding`], or be structs whose members all have /// `Binding`s. This associates every value passed to or returned from the entry /// point with either a [`BuiltIn`] or a [`Location`]: /// /// - A [`BuiltIn`] has special semantics, usually specific to its pipeline /// stage. For example, the result of a vertex shader can include a /// [`BuiltIn::Position`] value, which determines the position of a vertex /// of a rendered primitive. Or, a compute shader might take an argument /// whose binding is [`BuiltIn::WorkGroupSize`], through which the compute /// pipeline would pass the number of invocations in your workgroup. /// /// - A [`Location`] indicates user-defined IO to be passed from one pipeline /// stage to the next. For example, a vertex shader might also produce a /// `uv` texture location as a user-defined IO value. /// /// In other words, the pipeline stage's input and output interface are /// determined by the bindings of the arguments and result of the `EntryPoint`'s /// [`function`]. /// /// [`Function`]: crate::Function /// [`Location`]: Binding::Location /// [`function`]: EntryPoint::function /// [`stage`]: EntryPoint::stage #[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct EntryPoint { /// Name of this entry point, visible externally. /// /// Entry point names for a given `stage` must be distinct within a module. pub name: String, /// Shader stage. pub stage: ShaderStage, /// Early depth test for fragment stages. pub early_depth_test: Option, /// Workgroup size for compute stages pub workgroup_size: [u32; 3], /// Override expressions for workgroup size in the global_expressions arena pub workgroup_size_overrides: Option<[Option>; 3]>, /// The entrance function. pub function: Function, /// Information for [`Mesh`] shaders. /// /// [`Mesh`]: ShaderStage::Mesh pub mesh_info: Option, /// The unique global variable used as a task payload from task shader to mesh shader pub task_payload: Option>, /// The unique global variable used as an incoming ray payload going into any hit, closest hit and miss shaders. /// Unlike the outgoing ray payload, an incoming ray payload must be unique pub incoming_ray_payload: Option>, } /// Return types predeclared for the frexp, modf, and atomicCompareExchangeWeak built-in functions. /// /// These cannot be spelled in WGSL source. /// /// Stored in [`SpecialTypes::predeclared_types`] and created by [`Module::generate_predeclared_type`]. #[derive(Debug, PartialEq, Eq, Hash, Clone)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum PredeclaredType { AtomicCompareExchangeWeakResult(Scalar), ModfResult { size: Option, scalar: Scalar, }, FrexpResult { size: Option, scalar: Scalar, }, } /// Set of special types that can be optionally generated by the frontends. #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct SpecialTypes { /// Type for `RayDesc`. /// /// Call [`Module::generate_ray_desc_type`] to populate this if /// needed and return the handle. pub ray_desc: Option>, /// Type for `RayIntersection`. /// /// Call [`Module::generate_ray_intersection_type`] to populate /// this if needed and return the handle. pub ray_intersection: Option>, /// Type for `RayVertexReturn`. /// /// Call [`Module::generate_vertex_return_type`] pub ray_vertex_return: Option>, /// Struct containing parameters required by some backends to emit code for /// [`ImageClass::External`] textures. /// /// See `wgpu_core::device::resource::ExternalTextureParams` for the /// documentation of each field. /// /// In WGSL, this type would be: /// /// ```ignore /// struct NagaExternalTextureParams { // align size offset /// yuv_conversion_matrix: mat4x4, // 16 64 0 /// gamut_conversion_matrix: mat3x3, // 16 48 64 /// src_tf: NagaExternalTextureTransferFn, // 4 16 112 /// dst_tf: NagaExternalTextureTransferFn, // 4 16 128 /// sample_transform: mat3x2, // 8 24 144 /// load_transform: mat3x2, // 8 24 168 /// size: vec2, // 8 8 192 /// num_planes: u32, // 4 4 200 /// } // whole struct: 16 208 /// ``` /// /// Call [`Module::generate_external_texture_types`] to populate this if /// needed. pub external_texture_params: Option>, /// Struct describing a gamma encoding transfer function. Member of /// `NagaExternalTextureParams`, describing how the backend should perform /// color space conversion when sampling from [`ImageClass::External`] /// textures. /// /// In WGSL, this type would be: /// /// ```ignore /// struct NagaExternalTextureTransferFn { // align size offset /// a: f32, // 4 4 0 /// b: f32, // 4 4 4 /// g: f32, // 4 4 8 /// k: f32, // 4 4 12 /// } // whole struct: 4 16 /// ``` /// /// Call [`Module::generate_external_texture_types`] to populate this if /// needed. pub external_texture_transfer_function: Option>, /// Types for predeclared wgsl types instantiated on demand. /// /// Call [`Module::generate_predeclared_type`] to populate this if /// needed and return the handle. pub predeclared_types: FastIndexMap>, } bitflags::bitflags! { /// Ray flags used when casting rays. /// Matching vulkan constants can be found in /// https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/KHR/ray_common/ray_flags_section.txt #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct RayFlag: u32 { /// Force all intersections to be treated as opaque. const FORCE_OPAQUE = 0x1; /// Force all intersections to be treated as non-opaque. const FORCE_NO_OPAQUE = 0x2; /// Stop traversal after the first hit. const TERMINATE_ON_FIRST_HIT = 0x4; /// Don't execute the closest hit shader. const SKIP_CLOSEST_HIT_SHADER = 0x8; /// Cull back facing geometry. const CULL_BACK_FACING = 0x10; /// Cull front facing geometry. const CULL_FRONT_FACING = 0x20; /// Cull opaque geometry. const CULL_OPAQUE = 0x40; /// Cull non-opaque geometry. const CULL_NO_OPAQUE = 0x80; /// Skip triangular geometry. const SKIP_TRIANGLES = 0x100; /// Skip axis-aligned bounding boxes. const SKIP_AABBS = 0x200; } } /// Type of a ray query intersection. /// Matching vulkan constants can be found in /// /// but the actual values are different for candidate intersections. #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum RayQueryIntersection { /// No intersection found. /// Matches `RayQueryCommittedIntersectionNoneKHR`. #[default] None = 0, /// Intersecting with triangles. /// Matches `RayQueryCommittedIntersectionTriangleKHR` and `RayQueryCandidateIntersectionTriangleKHR`. Triangle = 1, /// Intersecting with generated primitives. /// Matches `RayQueryCommittedIntersectionGeneratedKHR`. Generated = 2, /// Intersecting with Axis Aligned Bounding Boxes. /// Matches `RayQueryCandidateIntersectionAABBKHR`. Aabb = 3, } /// Doc comments preceding items. /// /// These can be used to generate automated documentation, /// IDE hover information or translate shaders with their context comments. #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct DocComments { pub types: FastIndexMap, Vec>, // The key is: // - key.0: the handle to the Struct // - key.1: the index of the `StructMember`. pub struct_members: FastIndexMap<(Handle, usize), Vec>, pub entry_points: FastIndexMap>, pub functions: FastIndexMap, Vec>, pub constants: FastIndexMap, Vec>, pub global_variables: FastIndexMap, Vec>, // Top level comments, appearing before any space. pub module: Vec, } /// The output topology for a mesh shader. Note that mesh shaders don't allow things like triangle-strips. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum MeshOutputTopology { /// Outputs individual vertices to be rendered as points. Points, /// Outputs groups of 2 vertices to be renderedas lines . Lines, /// Outputs groups of 3 vertices to be rendered as triangles. Triangles, } /// Information specific to mesh shader entry points. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] #[allow(dead_code)] pub struct MeshStageInfo { /// The type of primitive outputted. pub topology: MeshOutputTopology, /// The maximum number of vertices a mesh shader may output. pub max_vertices: u32, /// If pipeline constants are used, the expressions that override `max_vertices` pub max_vertices_override: Option>, /// The maximum number of primitives a mesh shader may output. pub max_primitives: u32, /// If pipeline constants are used, the expressions that override `max_primitives` pub max_primitives_override: Option>, /// The type used by vertex outputs, i.e. what is passed to `setVertex`. pub vertex_output_type: Handle, /// The type used by primitive outputs, i.e. what is passed to `setPrimitive`. pub primitive_output_type: Handle, /// The global variable holding the outputted vertices, primitives, and counts pub output_variable: Handle, } /// Ray tracing pipeline intrinsics #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum RayPipelineFunction { /// Traces a ray through the given acceleration structure TraceRay { /// The acceleration structure within which this ray should search for hits. /// /// The expression must be an [`AccelerationStructure`]. /// /// [`AccelerationStructure`]: TypeInner::AccelerationStructure acceleration_structure: Handle, #[allow(rustdoc::private_intra_doc_links)] /// A struct of detailed parameters for the ray query. /// /// This expression should have the struct type given in /// [`SpecialTypes::ray_desc`]. This is available in the WGSL /// front end as the `RayDesc` type. descriptor: Handle, /// A pointer in the ray_payload or incoming_ray_payload address spaces payload: Handle, // Do we want miss index? What about sbt offset and sbt stride (could be hard to validate)? // https://github.com/gfx-rs/wgpu/issues/8894 }, } /// Shader module. /// /// A module is a set of constants, global variables and functions, as well as /// the types required to define them. /// /// Some functions are marked as entry points, to be used in a certain shader stage. /// /// To create a new module, use the `Default` implementation. /// Alternatively, you can load an existing shader using one of the [available front ends]. /// /// When finished, you can export modules using one of the [available backends]. /// /// ## Module arenas /// /// Most module contents are stored in [`Arena`]s. In a valid module, arena /// elements only refer to prior arena elements. That is, whenever an element in /// some `Arena` contains a `Handle` referring to another element the same /// arena, the handle's referent always precedes the element containing the /// handle. /// /// The elements of [`Module::types`] may refer to [`Expression`]s in /// [`Module::global_expressions`], and those expressions may in turn refer back /// to [`Type`]s in [`Module::types`]. In a valid module, there exists an order /// in which all types and global expressions can be visited such that: /// /// - types and expressions are visited in the order in which they appear in /// their arenas, and /// /// - every element refers only to previously visited elements. /// /// This implies that the graph of types and global expressions is acyclic. /// (However, it is a stronger condition: there are cycle-free arrangements of /// types and expressions for which an order like the one described above does /// not exist. Modules arranged in such a way are not valid.) /// /// [available front ends]: crate::front /// [available backends]: crate::back #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct Module { /// Arena for the types defined in this module. /// /// See the [`Module`] docs for more details about this field. pub types: UniqueArena, /// Dictionary of special type handles. pub special_types: SpecialTypes, /// Arena for the constants defined in this module. pub constants: Arena, /// Arena for the pipeline-overridable constants defined in this module. pub overrides: Arena, /// Arena for the global variables defined in this module. pub global_variables: Arena, /// [Constant expressions] and [override expressions] used by this module. /// /// If an expression is in this arena, then its subexpressions are in this /// arena too. In other words, every `Handle` in this arena /// refers to an [`Expression`] in this arena too. /// /// See the [`Module`] docs for more details about this field. /// /// [Constant expressions]: index.html#constant-expressions /// [override expressions]: index.html#override-expressions pub global_expressions: Arena, /// Arena for the functions defined in this module. /// /// Each function must appear in this arena strictly before all its callers. /// Recursion is not supported. pub functions: Arena, /// Entry points. pub entry_points: Vec, /// Arena for all diagnostic filter rules parsed in this module, including those in functions /// and statements. /// /// This arena contains elements of a _tree_ of diagnostic filter rules. When nodes are built /// by a front-end, they refer to a parent scope pub diagnostic_filters: Arena, /// The leaf of all diagnostic filter rules tree parsed from directives in this module. /// /// In WGSL, this corresponds to `diagnostic(…);` directives. /// /// See [`DiagnosticFilterNode`] for details on how the tree is represented and used in /// validation. pub diagnostic_filter_leaf: Option>, /// Doc comments. pub doc_comments: Option>, } ================================================ FILE: naga/src/keywords/mod.rs ================================================ /*! Lists of reserved keywords for each shading language with a [frontend][crate::front] or [backend][crate::back]. */ #[cfg(any(feature = "wgsl-in", wgsl_out))] pub mod wgsl; ================================================ FILE: naga/src/keywords/wgsl.rs ================================================ /*! Keywords for [WGSL][wgsl] (WebGPU Shading Language). [wgsl]: https://gpuweb.github.io/gpuweb/wgsl.html */ use crate::proc::KeywordSet; use crate::racy_lock::RacyLock; // last sync: https://www.w3.org/TR/2025/CRD-WGSL-20250809/#keyword-summary pub const RESERVED: &[&str] = &[ // Keywords "alias", "break", "case", "const", "const_assert", "continue", "continuing", "default", "diagnostic", "discard", "else", "enable", "false", "fn", "for", "if", "let", "loop", "override", "requires", "return", "struct", "switch", "true", "var", "while", // Reserved "NULL", "Self", "abstract", "active", "alignas", "alignof", "as", "asm", "asm_fragment", "async", "attribute", "auto", "await", "become", "cast", "catch", "class", "co_await", "co_return", "co_yield", "coherent", "column_major", "common", "compile", "compile_fragment", "concept", "const_cast", "consteval", "constexpr", "constinit", "crate", "debugger", "decltype", "delete", "demote", "demote_to_helper", "do", "dynamic_cast", "enum", "explicit", "export", "extends", "extern", "external", "fallthrough", "filter", "final", "finally", "friend", "from", "fxgroup", "get", "goto", "groupshared", "highp", "impl", "implements", "import", "inline", "instanceof", "interface", "layout", "lowp", "macro", "macro_rules", "match", "mediump", "meta", "mod", "module", "move", "mut", "mutable", "namespace", "new", "nil", "noexcept", "noinline", "nointerpolation", "non_coherent", "noncoherent", "noperspective", "null", "nullptr", "of", "operator", "package", "packoffset", "partition", "pass", "patch", "pixelfragment", "precise", "precision", "premerge", "priv", "protected", "pub", "public", "readonly", "ref", "regardless", "register", "reinterpret_cast", "require", "resource", "restrict", "self", "set", "shared", "sizeof", "smooth", "snorm", "static", "static_assert", "static_cast", "std", "subroutine", "super", "target", "template", "this", "thread_local", "throw", "trait", "try", "type", "typedef", "typeid", "typename", "typeof", "union", "unless", "unorm", "unsafe", "unsized", "use", "using", "varying", "virtual", "volatile", "wgsl", "where", "with", "writeonly", "yield", ]; /// The above set of reserved keywords, turned into a cached HashSet. This saves /// significant time during [`Namer::reset`](crate::proc::Namer::reset). /// /// See for benchmarks. pub static RESERVED_SET: RacyLock = RacyLock::new(|| KeywordSet::from_iter(RESERVED)); /// Shadowable words that the WGSL backend should avoid using for declarations. /// /// Includes: /// - [6.9. Predeclared Types and Type-Generators] /// - [6.3.1. Predeclared enumerants] /// - [17. Built-in Functions] /// /// This set must be separate from the [`RESERVED`] set above since the /// [`Namer`](crate::proc::Namer) must ignore these identifiers if they appear /// as struct member names. This is because this set contains `fract` and `exp` /// which are also names used by return types of the `frexp` and `modf` built-in functions. /// /// [6.9. Predeclared Types and Type-Generators]: https://www.w3.org/TR/WGSL/#predeclared-types /// [6.3.1. Predeclared enumerants]: https://www.w3.org/TR/WGSL/#predeclared-enumerants /// [17. Built-in Functions]: https://www.w3.org/TR/WGSL/#builtin-functions pub const BUILTIN_IDENTIFIERS: &[&str] = &[ // types "bool", "i32", "u32", "f32", "f16", "array", "atomic", "vec2", "vec3", "vec4", "mat2x2", "mat2x3", "mat2x4", "mat3x2", "mat3x3", "mat3x4", "mat4x2", "mat4x3", "mat4x4", "ptr", "sampler", "sampler_comparison", "texture_1d", "texture_2d", "texture_2d_array", "texture_3d", "texture_cube", "texture_cube_array", "texture_multisampled_2d", "texture_depth_multisampled_2d", "texture_external", "texture_storage_1d", "texture_storage_2d", "texture_storage_2d_array", "texture_storage_3d", "texture_depth_2d", "texture_depth_2d_array", "texture_depth_cube", "texture_depth_cube_array", // enumerants "read", "write", "read_write", "function", "private", "workgroup", "uniform", "storage", "rgba8unorm", "rgba8snorm", "rgba8uint", "rgba8sint", "rgba16unorm", "rgba16snorm", "rgba16uint", "rgba16sint", "rgba16float", "rg8unorm", "rg8snorm", "rg8uint", "rg8sint", "rg16unorm", "rg16snorm", "rg16uint", "rg16sint", "rg16float", "r32uint", "r32sint", "r32float", "rg32uint", "rg32sint", "rg32float", "rgba32uint", "rgba32sint", "rgba32float", "bgra8unorm", "r8unorm", "r8snorm", "r8uint", "r8sint", "r16unorm", "r16snorm", "r16uint", "r16sint", "r16float", "rgb10a2unorm", "rgb10a2uint", "rg11b10ufloat", // functions "bitcast", "all", "any", "select", "arrayLength", "abs", "acos", "acosh", "asin", "asinh", "atan", "atanh", "atan2", "ceil", "clamp", "cos", "cosh", "countLeadingZeros", "countOneBits", "countTrailingZeros", "cross", "degrees", "determinant", "distance", "dot", "dot4U8Packed", "dot4I8Packed", "exp", "exp2", "extractBits", "faceForward", "firstLeadingBit", "firstTrailingBit", "floor", "fma", "fract", "frexp", "insertBits", "inverseSqrt", "ldexp", "length", "log", "log2", "max", "min", "mix", "modf", "normalize", "pow", "quantizeToF16", "radians", "reflect", "refract", "reverseBits", "round", "saturate", "sign", "sin", "sinh", "smoothstep", "sqrt", "step", "tan", "tanh", "transpose", "trunc", "dpdx", "dpdxCoarse", "dpdxFine", "dpdy", "dpdyCoarse", "dpdyFine", "fwidth", "fwidthCoarse", "fwidthFine", "textureDimensions", "textureGather", "textureGatherCompare", "textureLoad", "textureNumLayers", "textureNumLevels", "textureNumSamples", "textureSample", "textureSampleBias", "textureSampleCompare", "textureSampleCompareLevel", "textureSampleGrad", "textureSampleLevel", "textureSampleBaseClampToEdge", "textureStore", "atomicLoad", "atomicStore", "atomicAdd", "atomicSub", "atomicMax", "atomicMin", "atomicAnd", "atomicOr", "atomicXor", "atomicExchange", "atomicCompareExchangeWeak", "pack4x8snorm", "pack4x8unorm", "pack4xI8", "pack4xU8", "pack4xI8Clamp", "pack4xU8Clamp", "pack2x16snorm", "pack2x16unorm", "pack2x16float", "unpack4x8snorm", "unpack4x8unorm", "unpack4xI8", "unpack4xU8", "unpack2x16snorm", "unpack2x16unorm", "unpack2x16float", "storageBarrier", "textureBarrier", "workgroupBarrier", "workgroupUniformLoad", "subgroupAdd", "subgroupExclusiveAdd", "subgroupInclusiveAdd", "subgroupAll", "subgroupAnd", "subgroupAny", "subgroupBallot", "subgroupBroadcast", "subgroupBroadcastFirst", "subgroupElect", "subgroupMax", "subgroupMin", "subgroupMul", "subgroupExclusiveMul", "subgroupInclusiveMul", "subgroupOr", "subgroupShuffle", "subgroupShuffleDown", "subgroupShuffleUp", "subgroupShuffleXor", "subgroupXor", "quadBroadcast", "quadSwapDiagonal", "quadSwapX", "quadSwapY", // not in the WGSL spec "i64", "u64", "f64", "push_constant", "r64uint", ]; pub static BUILTIN_IDENTIFIER_SET: RacyLock = RacyLock::new(|| KeywordSet::from_iter(BUILTIN_IDENTIFIERS)); ================================================ FILE: naga/src/lib.rs ================================================ /*! Naga can be used to translate source code written in one shading language to another. # Example The following example translates WGSL to GLSL. It requires the features `"wgsl-in"` and `"glsl-out"` to be enabled. */ // If we don't have the required front- and backends, don't try to build this example. #![cfg_attr(all(feature = "wgsl-in", feature = "glsl-out"), doc = "```")] #![cfg_attr(not(all(feature = "wgsl-in", feature = "glsl-out")), doc = "```ignore")] /*! let wgsl_source = " @fragment fn main_fs() -> @location(0) vec4 { return vec4(1.0, 1.0, 1.0, 1.0); } "; // Parse the source into a Module. let module: naga::Module = naga::front::wgsl::parse_str(wgsl_source)?; // Validate the module. // Validation can be made less restrictive by changing the ValidationFlags. let module_info: naga::valid::ModuleInfo = naga::valid::Validator::new( naga::valid::ValidationFlags::all(), naga::valid::Capabilities::all(), ) .subgroup_stages(naga::valid::ShaderStages::all()) .subgroup_operations(naga::valid::SubgroupOperationSet::all()) .validate(&module)?; // Translate the module. use naga::back::glsl; let mut glsl_source = String::new(); glsl::Writer::new( &mut glsl_source, &module, &module_info, &glsl::Options::default(), &glsl::PipelineOptions { entry_point: "main_fs".into(), shader_stage: naga::ShaderStage::Fragment, multiview: None, }, naga::proc::BoundsCheckPolicies::default(), )?.write()?; assert_eq!(glsl_source, "\ #version 310 es precision highp float; precision highp int; layout(location = 0) out vec4 _fs2p_location0; void main() { _fs2p_location0 = vec4(1.0, 1.0, 1.0, 1.0); return; } "); # Ok::<(), Box>(()) ``` */ #![allow( clippy::new_without_default, clippy::unneeded_field_pattern, clippy::match_like_matches_macro, clippy::collapsible_if, clippy::derive_partial_eq_without_eq, clippy::needless_borrowed_reference, clippy::single_match, clippy::enum_variant_names )] #![warn( trivial_casts, trivial_numeric_casts, unused_extern_crates, unused_qualifications, clippy::pattern_type_mismatch, clippy::missing_const_for_fn, clippy::rest_pat_in_fully_bound_structs, clippy::match_wildcard_for_single_variants )] #![deny(clippy::exit)] #![cfg_attr( not(test), warn( clippy::dbg_macro, clippy::panic, clippy::print_stderr, clippy::print_stdout, clippy::todo ) )] #![no_std] #![forbid(unsafe_code)] #[cfg(std)] extern crate std; extern crate alloc; mod arena; pub mod back; pub mod common; pub mod compact; pub mod diagnostic_filter; pub mod error; pub mod front; pub mod ir; pub mod keywords; mod non_max_u32; pub mod proc; mod racy_lock; mod span; pub mod valid; use alloc::string::String; pub use crate::arena::{Arena, Handle, Range, UniqueArena}; pub use crate::span::{SourceLocation, Span, SpanContext, WithSpan}; // TODO: Eliminate this re-export and migrate uses of `crate::Foo` to `use crate::ir; ir::Foo`. pub use ir::*; /// Width of a boolean type, in bytes. pub const BOOL_WIDTH: Bytes = 1; /// Width of abstract types, in bytes. pub const ABSTRACT_WIDTH: Bytes = 8; /// Hash map that is faster but not resilient to DoS attacks. /// (Similar to rustc_hash::FxHashMap but using hashbrown::HashMap instead of alloc::collections::HashMap.) /// To construct a new instance: `FastHashMap::default()` pub type FastHashMap = hashbrown::HashMap>; /// Hash set that is faster but not resilient to DoS attacks. /// (Similar to rustc_hash::FxHashSet but using hashbrown::HashSet instead of alloc::collections::HashMap.) pub type FastHashSet = hashbrown::HashSet>; /// Insertion-order-preserving hash set (`IndexSet`), but with the same /// hasher as `FastHashSet` (faster but not resilient to DoS attacks). pub type FastIndexSet = indexmap::IndexSet>; /// Insertion-order-preserving hash map (`IndexMap`), but with the same /// hasher as `FastHashMap` (faster but not resilient to DoS attacks). pub type FastIndexMap = indexmap::IndexMap>; /// Map of expressions that have associated variable names pub(crate) type NamedExpressions = FastIndexMap, String>; ================================================ FILE: naga/src/non_max_u32.rs ================================================ //! [`NonMaxU32`], a 32-bit type that can represent any value except [`u32::MAX`]. //! //! Naga would like `Option>` to be a 32-bit value, which means we //! need to exclude some index value for use in representing [`None`]. We could //! have [`Handle`] store a [`NonZeroU32`], but zero is a very useful value for //! indexing. We could have a [`Handle`] store a value one greater than its index, //! but it turns out that it's not uncommon to want to work with [`Handle`]s' //! indices, so that bias of 1 becomes more visible than one would like. //! //! This module defines the type [`NonMaxU32`], for which `Option` is //! still a 32-bit value, but which is directly usable as a [`Handle`] index //! type. It still uses a bias of 1 under the hood, but that fact is isolated //! within the implementation. //! //! [`Handle`]: crate::arena::Handle //! [`NonZeroU32`]: core::num::NonZeroU32 #![allow(dead_code)] use core::num::NonZeroU32; /// An unsigned 32-bit value known not to be [`u32::MAX`]. /// /// A `NonMaxU32` value can represent any value in the range `0 .. u32::MAX - /// 1`, and an `Option` is still a 32-bit value. In other words, /// `NonMaxU32` is just like [`NonZeroU32`], except that a different value is /// missing from the full `u32` range. /// /// Since zero is a very useful value in indexing, `NonMaxU32` is more useful /// for representing indices than [`NonZeroU32`]. /// /// `NonMaxU32` values and `Option` values both occupy 32 bits. /// /// # Serialization and Deserialization /// /// When the appropriate Cargo features are enabled, `NonMaxU32` implements /// [`serde::Serialize`] and [`serde::Deserialize`] in the natural way, as the /// integer value it represents. For example, serializing /// `NonMaxU32::new(0).unwrap()` as JSON or RON yields the string `"0"`. This is /// the case despite `NonMaxU32`'s implementation, described below. /// /// # Implementation /// /// Although this should not be observable to its users, a `NonMaxU32` whose /// value is `n` is a newtype around a [`NonZeroU32`] whose value is `n + 1`. /// This way, the range of values that `NonMaxU32` can represent, /// `0..=u32::MAX - 1`, is mapped to the range `1..=u32::MAX`, which is the /// range that /// [`NonZeroU32`] can represent. (And conversely, since /// [`u32`] addition wraps around, the value unrepresentable in `NonMaxU32`, /// [`u32::MAX`], becomes the value unrepresentable in [`NonZeroU32`], `0`.) /// /// [`NonZeroU32`]: core::num::NonZeroU32 #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct NonMaxU32(NonZeroU32); impl NonMaxU32 { /// Construct a [`NonMaxU32`] whose value is `n`, if possible. pub const fn new(n: u32) -> Option { // If `n` is `u32::MAX`, then `n.wrapping_add(1)` is `0`, // so `NonZeroU32::new` returns `None` in exactly the case // where we must return `None`. match NonZeroU32::new(n.wrapping_add(1)) { Some(non_zero) => Some(NonMaxU32(non_zero)), None => None, } } /// Return the value of `self` as a [`u32`]. pub const fn get(self) -> u32 { self.0.get() - 1 } pub fn checked_add(self, n: u32) -> Option { // Adding `n` to `self` produces `u32::MAX` if and only if // adding `n` to `self.0` produces `0`. So we can simply // call `NonZeroU32::checked_add` and let its check for zero // determine whether our add would have produced `u32::MAX`. Some(NonMaxU32(self.0.checked_add(n)?)) } } impl core::fmt::Debug for NonMaxU32 { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.get().fmt(f) } } impl core::fmt::Display for NonMaxU32 { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.get().fmt(f) } } #[cfg(feature = "serialize")] impl serde::Serialize for NonMaxU32 { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_u32(self.get()) } } #[cfg(feature = "deserialize")] impl<'de> serde::Deserialize<'de> for NonMaxU32 { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { // Defer to `u32`'s `Deserialize` implementation. let n = ::deserialize(deserializer)?; // Constrain the range of the value further. NonMaxU32::new(n).ok_or_else(|| { ::invalid_value( serde::de::Unexpected::Unsigned(n as u64), &"a value no less than 0 and no greater than 4294967294 (2^32 - 2)", ) }) } } #[test] fn size() { assert_eq!(size_of::>(), size_of::()); } ================================================ FILE: naga/src/proc/constant_evaluator.rs ================================================ // Code in this file intentionally uses `for` loops and `.push()` rather than // `ArrayVec::from_iter`, because the latter is monomorphized by all three of // the item type, the capacity, and the iterator type, which can easily bloat // the compiled executable (by ~260 KiB, when it was removed). use alloc::{ format, string::{String, ToString}, vec, vec::Vec, }; use core::iter; use arrayvec::ArrayVec; use half::f16; use num_traits::{real::Real, FromPrimitive, One, ToPrimitive, Zero}; use crate::{ arena::{Arena, Handle, HandleVec, UniqueArena}, ArraySize, BinaryOperator, Constant, Expression, Literal, Override, RelationalFunction, ScalarKind, Span, Type, TypeInner, UnaryOperator, }; #[cfg(feature = "wgsl-in")] use crate::common::wgsl::TryToWgsl; /// A macro that allows dollar signs (`$`) to be emitted by other macros. Useful for generating /// `macro_rules!` items that, in turn, emit their own `macro_rules!` items. /// /// Technique stolen directly from /// . macro_rules! with_dollar_sign { ($($body:tt)*) => { macro_rules! __with_dollar_sign { $($body)* } __with_dollar_sign!($); } } macro_rules! gen_component_wise_extractor { ( $ident:ident -> $target:ident, literals: [$( $literal:ident => $mapping:ident: $ty:ident ),+ $(,)?], scalar_kinds: [$( $scalar_kind:ident ),* $(,)?], ) => { /// A subset of [`Literal`]s intended to be used for implementing numeric built-ins. #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] enum $target { $( #[doc = concat!( "Maps to [`Literal::", stringify!($literal), "`]", )] $mapping([$ty; N]), )+ } impl From<$target<1>> for Expression { fn from(value: $target<1>) -> Self { match value { $( $target::$mapping([value]) => { Expression::Literal(Literal::$literal(value)) } )+ } } } #[doc = concat!( "Attempts to evaluate multiple `exprs` as a combined [`", stringify!($target), "`] to pass to `handler`. ", )] /// If `exprs` are vectors of the same length, `handler` is called for each corresponding /// component of each vector. /// /// `handler`'s output is registered as a new expression. If `exprs` are vectors of the /// same length, a new vector expression is registered, composed of each component emitted /// by `handler`. fn $ident( eval: &mut ConstantEvaluator<'_>, span: Span, exprs: [Handle; N], handler: fn($target) -> Result<$target, ConstantEvaluatorError>, ) -> Result, ConstantEvaluatorError> where $target: Into, { assert!(N > 0); let err = ConstantEvaluatorError::InvalidMathArg; let mut exprs = exprs.into_iter(); macro_rules! sanitize { ($expr:expr) => { eval.eval_zero_value_and_splat($expr, span) .map(|expr| &eval.expressions[expr]) }; } let new_expr: Result = match sanitize!(exprs.next().unwrap())? { $( &Expression::Literal(Literal::$literal(x)) => { let mut arr = ArrayVec::<_, N>::new(); arr.push(x); for expr in exprs { match sanitize!(expr)? { &Expression::Literal(Literal::$literal(val)) => arr.push(val), _ => return Err(err), } } let comps = $target::$mapping(arr.into_inner().unwrap()); Ok(handler(comps)?.into()) }, )+ &Expression::Compose { ty, ref components } => match &eval.types[ty].inner { &TypeInner::Vector { size, scalar } => match scalar.kind { $(ScalarKind::$scalar_kind)|* => { let first_ty = ty; let mut component_groups = ArrayVec::, N>::new(); { let mut inner = ArrayVec::new(); for item in crate::proc::flatten_compose( first_ty, components, eval.expressions, eval.types, ) { inner.push(item); } component_groups.push(inner); } for expr in exprs { match sanitize!(expr)? { &Expression::Compose { ty, ref components } if &eval.types[ty].inner == &eval.types[first_ty].inner => { let mut inner = ArrayVec::new(); for item in crate::proc::flatten_compose( ty, components, eval.expressions, eval.types, ) { inner.push(item); } component_groups.push(inner); } _ => return Err(err), } } let component_groups = component_groups.into_inner().unwrap(); let mut new_components = ArrayVec::<_, { crate::VectorSize::MAX }>::new(); for idx in 0..(size as u8).into() { let mut group_arr = ArrayVec::<_, N>::new(); for cs in component_groups.iter() { group_arr.push( cs.get(idx).cloned().ok_or_else(|| err.clone())?, ); } let group = group_arr.into_inner().unwrap(); new_components.push($ident( eval, span, group, handler, )?); } Ok(Expression::Compose { ty: first_ty, components: new_components.into_iter().collect(), }) } _ => return Err(err), }, _ => return Err(err), }, _ => return Err(err), }; eval.register_evaluated_expr(new_expr?, span) } with_dollar_sign! { ($d:tt) => { #[allow(unused)] #[doc = concat!( "A convenience macro for using the same RHS for each [`", stringify!($target), "`] variant in a call to [`", stringify!($ident), "`].", )] macro_rules! $ident { ( $eval:expr, $span:expr, [$d ($d expr:expr),+ $d (,)?], |$d ($d arg:ident),+| $d tt:tt ) => { $ident($eval, $span, [$d ($d expr),+], |args| match args { $( $target::$mapping([$d ($d arg),+]) => { let res = $d tt; Result::map(res, $target::$mapping) }, )+ }) }; } }; } }; } gen_component_wise_extractor! { component_wise_scalar -> Scalar, literals: [ AbstractFloat => AbstractFloat: f64, F32 => F32: f32, F16 => F16: f16, AbstractInt => AbstractInt: i64, U32 => U32: u32, I32 => I32: i32, U64 => U64: u64, I64 => I64: i64, ], scalar_kinds: [ Float, AbstractFloat, Sint, Uint, AbstractInt, ], } gen_component_wise_extractor! { component_wise_float -> Float, literals: [ AbstractFloat => Abstract: f64, F32 => F32: f32, F16 => F16: f16, ], scalar_kinds: [ Float, AbstractFloat, ], } gen_component_wise_extractor! { component_wise_concrete_int -> ConcreteInt, literals: [ U32 => U32: u32, I32 => I32: i32, ], scalar_kinds: [ Sint, Uint, ], } gen_component_wise_extractor! { component_wise_signed -> Signed, literals: [ AbstractFloat => AbstractFloat: f64, AbstractInt => AbstractInt: i64, F32 => F32: f32, F16 => F16: f16, I32 => I32: i32, ], scalar_kinds: [ Sint, AbstractInt, Float, AbstractFloat, ], } /// Vectors with a concrete element type. #[derive(Debug)] enum LiteralVector { F64(ArrayVec), F32(ArrayVec), F16(ArrayVec), U32(ArrayVec), I32(ArrayVec), U64(ArrayVec), I64(ArrayVec), Bool(ArrayVec), AbstractInt(ArrayVec), AbstractFloat(ArrayVec), } impl LiteralVector { #[allow(clippy::missing_const_for_fn, reason = "MSRV")] fn len(&self) -> usize { match *self { LiteralVector::F64(ref v) => v.len(), LiteralVector::F32(ref v) => v.len(), LiteralVector::F16(ref v) => v.len(), LiteralVector::U32(ref v) => v.len(), LiteralVector::I32(ref v) => v.len(), LiteralVector::U64(ref v) => v.len(), LiteralVector::I64(ref v) => v.len(), LiteralVector::Bool(ref v) => v.len(), LiteralVector::AbstractInt(ref v) => v.len(), LiteralVector::AbstractFloat(ref v) => v.len(), } } /// Creates [`LiteralVector`] of size 1 from single [`Literal`] fn from_literal(literal: Literal) -> Self { fn arrayvec_of(val: T) -> ArrayVec { let mut v = ArrayVec::new(); v.push(val); v } match literal { Literal::F64(e) => Self::F64(arrayvec_of(e)), Literal::F32(e) => Self::F32(arrayvec_of(e)), Literal::U32(e) => Self::U32(arrayvec_of(e)), Literal::I32(e) => Self::I32(arrayvec_of(e)), Literal::U64(e) => Self::U64(arrayvec_of(e)), Literal::I64(e) => Self::I64(arrayvec_of(e)), Literal::Bool(e) => Self::Bool(arrayvec_of(e)), Literal::AbstractInt(e) => Self::AbstractInt(arrayvec_of(e)), Literal::AbstractFloat(e) => Self::AbstractFloat(arrayvec_of(e)), Literal::F16(e) => Self::F16(arrayvec_of(e)), } } /// Creates [`LiteralVector`] from [`ArrayVec`] of [`Literal`]s. /// Returns error if components types do not match. /// # Panics /// Panics if vector is empty fn from_literal_vec( components: ArrayVec, ) -> Result { assert!(!components.is_empty()); // TODO: should a vector of i32 be constructible from abstract int? macro_rules! compose_literals { ($components:expr, $variant:ident, $self_variant:ident) => {{ let mut out = ArrayVec::new(); for l in &$components { match l { &Literal::$variant(v) => out.push(v), _ => return Err(ConstantEvaluatorError::InvalidMathArg), } } Self::$self_variant(out) }}; } Ok(match components[0] { Literal::I32(_) => compose_literals!(components, I32, I32), Literal::U32(_) => compose_literals!(components, U32, U32), Literal::I64(_) => compose_literals!(components, I64, I64), Literal::U64(_) => compose_literals!(components, U64, U64), Literal::F32(_) => compose_literals!(components, F32, F32), Literal::F64(_) => compose_literals!(components, F64, F64), Literal::Bool(_) => compose_literals!(components, Bool, Bool), Literal::AbstractInt(_) => compose_literals!(components, AbstractInt, AbstractInt), Literal::AbstractFloat(_) => { compose_literals!(components, AbstractFloat, AbstractFloat) } Literal::F16(_) => compose_literals!(components, F16, F16), }) } #[allow(dead_code)] /// Returns [`ArrayVec`] of [`Literal`]s fn to_literal_vec(&self) -> ArrayVec { macro_rules! decompose_literals { ($v:expr, $variant:ident) => {{ let mut out = ArrayVec::new(); for e in $v { out.push(Literal::$variant(*e)); } out }}; } match *self { LiteralVector::F64(ref v) => decompose_literals!(v, F64), LiteralVector::F32(ref v) => decompose_literals!(v, F32), LiteralVector::F16(ref v) => decompose_literals!(v, F16), LiteralVector::U32(ref v) => decompose_literals!(v, U32), LiteralVector::I32(ref v) => decompose_literals!(v, I32), LiteralVector::U64(ref v) => decompose_literals!(v, U64), LiteralVector::I64(ref v) => decompose_literals!(v, I64), LiteralVector::Bool(ref v) => decompose_literals!(v, Bool), LiteralVector::AbstractInt(ref v) => decompose_literals!(v, AbstractInt), LiteralVector::AbstractFloat(ref v) => decompose_literals!(v, AbstractFloat), } } #[allow(dead_code)] /// Puts self into eval's expressions arena and returns handle to it fn register_as_evaluated_expr( &self, eval: &mut ConstantEvaluator<'_>, span: Span, ) -> Result, ConstantEvaluatorError> { let lit_vec = self.to_literal_vec(); assert!(!lit_vec.is_empty()); let expr = if lit_vec.len() == 1 { Expression::Literal(lit_vec[0]) } else { Expression::Compose { ty: eval.types.insert( Type { name: None, inner: TypeInner::Vector { size: match lit_vec.len() { 2 => crate::VectorSize::Bi, 3 => crate::VectorSize::Tri, 4 => crate::VectorSize::Quad, _ => unreachable!(), }, scalar: lit_vec[0].scalar(), }, }, Span::UNDEFINED, ), components: lit_vec .iter() .map(|&l| eval.register_evaluated_expr(Expression::Literal(l), span)) .collect::>()?, } }; eval.register_evaluated_expr(expr, span) } } /// A macro for matching on [`LiteralVector`] variants. /// /// `Float` variant expands to `F16`, `F32`, `F64` and `AbstractFloat`. /// `Integer` variant expands to `I32`, `I64`, `U32`, `U64` and `AbstractInt`. /// /// For output both [`Literal`] (fold) and [`LiteralVector`] (map) are supported. /// /// Example usage: /// /// ```rust,ignore /// match_literal_vector!(match v => Literal { /// F16 => |v| {v.sum()}, /// Integer => |v| {v.sum()}, /// U32 => |v| -> I32 {v.sum()}, // optionally override return type /// }) /// ``` /// /// ```rust,ignore /// match_literal_vector!(match (e1, e2) => LiteralVector { /// F16 => |e1, e2| {e1+e2}, /// Integer => |e1, e2| {e1+e2}, /// U32 => |e1, e2| -> I32 {e1+e2}, // optionally override return type /// }) /// ``` macro_rules! match_literal_vector { (match $lit_vec:expr => $out:ident { $( $ty:ident => |$($var:ident),+| $(-> $ret:ident)? { $body:expr } ),+ $(,)? }) => { match_literal_vector!(@inner_start $lit_vec; $out; [$($ty),+]; [$({ $($var),+ ; $($ret)? ; $body }),+]) }; (@inner_start $lit_vec:expr; $out:ident; [$($ty:ident),+]; [$({ $($var:ident),+ ; $($ret:ident)? ; $body:expr }),+] ) => { match_literal_vector!(@inner $lit_vec; $out; [$($ty),+]; [] <> [$({ $($var),+ ; $($ret)? ; $body }),+] ) }; (@inner $lit_vec:expr; $out:ident; [$ty:ident $(, $ty1:ident)*]; [$({$_ty:ident ; $($_var:ident),+ ; $($_ret:ident)? ; $_body:expr}),*] <> [$({ $($var:ident),+ ; $($ret:ident)? ; $body:expr }),+] ) => { match_literal_vector!(@inner $ty; $lit_vec; $out; [$($ty1),*]; [$({$_ty ; $($_var),+ ; $($_ret)? ; $_body}),*] <> [$({ $($var),+ ; $($ret)? ; $body }),+] ) }; (@inner Integer; $lit_vec:expr; $out:ident; [$($ty:ident),*]; [$({$_ty:ident ; $($_var:ident),+ ; $($_ret:ident)? ; $_body:expr}),*] <> [ { $($var:ident),+ ; $($ret:ident)? ; $body:expr } $(,{ $($var1:ident),+ ; $($ret1:ident)? ; $body1:expr })* ] ) => { match_literal_vector!(@inner $lit_vec; $out; [U32, I32, U64, I64, AbstractInt $(, $ty)*]; [$({$_ty ; $($_var),+ ; $($_ret)? ; $_body}),*] <> [ { $($var),+ ; $($ret)? ; $body }, // U32 { $($var),+ ; $($ret)? ; $body }, // I32 { $($var),+ ; $($ret)? ; $body }, // U64 { $($var),+ ; $($ret)? ; $body }, // I64 { $($var),+ ; $($ret)? ; $body } // AbstractInt $(,{ $($var1),+ ; $($ret1)? ; $body1 })* ] ) }; (@inner Float; $lit_vec:expr; $out:ident; [$($ty:ident),*]; [$({$_ty:ident ; $($_var:ident),+ ; $($_ret:ident)? ; $_body:expr}),*] <> [ { $($var:ident),+ ; $($ret:ident)? ; $body:expr } $(,{ $($var1:ident),+ ; $($ret1:ident)? ; $body1:expr })* ] ) => { match_literal_vector!(@inner $lit_vec; $out; [F16, F32, F64, AbstractFloat $(, $ty)*]; [$({$_ty ; $($_var),+ ; $($_ret)? ; $_body}),*] <> [ { $($var),+ ; $($ret)? ; $body }, // F16 { $($var),+ ; $($ret)? ; $body }, // F32 { $($var),+ ; $($ret)? ; $body }, // F64 { $($var),+ ; $($ret)? ; $body } // AbstractFloat $(,{ $($var1),+ ; $($ret1)? ; $body1 })* ] ) }; (@inner $ty:ident; $lit_vec:expr; $out:ident; [$ty1:ident $(,$ty2:ident)*]; [$({$_ty:ident ; $($_var:ident),+ ; $($_ret:ident)? ; $_body:expr}),*] <> [ { $($var:ident),+ ; $($ret:ident)? ; $body:expr } $(, { $($var1:ident),+ ; $($ret1:ident)? ; $body1:expr })* ] ) => { match_literal_vector!(@inner $ty1; $lit_vec; $out; [$($ty2),*]; [ $({$_ty ; $($_var),+ ; $($_ret)? ; $_body},)* { $ty; $($var),+ ; $($ret)? ; $body } ] <> [$({ $($var1),+ ; $($ret1)? ; $body1 }),*] ) }; (@inner $ty:ident; $lit_vec:expr; $out:ident; []; [$({$_ty:ident ; $($_var:ident),+ ; $($_ret:ident)? ; $_body:expr}),*] <> [{ $($var:ident),+ ; $($ret:ident)? ; $body:expr }] ) => { match_literal_vector!(@inner_finish $lit_vec; $out; [ $({ $_ty ; $($_var),+ ; $($_ret)? ; $_body },)* { $ty; $($var),+ ; $($ret)? ; $body } ] ) }; (@inner_finish $lit_vec:expr; $out:ident; [$({$ty:ident ; $($var:ident),+ ; $($ret:ident)? ; $body:expr}),+] ) => { match $lit_vec { $( #[allow(unused_parens)] ($(LiteralVector::$ty(ref $var)),+) => { Ok(match_literal_vector!(@expand_ret $out; $ty $(; $ret)? ; $body)) } )+ _ => Err(ConstantEvaluatorError::InvalidMathArg), } }; (@expand_ret $out:ident; $ty:ident; $body:expr) => { $out::$ty($body) }; (@expand_ret $out:ident; $_ty:ident; $ret:ident; $body:expr) => { $out::$ret($body) }; } #[derive(Debug)] enum Behavior<'a> { Wgsl(WgslRestrictions<'a>), Glsl(GlslRestrictions<'a>), } impl Behavior<'_> { /// Returns `true` if the inner WGSL/GLSL restrictions are runtime restrictions. const fn has_runtime_restrictions(&self) -> bool { matches!( self, &Behavior::Wgsl(WgslRestrictions::Runtime(_)) | &Behavior::Glsl(GlslRestrictions::Runtime(_)) ) } } /// A context for evaluating constant expressions. /// /// A `ConstantEvaluator` points at an expression arena to which it can append /// newly evaluated expressions: you pass [`try_eval_and_append`] whatever kind /// of Naga [`Expression`] you like, and if its value can be computed at compile /// time, `try_eval_and_append` appends an expression representing the computed /// value - a tree of [`Literal`], [`Compose`], [`ZeroValue`], and [`Swizzle`] /// expressions - to the arena. See the [`try_eval_and_append`] method for details. /// /// A `ConstantEvaluator` also holds whatever information we need to carry out /// that evaluation: types, other constants, and so on. /// /// [`try_eval_and_append`]: ConstantEvaluator::try_eval_and_append /// [`Compose`]: Expression::Compose /// [`ZeroValue`]: Expression::ZeroValue /// [`Literal`]: Expression::Literal /// [`Swizzle`]: Expression::Swizzle #[derive(Debug)] pub struct ConstantEvaluator<'a> { /// Which language's evaluation rules we should follow. behavior: Behavior<'a>, /// The module's type arena. /// /// Because expressions like [`Splat`] contain type handles, we need to be /// able to add new types to produce those expressions. /// /// [`Splat`]: Expression::Splat types: &'a mut UniqueArena, /// The module's constant arena. constants: &'a Arena, /// The module's override arena. overrides: &'a Arena, /// The arena to which we are contributing expressions. expressions: &'a mut Arena, /// Tracks the constness of expressions residing in [`Self::expressions`] expression_kind_tracker: &'a mut ExpressionKindTracker, layouter: &'a mut crate::proc::Layouter, } #[derive(Debug)] enum WgslRestrictions<'a> { /// - const-expressions will be evaluated and inserted in the arena Const(Option>), /// - const-expressions will be evaluated and inserted in the arena /// - override-expressions will be inserted in the arena Override, /// - const-expressions will be evaluated and inserted in the arena /// - override-expressions will be inserted in the arena /// - runtime-expressions will be inserted in the arena Runtime(FunctionLocalData<'a>), } #[derive(Debug)] enum GlslRestrictions<'a> { /// - const-expressions will be evaluated and inserted in the arena Const, /// - const-expressions will be evaluated and inserted in the arena /// - override-expressions will be inserted in the arena /// - runtime-expressions will be inserted in the arena Runtime(FunctionLocalData<'a>), } #[derive(Debug)] struct FunctionLocalData<'a> { /// Global constant expressions global_expressions: &'a Arena, emitter: &'a mut super::Emitter, block: &'a mut crate::Block, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] pub enum ExpressionKind { Const, Override, Runtime, } #[derive(Debug)] pub struct ExpressionKindTracker { inner: HandleVec, } impl ExpressionKindTracker { pub const fn new() -> Self { Self { inner: HandleVec::new(), } } /// Forces the the expression to not be const pub fn force_non_const(&mut self, value: Handle) { self.inner[value] = ExpressionKind::Runtime; } pub fn insert(&mut self, value: Handle, expr_type: ExpressionKind) { self.inner.insert(value, expr_type); } pub fn is_const(&self, h: Handle) -> bool { matches!(self.type_of(h), ExpressionKind::Const) } pub fn is_const_or_override(&self, h: Handle) -> bool { matches!( self.type_of(h), ExpressionKind::Const | ExpressionKind::Override ) } fn type_of(&self, value: Handle) -> ExpressionKind { self.inner[value] } pub fn from_arena(arena: &Arena) -> Self { let mut tracker = Self { inner: HandleVec::with_capacity(arena.len()), }; for (handle, expr) in arena.iter() { tracker .inner .insert(handle, tracker.type_of_with_expr(expr)); } tracker } fn type_of_with_expr(&self, expr: &Expression) -> ExpressionKind { match *expr { Expression::Literal(_) | Expression::ZeroValue(_) | Expression::Constant(_) => { ExpressionKind::Const } Expression::Override(_) => ExpressionKind::Override, Expression::Compose { ref components, .. } => { let mut expr_type = ExpressionKind::Const; for component in components { expr_type = expr_type.max(self.type_of(*component)) } expr_type } Expression::Splat { value, .. } => self.type_of(value), Expression::AccessIndex { base, .. } => self.type_of(base), Expression::Access { base, index } => self.type_of(base).max(self.type_of(index)), Expression::Swizzle { vector, .. } => self.type_of(vector), Expression::Unary { expr, .. } => self.type_of(expr), Expression::Binary { left, right, .. } => self.type_of(left).max(self.type_of(right)), Expression::Math { arg, arg1, arg2, arg3, .. } => self .type_of(arg) .max( arg1.map(|arg| self.type_of(arg)) .unwrap_or(ExpressionKind::Const), ) .max( arg2.map(|arg| self.type_of(arg)) .unwrap_or(ExpressionKind::Const), ) .max( arg3.map(|arg| self.type_of(arg)) .unwrap_or(ExpressionKind::Const), ), Expression::As { expr, .. } => self.type_of(expr), Expression::Select { condition, accept, reject, } => self .type_of(condition) .max(self.type_of(accept)) .max(self.type_of(reject)), Expression::Relational { argument, .. } => self.type_of(argument), Expression::ArrayLength(expr) => self.type_of(expr), _ => ExpressionKind::Runtime, } } } #[derive(Clone, Debug, thiserror::Error)] #[cfg_attr(test, derive(PartialEq))] pub enum ConstantEvaluatorError { #[error("Constants cannot access function arguments")] FunctionArg, #[error("Constants cannot access global variables")] GlobalVariable, #[error("Constants cannot access local variables")] LocalVariable, #[error("Cannot get the array length of a non array type")] InvalidArrayLengthArg, #[error("Constants cannot get the array length of a dynamically sized array")] ArrayLengthDynamic, #[error("Cannot call arrayLength on array sized by override-expression")] ArrayLengthOverridden, #[error("Constants cannot call functions")] Call, #[error("Constants don't support workGroupUniformLoad")] WorkGroupUniformLoadResult, #[error("Constants don't support atomic functions")] Atomic, #[error("Constants don't support derivative functions")] Derivative, #[error("Constants don't support load expressions")] Load, #[error("Constants don't support image expressions")] ImageExpression, #[error("Constants don't support ray query expressions")] RayQueryExpression, #[error("Constants don't support subgroup expressions")] SubgroupExpression, #[error("Cannot access the type")] InvalidAccessBase, #[error("Cannot access at the index")] InvalidAccessIndex, #[error("Cannot access with index of type")] InvalidAccessIndexTy, #[error("Constants don't support array length expressions")] ArrayLength, #[error("Cannot cast scalar components of expression `{from}` to type `{to}`")] InvalidCastArg { from: String, to: String }, #[error("Cannot apply the unary op to the argument")] InvalidUnaryOpArg, #[error("Cannot apply the binary op to the arguments")] InvalidBinaryOpArgs, #[error("Cannot apply math function to type")] InvalidMathArg, #[error("{0:?} built-in function expects {1:?} arguments but {2:?} were supplied")] InvalidMathArgCount(crate::MathFunction, usize, usize), #[error("{0} built-in function argument is out of valid range")] InvalidMathArgValue(String), #[error("Cannot apply relational function to type")] InvalidRelationalArg(RelationalFunction), #[error("value of `low` is greater than `high` for clamp built-in function")] InvalidClamp, #[error("Constructor expects {expected} components, found {actual}")] InvalidVectorComposeLength { expected: usize, actual: usize }, #[error("Constructor must only contain vector or scalar arguments")] InvalidVectorComposeComponent, #[error("Splat is defined only on scalar values")] SplatScalarOnly, #[error("Can only swizzle vector constants")] SwizzleVectorOnly, #[error("swizzle component not present in source expression")] SwizzleOutOfBounds, #[error("Type is not constructible")] TypeNotConstructible, #[error("Subexpression(s) are not constant")] SubexpressionsAreNotConstant, #[error("Not implemented as constant expression: {0}")] NotImplemented(String), #[error("{0} operation overflowed")] Overflow(String), #[error( "the concrete type `{to_type}` cannot represent the abstract value `{value}` accurately" )] AutomaticConversionLossy { value: String, to_type: &'static str, }, #[error("Division by zero")] DivisionByZero, #[error("Remainder by zero")] RemainderByZero, #[error("RHS of shift operation is greater than or equal to 32")] ShiftedMoreThan32Bits, #[error(transparent)] Literal(#[from] crate::valid::LiteralError), #[error("Can't use pipeline-overridable constants in const-expressions")] Override, #[error("Unexpected runtime-expression")] RuntimeExpr, #[error("Unexpected override-expression")] OverrideExpr, #[error("Expected boolean expression for condition argument of `select`, got something else")] SelectScalarConditionNotABool, #[error( "Expected vectors of the same size for reject and accept args., got {:?} and {:?}", reject, accept )] SelectVecRejectAcceptSizeMismatch { reject: crate::VectorSize, accept: crate::VectorSize, }, #[error("Expected boolean vector for condition arg., got something else")] SelectConditionNotAVecBool, #[error( "Expected same number of vector components between condition, accept, and reject args., got something else", )] SelectConditionVecSizeMismatch, #[error( "Expected reject and accept args. to be scalars of vectors of the same type, got something else", )] SelectAcceptRejectTypeMismatch, #[error("Cooperative operations can't be constant")] CooperativeOperation, } impl<'a> ConstantEvaluator<'a> { /// Return a [`ConstantEvaluator`] that will add expressions to `module`'s /// constant expression arena. /// /// Report errors according to WGSL's rules for constant evaluation. pub const fn for_wgsl_module( module: &'a mut crate::Module, global_expression_kind_tracker: &'a mut ExpressionKindTracker, layouter: &'a mut crate::proc::Layouter, in_override_ctx: bool, ) -> Self { Self::for_module( Behavior::Wgsl(if in_override_ctx { WgslRestrictions::Override } else { WgslRestrictions::Const(None) }), module, global_expression_kind_tracker, layouter, ) } /// Return a [`ConstantEvaluator`] that will add expressions to `module`'s /// constant expression arena. /// /// Report errors according to GLSL's rules for constant evaluation. pub const fn for_glsl_module( module: &'a mut crate::Module, global_expression_kind_tracker: &'a mut ExpressionKindTracker, layouter: &'a mut crate::proc::Layouter, ) -> Self { Self::for_module( Behavior::Glsl(GlslRestrictions::Const), module, global_expression_kind_tracker, layouter, ) } const fn for_module( behavior: Behavior<'a>, module: &'a mut crate::Module, global_expression_kind_tracker: &'a mut ExpressionKindTracker, layouter: &'a mut crate::proc::Layouter, ) -> Self { Self { behavior, types: &mut module.types, constants: &module.constants, overrides: &module.overrides, expressions: &mut module.global_expressions, expression_kind_tracker: global_expression_kind_tracker, layouter, } } /// Return a [`ConstantEvaluator`] that will add expressions to `function`'s /// expression arena. /// /// Report errors according to WGSL's rules for constant evaluation. pub const fn for_wgsl_function( module: &'a mut crate::Module, expressions: &'a mut Arena, local_expression_kind_tracker: &'a mut ExpressionKindTracker, layouter: &'a mut crate::proc::Layouter, emitter: &'a mut super::Emitter, block: &'a mut crate::Block, is_const: bool, ) -> Self { let local_data = FunctionLocalData { global_expressions: &module.global_expressions, emitter, block, }; Self { behavior: Behavior::Wgsl(if is_const { WgslRestrictions::Const(Some(local_data)) } else { WgslRestrictions::Runtime(local_data) }), types: &mut module.types, constants: &module.constants, overrides: &module.overrides, expressions, expression_kind_tracker: local_expression_kind_tracker, layouter, } } /// Return a [`ConstantEvaluator`] that will add expressions to `function`'s /// expression arena. /// /// Report errors according to GLSL's rules for constant evaluation. pub const fn for_glsl_function( module: &'a mut crate::Module, expressions: &'a mut Arena, local_expression_kind_tracker: &'a mut ExpressionKindTracker, layouter: &'a mut crate::proc::Layouter, emitter: &'a mut super::Emitter, block: &'a mut crate::Block, ) -> Self { Self { behavior: Behavior::Glsl(GlslRestrictions::Runtime(FunctionLocalData { global_expressions: &module.global_expressions, emitter, block, })), types: &mut module.types, constants: &module.constants, overrides: &module.overrides, expressions, expression_kind_tracker: local_expression_kind_tracker, layouter, } } pub const fn to_ctx(&self) -> crate::proc::GlobalCtx<'_> { crate::proc::GlobalCtx { types: self.types, constants: self.constants, overrides: self.overrides, global_expressions: match self.function_local_data() { Some(data) => data.global_expressions, None => self.expressions, }, } } fn check(&self, expr: Handle) -> Result<(), ConstantEvaluatorError> { if !self.expression_kind_tracker.is_const(expr) { log::debug!("check: SubexpressionsAreNotConstant"); return Err(ConstantEvaluatorError::SubexpressionsAreNotConstant); } Ok(()) } fn check_and_get( &mut self, expr: Handle, ) -> Result, ConstantEvaluatorError> { match self.expressions[expr] { Expression::Constant(c) => { // Are we working in a function's expression arena, or the // module's constant expression arena? if let Some(function_local_data) = self.function_local_data() { // Deep-copy the constant's value into our arena. self.copy_from( self.constants[c].init, function_local_data.global_expressions, ) } else { // "See through" the constant and use its initializer. Ok(self.constants[c].init) } } _ => { self.check(expr)?; Ok(expr) } } } /// Try to evaluate `expr` at compile time. /// /// The `expr` argument can be any sort of Naga [`Expression`] you like. If /// we can determine its value at compile time, we append an expression /// representing its value - a tree of [`Literal`], [`Compose`], /// [`ZeroValue`], and [`Swizzle`] expressions - to the expression arena /// `self` contributes to. /// /// If `expr`'s value cannot be determined at compile time, and `self` is /// contributing to some function's expression arena, then append `expr` to /// that arena unchanged (and thus unevaluated). Otherwise, `self` must be /// contributing to the module's constant expression arena; since `expr`'s /// value is not a constant, return an error. /// /// We only consider `expr` itself, without recursing into its operands. Its /// operands must all have been produced by prior calls to /// `try_eval_and_append`, to ensure that they have already been reduced to /// an evaluated form if possible. /// /// [`Literal`]: Expression::Literal /// [`Compose`]: Expression::Compose /// [`ZeroValue`]: Expression::ZeroValue /// [`Swizzle`]: Expression::Swizzle pub fn try_eval_and_append( &mut self, expr: Expression, span: Span, ) -> Result, ConstantEvaluatorError> { match self.expression_kind_tracker.type_of_with_expr(&expr) { ExpressionKind::Const => { let eval_result = self.try_eval_and_append_impl(&expr, span); // We should be able to evaluate `Const` expressions at this // point. If we failed to, then that probably means we just // haven't implemented that part of constant evaluation. Work // around this by simply emitting it as a run-time expression. if self.behavior.has_runtime_restrictions() && matches!( eval_result, Err(ConstantEvaluatorError::NotImplemented(_) | ConstantEvaluatorError::InvalidBinaryOpArgs,) ) { Ok(self.append_expr(expr, span, ExpressionKind::Runtime)) } else { eval_result } } ExpressionKind::Override => match self.behavior { Behavior::Wgsl(WgslRestrictions::Override | WgslRestrictions::Runtime(_)) => { Ok(self.append_expr(expr, span, ExpressionKind::Override)) } Behavior::Wgsl(WgslRestrictions::Const(_)) => { Err(ConstantEvaluatorError::OverrideExpr) } // GLSL specialization constants (constant_id) become Override expressions Behavior::Glsl(GlslRestrictions::Runtime(_)) => { Ok(self.append_expr(expr, span, ExpressionKind::Override)) } Behavior::Glsl(GlslRestrictions::Const) => { Err(ConstantEvaluatorError::OverrideExpr) } }, ExpressionKind::Runtime => { if self.behavior.has_runtime_restrictions() { Ok(self.append_expr(expr, span, ExpressionKind::Runtime)) } else { Err(ConstantEvaluatorError::RuntimeExpr) } } } } /// Is the [`Self::expressions`] arena the global module expression arena? const fn is_global_arena(&self) -> bool { matches!( self.behavior, Behavior::Wgsl(WgslRestrictions::Const(None) | WgslRestrictions::Override) | Behavior::Glsl(GlslRestrictions::Const) ) } const fn function_local_data(&self) -> Option<&FunctionLocalData<'a>> { match self.behavior { Behavior::Wgsl( WgslRestrictions::Runtime(ref function_local_data) | WgslRestrictions::Const(Some(ref function_local_data)), ) | Behavior::Glsl(GlslRestrictions::Runtime(ref function_local_data)) => { Some(function_local_data) } _ => None, } } fn try_eval_and_append_impl( &mut self, expr: &Expression, span: Span, ) -> Result, ConstantEvaluatorError> { log::trace!("try_eval_and_append: {expr:?}"); match *expr { Expression::Constant(c) if self.is_global_arena() => { // "See through" the constant and use its initializer. // This is mainly done to avoid having constants pointing to other constants. Ok(self.constants[c].init) } Expression::Override(_) => Err(ConstantEvaluatorError::Override), Expression::Literal(_) | Expression::ZeroValue(_) | Expression::Constant(_) => { self.register_evaluated_expr(expr.clone(), span) } Expression::Compose { ty, ref components } => { let components = components .iter() .map(|component| self.check_and_get(*component)) .collect::, _>>()?; self.register_evaluated_expr(Expression::Compose { ty, components }, span) } Expression::Splat { size, value } => { let value = self.check_and_get(value)?; self.register_evaluated_expr(Expression::Splat { size, value }, span) } Expression::AccessIndex { base, index } => { let base = self.check_and_get(base)?; self.access(base, index as usize, span) } Expression::Access { base, index } => { let base = self.check_and_get(base)?; let index = self.check_and_get(index)?; let index_val: u32 = self .to_ctx() .get_const_val_from(index, self.expressions) .map_err(|_| ConstantEvaluatorError::InvalidAccessIndexTy)?; self.access(base, index_val as usize, span) } Expression::Swizzle { size, vector, pattern, } => { let vector = self.check_and_get(vector)?; self.swizzle(size, span, vector, pattern) } Expression::Unary { expr, op } => { let expr = self.check_and_get(expr)?; self.unary_op(op, expr, span) } Expression::Binary { left, right, op } => { let left = self.check_and_get(left)?; let right = self.check_and_get(right)?; self.binary_op(op, left, right, span) } Expression::Math { fun, arg, arg1, arg2, arg3, } => { let arg = self.check_and_get(arg)?; let arg1 = arg1.map(|arg| self.check_and_get(arg)).transpose()?; let arg2 = arg2.map(|arg| self.check_and_get(arg)).transpose()?; let arg3 = arg3.map(|arg| self.check_and_get(arg)).transpose()?; self.math(arg, arg1, arg2, arg3, fun, span) } Expression::As { convert, expr, kind, } => { let expr = self.check_and_get(expr)?; match convert { Some(width) => self.cast(expr, crate::Scalar { kind, width }, span), None => Err(ConstantEvaluatorError::NotImplemented( "bitcast built-in function".into(), )), } } Expression::Select { reject, accept, condition, } => { let mut arg = |expr| self.check_and_get(expr); let reject = arg(reject)?; let accept = arg(accept)?; let condition = arg(condition)?; self.select(reject, accept, condition, span) } Expression::Relational { fun, argument } => { let argument = self.check_and_get(argument)?; self.relational(fun, argument, span) } Expression::ArrayLength(expr) => match self.behavior { Behavior::Wgsl(_) => Err(ConstantEvaluatorError::ArrayLength), Behavior::Glsl(_) => { let expr = self.check_and_get(expr)?; self.array_length(expr, span) } }, Expression::Load { .. } => Err(ConstantEvaluatorError::Load), Expression::LocalVariable(_) => Err(ConstantEvaluatorError::LocalVariable), Expression::Derivative { .. } => Err(ConstantEvaluatorError::Derivative), Expression::CallResult { .. } => Err(ConstantEvaluatorError::Call), Expression::WorkGroupUniformLoadResult { .. } => { Err(ConstantEvaluatorError::WorkGroupUniformLoadResult) } Expression::AtomicResult { .. } => Err(ConstantEvaluatorError::Atomic), Expression::FunctionArgument(_) => Err(ConstantEvaluatorError::FunctionArg), Expression::GlobalVariable(_) => Err(ConstantEvaluatorError::GlobalVariable), Expression::ImageSample { .. } | Expression::ImageLoad { .. } | Expression::ImageQuery { .. } => Err(ConstantEvaluatorError::ImageExpression), Expression::RayQueryProceedResult | Expression::RayQueryGetIntersection { .. } | Expression::RayQueryVertexPositions { .. } => { Err(ConstantEvaluatorError::RayQueryExpression) } Expression::SubgroupBallotResult => Err(ConstantEvaluatorError::SubgroupExpression), Expression::SubgroupOperationResult { .. } => { Err(ConstantEvaluatorError::SubgroupExpression) } Expression::CooperativeLoad { .. } | Expression::CooperativeMultiplyAdd { .. } => { Err(ConstantEvaluatorError::CooperativeOperation) } } } /// Splat `value` to `size`, without using [`Splat`] expressions. /// /// This constructs [`Compose`] or [`ZeroValue`] expressions to /// build a vector with the given `size` whose components are all /// `value`. /// /// Use `span` as the span of the inserted expressions and /// resulting types. /// /// [`Splat`]: Expression::Splat /// [`Compose`]: Expression::Compose /// [`ZeroValue`]: Expression::ZeroValue fn splat( &mut self, value: Handle, size: crate::VectorSize, span: Span, ) -> Result, ConstantEvaluatorError> { match self.expressions[value] { Expression::Literal(literal) => { let scalar = literal.scalar(); let ty = self.types.insert( Type { name: None, inner: TypeInner::Vector { size, scalar }, }, span, ); let expr = Expression::Compose { ty, components: vec![value; size as usize], }; self.register_evaluated_expr(expr, span) } Expression::ZeroValue(ty) => { let inner = match self.types[ty].inner { TypeInner::Scalar(scalar) => TypeInner::Vector { size, scalar }, _ => return Err(ConstantEvaluatorError::SplatScalarOnly), }; let res_ty = self.types.insert(Type { name: None, inner }, span); let expr = Expression::ZeroValue(res_ty); self.register_evaluated_expr(expr, span) } _ => Err(ConstantEvaluatorError::SplatScalarOnly), } } fn swizzle( &mut self, size: crate::VectorSize, span: Span, src_constant: Handle, pattern: [crate::SwizzleComponent; 4], ) -> Result, ConstantEvaluatorError> { let mut get_dst_ty = |ty| match self.types[ty].inner { TypeInner::Vector { size: _, scalar } => Ok(self.types.insert( Type { name: None, inner: TypeInner::Vector { size, scalar }, }, span, )), _ => Err(ConstantEvaluatorError::SwizzleVectorOnly), }; match self.expressions[src_constant] { Expression::ZeroValue(ty) => { let dst_ty = get_dst_ty(ty)?; let expr = Expression::ZeroValue(dst_ty); self.register_evaluated_expr(expr, span) } Expression::Splat { value, .. } => { let expr = Expression::Splat { size, value }; self.register_evaluated_expr(expr, span) } Expression::Compose { ty, ref components } => { let dst_ty = get_dst_ty(ty)?; let mut flattened = [src_constant; 4]; // dummy value let len = crate::proc::flatten_compose(ty, components, self.expressions, self.types) .zip(flattened.iter_mut()) .map(|(component, elt)| *elt = component) .count(); let flattened = &flattened[..len]; let swizzled_components = pattern[..size as usize] .iter() .map(|&sc| { let sc = sc as usize; if let Some(elt) = flattened.get(sc) { Ok(*elt) } else { Err(ConstantEvaluatorError::SwizzleOutOfBounds) } }) .collect::>, _>>()?; let expr = Expression::Compose { ty: dst_ty, components: swizzled_components, }; self.register_evaluated_expr(expr, span) } _ => Err(ConstantEvaluatorError::SwizzleVectorOnly), } } fn math( &mut self, arg: Handle, arg1: Option>, arg2: Option>, arg3: Option>, fun: crate::MathFunction, span: Span, ) -> Result, ConstantEvaluatorError> { let expected = fun.argument_count(); let given = Some(arg) .into_iter() .chain(arg1) .chain(arg2) .chain(arg3) .count(); if expected != given { return Err(ConstantEvaluatorError::InvalidMathArgCount( fun, expected, given, )); } // NOTE: We try to match the declaration order of `MathFunction` here. match fun { // comparison crate::MathFunction::Abs => { component_wise_scalar(self, span, [arg], |args| match args { Scalar::AbstractFloat([e]) => Ok(Scalar::AbstractFloat([e.abs()])), Scalar::F32([e]) => Ok(Scalar::F32([e.abs()])), Scalar::F16([e]) => Ok(Scalar::F16([e.abs()])), Scalar::AbstractInt([e]) => Ok(Scalar::AbstractInt([e.wrapping_abs()])), Scalar::I32([e]) => Ok(Scalar::I32([e.wrapping_abs()])), Scalar::U32([e]) => Ok(Scalar::U32([e])), // TODO: just re-use the expression, ezpz Scalar::I64([e]) => Ok(Scalar::I64([e.wrapping_abs()])), Scalar::U64([e]) => Ok(Scalar::U64([e])), }) } crate::MathFunction::Min => { component_wise_scalar!(self, span, [arg, arg1.unwrap()], |e1, e2| { Ok([e1.min(e2)]) }) } crate::MathFunction::Max => { component_wise_scalar!(self, span, [arg, arg1.unwrap()], |e1, e2| { Ok([e1.max(e2)]) }) } crate::MathFunction::Clamp => { component_wise_scalar!( self, span, [arg, arg1.unwrap(), arg2.unwrap()], |e, low, high| { if low > high { Err(ConstantEvaluatorError::InvalidClamp) } else { Ok([e.clamp(low, high)]) } } ) } crate::MathFunction::Saturate => component_wise_float(self, span, [arg], |e| match e { Float::F16([e]) => Ok(Float::F16( [e.clamp(f16::from_f32(0.0), f16::from_f32(1.0))], )), Float::F32([e]) => Ok(Float::F32([e.clamp(0., 1.)])), Float::Abstract([e]) => Ok(Float::Abstract([e.clamp(0., 1.)])), }), // trigonometry crate::MathFunction::Cos => { component_wise_float!(self, span, [arg], |e| { Ok([e.cos()]) }) } crate::MathFunction::Cosh => { component_wise_float!(self, span, [arg], |e| { let result = e.cosh(); if result.is_finite() { Ok([result]) } else { Err(ConstantEvaluatorError::Overflow("cosh".into())) } }) } crate::MathFunction::Sin => { component_wise_float!(self, span, [arg], |e| { Ok([e.sin()]) }) } crate::MathFunction::Sinh => { component_wise_float!(self, span, [arg], |e| { let result = e.sinh(); if result.is_finite() { Ok([result]) } else { Err(ConstantEvaluatorError::Overflow("sinh".into())) } }) } crate::MathFunction::Tan => { component_wise_float!(self, span, [arg], |e| { Ok([e.tan()]) }) } crate::MathFunction::Tanh => { component_wise_float!(self, span, [arg], |e| { Ok([e.tanh()]) }) } crate::MathFunction::Acos => { component_wise_float!(self, span, [arg], |e| { if e.abs() <= One::one() { Ok([e.acos()]) } else { Err(ConstantEvaluatorError::InvalidMathArgValue("acos".into())) } }) } crate::MathFunction::Asin => { component_wise_float!(self, span, [arg], |e| { if e.abs() <= One::one() { Ok([e.asin()]) } else { Err(ConstantEvaluatorError::InvalidMathArgValue("asin".into())) } }) } crate::MathFunction::Atan => { component_wise_float!(self, span, [arg], |e| { Ok([e.atan()]) }) } crate::MathFunction::Atan2 => { component_wise_float!(self, span, [arg, arg1.unwrap()], |y, x| { Ok([y.atan2(x)]) }) } crate::MathFunction::Asinh => component_wise_float(self, span, [arg], |e| match e { Float::Abstract([e]) => Ok(Float::Abstract([libm::asinh(e)])), Float::F32([e]) => Ok(Float::F32([(e as f64).asinh() as f32])), Float::F16([e]) => Ok(Float::F16([e.asinh()])), }), crate::MathFunction::Acosh => { component_wise_float!(self, span, [arg], |e| { Ok([e.acosh()]) }) } crate::MathFunction::Atanh => { component_wise_float!(self, span, [arg], |e| { if e.abs() < One::one() { Ok([e.atanh()]) } else { Err(ConstantEvaluatorError::InvalidMathArgValue("atanh".into())) } }) } crate::MathFunction::Radians => { component_wise_float!(self, span, [arg], |e1| { Ok([e1.to_radians()]) }) } crate::MathFunction::Degrees => { component_wise_float!(self, span, [arg], |e| { let result = e.to_degrees(); if result.is_finite() { Ok([result]) } else { Err(ConstantEvaluatorError::Overflow("degrees".into())) } }) } // decomposition crate::MathFunction::Ceil => { component_wise_float!(self, span, [arg], |e| { Ok([e.ceil()]) }) } crate::MathFunction::Floor => { component_wise_float!(self, span, [arg], |e| { Ok([e.floor()]) }) } crate::MathFunction::Round => { component_wise_float(self, span, [arg], |e| match e { Float::Abstract([e]) => Ok(Float::Abstract([libm::rint(e)])), Float::F32([e]) => Ok(Float::F32([libm::rintf(e)])), Float::F16([e]) => { // TODO: `round_ties_even` is not available on `half::f16` yet. // // This polyfill is shamelessly [~~stolen from~~ inspired by `ndarray-image`][polyfill source], // which has licensing compatible with ours. See also // . // // [polyfill source]: https://github.com/imeka/ndarray-ndimage/blob/8b14b4d6ecfbc96a8a052f802e342a7049c68d8f/src/lib.rs#L98 fn round_ties_even(x: f64) -> f64 { let i = x as i64; let f = (x - i as f64).abs(); if f == 0.5 { if i & 1 == 1 { // -1.5, 1.5, 3.5, ... (x.abs() + 0.5).copysign(x) } else { (x.abs() - 0.5).copysign(x) } } else { x.round() } } Ok(Float::F16([(f16::from_f64(round_ties_even(f64::from(e))))])) } }) } crate::MathFunction::Fract => { component_wise_float!(self, span, [arg], |e| { // N.B., Rust's definition of `fract` is `e - e.trunc()`, so we can't use that // here. Ok([e - e.floor()]) }) } crate::MathFunction::Trunc => { component_wise_float!(self, span, [arg], |e| { Ok([e.trunc()]) }) } // exponent crate::MathFunction::Exp => { component_wise_float!(self, span, [arg], |e| { let result = e.exp(); if result.is_finite() { Ok([result]) } else { Err(ConstantEvaluatorError::Overflow("exp".into())) } }) } crate::MathFunction::Exp2 => { component_wise_float!(self, span, [arg], |e| { let result = e.exp2(); if result.is_finite() { Ok([result]) } else { Err(ConstantEvaluatorError::Overflow("exp2".into())) } }) } crate::MathFunction::Log => { component_wise_float!(self, span, [arg], |e| { if e > Zero::zero() { Ok([e.ln()]) } else { Err(ConstantEvaluatorError::InvalidMathArgValue("log".into())) } }) } crate::MathFunction::Log2 => { component_wise_float!(self, span, [arg], |e| { if e > Zero::zero() { Ok([e.log2()]) } else { Err(ConstantEvaluatorError::InvalidMathArgValue("log2".into())) } }) } crate::MathFunction::Pow => { component_wise_float!(self, span, [arg, arg1.unwrap()], |e1, e2| { Ok([e1.powf(e2)]) }) } // computational crate::MathFunction::Sign => { component_wise_signed!(self, span, [arg], |e| { Ok([if e.is_zero() { Zero::zero() } else { e.signum() }]) }) } crate::MathFunction::Fma => { component_wise_float!( self, span, [arg, arg1.unwrap(), arg2.unwrap()], |e1, e2, e3| { Ok([e1.mul_add(e2, e3)]) } ) } crate::MathFunction::Step => { component_wise_float(self, span, [arg, arg1.unwrap()], |x| match x { Float::Abstract([edge, x]) => { Ok(Float::Abstract([if edge <= x { 1.0 } else { 0.0 }])) } Float::F32([edge, x]) => Ok(Float::F32([if edge <= x { 1.0 } else { 0.0 }])), Float::F16([edge, x]) => Ok(Float::F16([if edge <= x { f16::one() } else { f16::zero() }])), }) } crate::MathFunction::Sqrt => { component_wise_float!(self, span, [arg], |e| { if e >= Zero::zero() { Ok([e.sqrt()]) } else { Err(ConstantEvaluatorError::InvalidMathArgValue("sqrt".into())) } }) } crate::MathFunction::InverseSqrt => { component_wise_float(self, span, [arg], |e| match e { Float::Abstract([e]) => Ok(Float::Abstract([1. / e.sqrt()])), Float::F32([e]) => Ok(Float::F32([1. / e.sqrt()])), Float::F16([e]) => Ok(Float::F16([f16::from_f32(1. / f32::from(e).sqrt())])), }) } // bits crate::MathFunction::CountTrailingZeros => { component_wise_concrete_int!(self, span, [arg], |e| { #[allow(clippy::useless_conversion)] Ok([e .trailing_zeros() .try_into() .expect("bit count overflowed 32 bits, somehow!?")]) }) } crate::MathFunction::CountLeadingZeros => { component_wise_concrete_int!(self, span, [arg], |e| { #[allow(clippy::useless_conversion)] Ok([e .leading_zeros() .try_into() .expect("bit count overflowed 32 bits, somehow!?")]) }) } crate::MathFunction::CountOneBits => { component_wise_concrete_int!(self, span, [arg], |e| { #[allow(clippy::useless_conversion)] Ok([e .count_ones() .try_into() .expect("bit count overflowed 32 bits, somehow!?")]) }) } crate::MathFunction::ReverseBits => { component_wise_concrete_int!(self, span, [arg], |e| { Ok([e.reverse_bits()]) }) } crate::MathFunction::FirstTrailingBit => { component_wise_concrete_int(self, span, [arg], |ci| Ok(first_trailing_bit(ci))) } crate::MathFunction::FirstLeadingBit => { component_wise_concrete_int(self, span, [arg], |ci| Ok(first_leading_bit(ci))) } // vector crate::MathFunction::Dot4I8Packed => { self.packed_dot_product(arg, arg1.unwrap(), span, true) } crate::MathFunction::Dot4U8Packed => { self.packed_dot_product(arg, arg1.unwrap(), span, false) } crate::MathFunction::Cross => self.cross_product(arg, arg1.unwrap(), span), crate::MathFunction::Dot => { // https://www.w3.org/TR/WGSL/#dot-builtin let e1 = self.extract_vec(arg, false)?; let e2 = self.extract_vec(arg1.unwrap(), false)?; if e1.len() != e2.len() { return Err(ConstantEvaluatorError::InvalidMathArg); } fn float_dot_checked

(a: &[P], b: &[P]) -> Result where P: num_traits::Float, { let result = a .iter() .zip(b.iter()) .map(|(&aa, &bb)| aa * bb) .fold(P::zero(), |acc, x| acc + x); if result.is_finite() { Ok(result) } else { Err(ConstantEvaluatorError::Overflow("in dot built-in".into())) } } fn int_dot_checked

(a: &[P], b: &[P]) -> Result where P: num_traits::PrimInt + num_traits::CheckedAdd + num_traits::CheckedMul, { a.iter() .zip(b.iter()) .map(|(&aa, bb)| aa.checked_mul(bb)) .try_fold(P::zero(), |acc, x| { if let Some(x) = x { acc.checked_add(&x) } else { None } }) .ok_or(ConstantEvaluatorError::Overflow( "in dot built-in".to_string(), )) } fn int_dot_wrapping

#[must_use] pub fn create_shader_module(&self, desc: ShaderModuleDescriptor<'_>) -> ShaderModule { let module = self .inner .create_shader_module(desc, wgt::ShaderRuntimeChecks::checked()); ShaderModule { inner: module } } /// Deprecated: Use [`create_shader_module_trusted`][csmt] instead. /// /// # Safety /// /// See [`create_shader_module_trusted`][csmt]. /// /// [csmt]: Self::create_shader_module_trusted #[deprecated( since = "24.0.0", note = "Use `Device::create_shader_module_trusted(desc, wgpu::ShaderRuntimeChecks::unchecked())` instead." )] #[must_use] pub unsafe fn create_shader_module_unchecked( &self, desc: ShaderModuleDescriptor<'_>, ) -> ShaderModule { unsafe { self.create_shader_module_trusted(desc, crate::ShaderRuntimeChecks::unchecked()) } } /// Creates a shader module with flags to dictate runtime checks. /// /// When running on WebGPU, this will merely call [`create_shader_module`][csm]. /// /// # Safety /// /// In contrast with [`create_shader_module`][csm] this function /// creates a shader module with user-customizable runtime checks which allows shaders to /// perform operations which can lead to undefined behavior like indexing out of bounds, /// thus it's the caller responsibility to pass a shader which doesn't perform any of this /// operations. /// /// See the documentation for [`ShaderRuntimeChecks`] for more information about specific checks. /// /// [csm]: Self::create_shader_module #[must_use] pub unsafe fn create_shader_module_trusted( &self, desc: ShaderModuleDescriptor<'_>, runtime_checks: crate::ShaderRuntimeChecks, ) -> ShaderModule { let module = self.inner.create_shader_module(desc, runtime_checks); ShaderModule { inner: module } } /// Creates a shader module which will bypass wgpu's shader tooling and validation and be used directly by the backend. /// /// # Safety /// /// This function passes data to the backend as-is and can potentially result in a /// driver crash or bogus behaviour. No attempt is made to ensure that data is valid. #[must_use] pub unsafe fn create_shader_module_passthrough( &self, desc: ShaderModuleDescriptorPassthrough<'_>, ) -> ShaderModule { let module = unsafe { self.inner.create_shader_module_passthrough(&desc) }; ShaderModule { inner: module } } /// Creates an empty [`CommandEncoder`]. #[must_use] pub fn create_command_encoder(&self, desc: &CommandEncoderDescriptor<'_>) -> CommandEncoder { let encoder = self.inner.create_command_encoder(desc); // Each encoder starts with its own deferred-action store that travels // with the CommandBuffer produced by finish(). CommandEncoder { inner: encoder, actions: Default::default(), } } /// Creates an empty [`RenderBundleEncoder`]. #[must_use] pub fn create_render_bundle_encoder<'a>( &self, desc: &RenderBundleEncoderDescriptor<'_>, ) -> RenderBundleEncoder<'a> { let encoder = self.inner.create_render_bundle_encoder(desc); RenderBundleEncoder { inner: encoder, _p: PhantomData, } } /// Creates a new [`BindGroup`]. #[must_use] pub fn create_bind_group(&self, desc: &BindGroupDescriptor<'_>) -> BindGroup { let group = self.inner.create_bind_group(desc); BindGroup { inner: group } } /// Creates a [`BindGroupLayout`]. #[must_use] pub fn create_bind_group_layout( &self, desc: &BindGroupLayoutDescriptor<'_>, ) -> BindGroupLayout { let layout = self.inner.create_bind_group_layout(desc); BindGroupLayout { inner: layout } } /// Creates a [`PipelineLayout`]. #[must_use] pub fn create_pipeline_layout(&self, desc: &PipelineLayoutDescriptor<'_>) -> PipelineLayout { let layout = self.inner.create_pipeline_layout(desc); PipelineLayout { inner: layout } } /// Creates a [`RenderPipeline`]. #[must_use] pub fn create_render_pipeline(&self, desc: &RenderPipelineDescriptor<'_>) -> RenderPipeline { let pipeline = self.inner.create_render_pipeline(desc); RenderPipeline { inner: pipeline } } /// Creates a mesh shader based [`RenderPipeline`]. #[must_use] pub fn create_mesh_pipeline(&self, desc: &MeshPipelineDescriptor<'_>) -> RenderPipeline { let pipeline = self.inner.create_mesh_pipeline(desc); RenderPipeline { inner: pipeline } } /// Creates a [`ComputePipeline`]. #[must_use] pub fn create_compute_pipeline(&self, desc: &ComputePipelineDescriptor<'_>) -> ComputePipeline { let pipeline = self.inner.create_compute_pipeline(desc); ComputePipeline { inner: pipeline } } /// Creates a [`Buffer`]. #[must_use] pub fn create_buffer(&self, desc: &BufferDescriptor<'_>) -> Buffer { let map_context = MapContext::new(desc.mapped_at_creation.then_some(0..desc.size)); let buffer = self.inner.create_buffer(desc); Buffer { inner: buffer, map_context: Arc::new(Mutex::new(map_context)), size: desc.size, usage: desc.usage, } } /// Creates a new [`Texture`]. /// /// `desc` specifies the general format of the texture. #[must_use] pub fn create_texture(&self, desc: &TextureDescriptor<'_>) -> Texture { let texture = self.inner.create_texture(desc); Texture { inner: texture, descriptor: TextureDescriptor { label: None, view_formats: &[], ..desc.clone() }, } } /// Creates a [`Texture`] from a wgpu-hal Texture. /// /// # Types /// /// The type of `A::Texture` depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("Texture")] #[doc = crate::macros::hal_type_metal!("Texture")] #[doc = crate::macros::hal_type_dx12!("Texture")] #[doc = crate::macros::hal_type_gles!("Texture")] /// /// # Safety /// /// - `hal_texture` must be created from this device internal handle /// - `hal_texture` must be created respecting `desc` /// - `hal_texture` must be initialized #[cfg(wgpu_core)] #[must_use] pub unsafe fn create_texture_from_hal( &self, hal_texture: A::Texture, desc: &TextureDescriptor<'_>, ) -> Texture { let texture = unsafe { let core_device = self.inner.as_core(); core_device .context .create_texture_from_hal::(hal_texture, core_device, desc) }; Texture { inner: texture.into(), descriptor: TextureDescriptor { label: None, view_formats: &[], ..desc.clone() }, } } /// Creates a new [`ExternalTexture`]. #[must_use] pub fn create_external_texture( &self, desc: &ExternalTextureDescriptor<'_>, planes: &[&TextureView], ) -> ExternalTexture { let external_texture = self.inner.create_external_texture(desc, planes); ExternalTexture { inner: external_texture, } } /// Creates a [`Buffer`] from a wgpu-hal Buffer. /// /// # Types /// /// The type of `A::Buffer` depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("Buffer")] #[doc = crate::macros::hal_type_metal!("Buffer")] #[doc = crate::macros::hal_type_dx12!("Buffer")] #[doc = crate::macros::hal_type_gles!("Buffer")] /// /// # Safety /// /// - `hal_buffer` must be created from this device internal handle /// - `hal_buffer` must be created respecting `desc` /// - `hal_buffer` must be initialized /// - `hal_buffer` must not have zero size #[cfg(wgpu_core)] #[must_use] pub unsafe fn create_buffer_from_hal( &self, hal_buffer: A::Buffer, desc: &BufferDescriptor<'_>, ) -> Buffer { let map_context = MapContext::new(desc.mapped_at_creation.then_some(0..desc.size)); let buffer = unsafe { let core_device = self.inner.as_core(); core_device .context .create_buffer_from_hal::(hal_buffer, core_device, desc) }; Buffer { inner: buffer.into(), map_context: Arc::new(Mutex::new(map_context)), size: desc.size, usage: desc.usage, } } /// Creates a new [`Sampler`]. /// /// `desc` specifies the behavior of the sampler. #[must_use] pub fn create_sampler(&self, desc: &SamplerDescriptor<'_>) -> Sampler { let sampler = self.inner.create_sampler(desc); Sampler { inner: sampler } } /// Creates a new [`QuerySet`]. #[must_use] pub fn create_query_set(&self, desc: &QuerySetDescriptor<'_>) -> QuerySet { let query_set = self.inner.create_query_set(desc); QuerySet { inner: query_set } } /// Set a callback which will be called for all errors that are not handled in error scopes. pub fn on_uncaptured_error(&self, handler: Arc) { self.inner.on_uncaptured_error(handler) } /// Push an error scope on this device's thread-local error scope /// stack. All operations on this device, or on resources created /// from this device, will have their errors captured by this scope /// until the scope is popped. /// /// Scopes must be popped in reverse order to their creation. If /// a guard is dropped without being `pop()`ped, the scope will be /// popped, and the captured errors will be dropped. /// /// Multiple error scopes may be active at one time, forming a stack. /// Each error will be reported to the inner-most scope that matches /// its filter. /// /// With the `std` feature enabled, this stack is **thread-local**. /// Without, this is **global** to all threads. /// /// ```rust /// # async move { /// # let device: wgpu::Device = unreachable!(); /// let error_scope = device.push_error_scope(wgpu::ErrorFilter::Validation); /// /// // ... /// // do work that may produce validation errors /// // ... /// /// // pop the error scope and get a future for the result /// let error_future = error_scope.pop(); /// /// // await the future to get the error, if any /// let error = error_future.await; /// # }; /// ``` pub fn push_error_scope(&self, filter: ErrorFilter) -> ErrorScopeGuard { let index = self.inner.push_error_scope(filter); ErrorScopeGuard { device: self.inner.clone(), index, popped: false, _phantom: PhantomData, } } /// Starts a capture in the attached graphics debugger. /// /// This behaves differently depending on which graphics debugger is attached: /// /// - Renderdoc: Calls [`StartFrameCapture(device, NULL)`][rd]. /// - Xcode: Creates a capture with [`MTLCaptureManager`][xcode]. /// - None: No action is taken. /// /// # Safety /// /// - There should not be any other captures currently active. /// - All other safety rules are defined by the graphics debugger, see the /// documentation for the specific debugger. /// - In general, graphics debuggers can easily cause crashes, so this isn't /// ever guaranteed to be sound. /// /// # Tips /// /// - Debuggers need to capture both the recording of the commands and the /// submission of the commands to the GPU. Try to wrap all of your /// gpu work in a capture. /// - If you encounter issues, try waiting for the GPU to finish all work /// before stopping the capture. /// /// [rd]: https://renderdoc.org/docs/in_application_api.html#_CPPv417StartFrameCapture23RENDERDOC_DevicePointer22RENDERDOC_WindowHandle /// [xcode]: https://developer.apple.com/documentation/metal/mtlcapturemanager #[doc(alias = "start_renderdoc_capture")] #[doc(alias = "start_xcode_capture")] pub unsafe fn start_graphics_debugger_capture(&self) { unsafe { self.inner.start_graphics_debugger_capture() } } /// Stops the current capture in the attached graphics debugger. /// /// This behaves differently depending on which graphics debugger is attached: /// /// - Renderdoc: Calls [`EndFrameCapture(device, NULL)`][rd]. /// - Xcode: Stops the capture with [`MTLCaptureManager`][xcode]. /// - None: No action is taken. /// /// # Safety /// /// - There should be a capture currently active. /// - All other safety rules are defined by the graphics debugger, see the /// documentation for the specific debugger. /// - In general, graphics debuggers can easily cause crashes, so this isn't /// ever guaranteed to be sound. /// /// # Tips /// /// - If you encounter issues, try to submit all work to the GPU, and waiting /// for that work to finish before stopping the capture. /// /// [rd]: https://renderdoc.org/docs/in_application_api.html#_CPPv415EndFrameCapture23RENDERDOC_DevicePointer22RENDERDOC_WindowHandle /// [xcode]: https://developer.apple.com/documentation/metal/mtlcapturemanager #[doc(alias = "stop_renderdoc_capture")] #[doc(alias = "stop_xcode_capture")] pub unsafe fn stop_graphics_debugger_capture(&self) { unsafe { self.inner.stop_graphics_debugger_capture() } } /// Query internal counters from the native backend for debugging purposes. /// /// Some backends may not set all counters, or may not set any counter at all. /// The `counters` cargo feature must be enabled for any counter to be set. /// /// If a counter is not set, its contains its default value (zero). #[must_use] pub fn get_internal_counters(&self) -> wgt::InternalCounters { self.inner.get_internal_counters() } /// Generate an GPU memory allocation report if the underlying backend supports it. /// /// Backends that do not support producing these reports return `None`. A backend may /// Support it and still return `None` if it is not using performing sub-allocation, /// for example as a workaround for driver issues. #[must_use] pub fn generate_allocator_report(&self) -> Option { self.inner.generate_allocator_report() } /// Get the [`wgpu_hal`] device from this `Device`. /// /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`], /// and pass that struct to the to the `A` type parameter. /// /// Returns a guard that dereferences to the type of the hal backend /// which implements [`A::Device`]. /// /// # Types /// /// The returned type depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("Device")] #[doc = crate::macros::hal_type_metal!("Device")] #[doc = crate::macros::hal_type_dx12!("Device")] #[doc = crate::macros::hal_type_gles!("Device")] /// /// # Errors /// /// This method will return None if: /// - The device is not from the backend specified by `A`. /// - The device is from the `webgpu` or `custom` backend. /// /// # Safety /// /// - The returned resource must not be destroyed unless the guard /// is the last reference to it and it is not in use by the GPU. /// The guard and handle may be dropped at any time however. /// - All the safety requirements of wgpu-hal must be upheld. /// /// [`A::Device`]: hal::Api::Device #[cfg(wgpu_core)] pub unsafe fn as_hal( &self, ) -> Option + WasmNotSendSync> { let device = self.inner.as_core_opt()?; unsafe { device.context.device_as_hal::(device) } } /// Destroy this device. pub fn destroy(&self) { self.inner.destroy() } /// Set a DeviceLostCallback on this device. pub fn set_device_lost_callback( &self, callback: impl Fn(DeviceLostReason, String) + Send + 'static, ) { self.inner.set_device_lost_callback(Box::new(callback)) } /// Create a [`PipelineCache`] with initial data /// /// This can be passed to [`Device::create_compute_pipeline`] /// and [`Device::create_render_pipeline`] to either accelerate these /// or add the cache results from those. /// /// # Safety /// /// If the `data` field of `desc` is set, it must have previously been returned from a call /// to [`PipelineCache::get_data`][^saving]. This `data` will only be used if it came /// from an adapter with the same [`util::pipeline_cache_key`]. /// This *is* compatible across wgpu versions, as any data format change will /// be accounted for. /// /// It is *not* supported to bring caches from previous direct uses of backend APIs /// into this method. /// /// # Errors /// /// Returns an error value if: /// * the [`PIPELINE_CACHE`](wgt::Features::PIPELINE_CACHE) feature is not enabled /// * this device is invalid; or /// * the device is out of memory /// /// This method also returns an error value if: /// * The `fallback` field on `desc` is false; and /// * the `data` provided would not be used[^data_not_used] /// /// If an error value is used in subsequent calls, default caching will be used. /// /// [^saving]: We do recognise that saving this data to disk means this condition /// is impossible to fully prove. Consider the risks for your own application in this case. /// /// [^data_not_used]: This data may be not used if: the data was produced by a prior /// version of wgpu; or was created for an incompatible adapter, or there was a GPU driver /// update. In some cases, the data might not be used and a real value is returned, /// this is left to the discretion of GPU drivers. #[must_use] pub unsafe fn create_pipeline_cache( &self, desc: &PipelineCacheDescriptor<'_>, ) -> PipelineCache { let cache = unsafe { self.inner.create_pipeline_cache(desc) }; PipelineCache { inner: cache } } } /// [`Features::EXPERIMENTAL_RAY_QUERY`] must be enabled on the device in order to call these functions. impl Device { /// Create a bottom level acceleration structure, used inside a top level acceleration structure for ray tracing. /// - `desc`: The descriptor of the acceleration structure. /// - `sizes`: Size descriptor limiting what can be built into the acceleration structure. /// /// # Validation /// If any of the following is not satisfied a validation error is generated /// /// The device ***must*** have [`Features::EXPERIMENTAL_RAY_QUERY`] enabled. /// if `sizes` is [`BlasGeometrySizeDescriptors::Triangles`] then the following must be satisfied /// - For every geometry descriptor (for the purposes this is called `geo_desc`) of `sizes.descriptors` the following must be satisfied: /// - `geo_desc.vertex_format` must be within allowed formats (allowed formats for a given feature set /// may be queried with [`Features::allowed_vertex_formats_for_blas`]). /// - Both or neither of `geo_desc.index_format` and `geo_desc.index_count` must be provided. /// /// [`Features::EXPERIMENTAL_RAY_QUERY`]: wgt::Features::EXPERIMENTAL_RAY_QUERY /// [`Features::allowed_vertex_formats_for_blas`]: wgt::Features::allowed_vertex_formats_for_blas #[must_use] pub fn create_blas( &self, desc: &CreateBlasDescriptor<'_>, sizes: BlasGeometrySizeDescriptors, ) -> Blas { let (handle, blas) = self.inner.create_blas(desc, sizes); Blas { inner: blas, handle, } } /// Create a top level acceleration structure, used for ray tracing. /// - `desc`: The descriptor of the acceleration structure. /// /// # Validation /// If any of the following is not satisfied a validation error is generated /// /// The device ***must*** have [`Features::EXPERIMENTAL_RAY_QUERY`] enabled. /// /// [`Features::EXPERIMENTAL_RAY_QUERY`]: wgt::Features::EXPERIMENTAL_RAY_QUERY #[must_use] pub fn create_tlas(&self, desc: &CreateTlasDescriptor<'_>) -> Tlas { let tlas = self.inner.create_tlas(desc); Tlas { inner: tlas, instances: vec![None; desc.max_instances as usize], lowest_unmodified: 0, } } } /// Requesting a device from an [`Adapter`] failed. #[derive(Clone, Debug)] pub struct RequestDeviceError { pub(crate) inner: RequestDeviceErrorKind, } #[derive(Clone, Debug)] pub(crate) enum RequestDeviceErrorKind { /// Error from [`wgpu_core`]. // must match dependency cfg #[cfg(wgpu_core)] Core(wgc::instance::RequestDeviceError), /// Error from web API that was called by `wgpu` to request a device. /// /// (This is currently never used by the webgl backend, but it could be.) #[cfg(webgpu)] WebGpu(String), } static_assertions::assert_impl_all!(RequestDeviceError: Send, Sync); impl fmt::Display for RequestDeviceError { fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.inner { #[cfg(wgpu_core)] RequestDeviceErrorKind::Core(error) => error.fmt(_f), #[cfg(webgpu)] RequestDeviceErrorKind::WebGpu(error) => { write!(_f, "{error}") } #[cfg(not(any(webgpu, wgpu_core)))] _ => unimplemented!("unknown `RequestDeviceErrorKind`"), } } } impl error::Error for RequestDeviceError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match &self.inner { #[cfg(wgpu_core)] RequestDeviceErrorKind::Core(error) => error.source(), #[cfg(webgpu)] RequestDeviceErrorKind::WebGpu(_) => None, #[cfg(not(any(webgpu, wgpu_core)))] _ => unimplemented!("unknown `RequestDeviceErrorKind`"), } } } #[cfg(wgpu_core)] impl From for RequestDeviceError { fn from(error: wgc::instance::RequestDeviceError) -> Self { Self { inner: RequestDeviceErrorKind::Core(error), } } } /// The callback of [`Device::on_uncaptured_error()`]. /// /// It must be a function with this signature. pub trait UncapturedErrorHandler: Fn(Error) + Send + Sync + 'static {} impl UncapturedErrorHandler for T where T: Fn(Error) + Send + Sync + 'static {} /// Kinds of [`Error`]s a [`Device::push_error_scope()`] may be configured to catch. #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)] pub enum ErrorFilter { /// Catch only out-of-memory errors. OutOfMemory, /// Catch only validation errors. Validation, /// Catch only internal errors. Internal, } static_assertions::assert_impl_all!(ErrorFilter: Send, Sync); /// Lower level source of the error. /// /// `Send + Sync` varies depending on configuration. #[cfg(send_sync)] #[cfg_attr(docsrs, doc(cfg(all())))] pub type ErrorSource = Box; /// Lower level source of the error. /// /// `Send + Sync` varies depending on configuration. #[cfg(not(send_sync))] #[cfg_attr(docsrs, doc(cfg(all())))] pub type ErrorSource = Box; /// Errors resulting from usage of GPU APIs. /// /// By default, errors translate into panics. Depending on the backend and circumstances, /// errors may occur synchronously or asynchronously. When errors need to be handled, use /// [`Device::push_error_scope()`] or [`Device::on_uncaptured_error()`]. #[derive(Debug)] pub enum Error { /// Out of memory. OutOfMemory { /// Lower level source of the error. source: ErrorSource, }, /// Validation error, signifying a bug in code or data provided to `wgpu`. Validation { /// Lower level source of the error. source: ErrorSource, /// Description of the validation error. description: String, }, /// Internal error. Used for signalling any failures not explicitly expected by WebGPU. /// /// These could be due to internal implementation or system limits being reached. Internal { /// Lower level source of the error. source: ErrorSource, /// Description of the internal GPU error. description: String, }, } #[cfg(send_sync)] static_assertions::assert_impl_all!(Error: Send, Sync); impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { Error::OutOfMemory { source } => Some(source.as_ref()), Error::Validation { source, .. } => Some(source.as_ref()), Error::Internal { source, .. } => Some(source.as_ref()), } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::OutOfMemory { .. } => f.write_str("Out of Memory"), Error::Validation { description, .. } => f.write_str(description), Error::Internal { description, .. } => f.write_str(description), } } } /// Guard for an error scope pushed with [`Device::push_error_scope()`]. /// /// Call [`pop()`] to pop the scope and get a future for the result. If /// the guard is dropped without being popped explicitly, the scope will still be popped, /// and the captured errors will be dropped. /// /// This guard is neither `Send` nor `Sync`, as error scopes are handled /// on a per-thread basis when the `std` feature is enabled. /// /// [`pop()`]: ErrorScopeGuard::pop #[must_use = "Error scopes must be explicitly popped to retrieve errors they catch"] pub struct ErrorScopeGuard { device: dispatch::DispatchDevice, index: u32, popped: bool, // Ensure the guard is !Send and !Sync _phantom: PhantomData<*mut ()>, } static_assertions::assert_not_impl_any!(ErrorScopeGuard: Send, Sync); impl ErrorScopeGuard { /// Pops the error scope. /// /// Returns a future which resolves to the error captured by this scope, if any. /// The pop takes effect immediately; the future does not need to be awaited before doing work that is outside of this error scope. pub fn pop(mut self) -> impl Future> + WasmNotSend { self.popped = true; self.device.pop_error_scope(self.index) } } impl Drop for ErrorScopeGuard { fn drop(&mut self) { if !self.popped { drop(self.device.pop_error_scope(self.index)); } } } ================================================ FILE: wgpu/src/api/external_texture.rs ================================================ use crate::*; /// Handle to an external texture on the GPU. /// /// It can be created with [`Device::create_external_texture`]. /// /// Corresponds to [WebGPU `GPUExternalTexture`](https://gpuweb.github.io/gpuweb/#gpuexternaltexture). #[derive(Debug, Clone)] pub struct ExternalTexture { pub(crate) inner: dispatch::DispatchExternalTexture, } #[cfg(send_sync)] static_assertions::assert_impl_all!(ExternalTexture: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(ExternalTexture => .inner); impl ExternalTexture { /// Destroy the associated native resources as soon as possible. pub fn destroy(&self) { self.inner.destroy(); } } /// Describes an [`ExternalTexture`]. /// /// For use with [`Device::create_external_texture`]. /// /// Corresponds to [WebGPU `GPUExternalTextureDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpuexternaltexturedescriptor). pub type ExternalTextureDescriptor<'a> = wgt::ExternalTextureDescriptor>; static_assertions::assert_impl_all!(ExternalTextureDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/instance.rs ================================================ use alloc::vec::Vec; use core::future::Future; use crate::{dispatch::InstanceInterface, util::Mutex, *}; bitflags::bitflags! { /// WGSL language extensions. /// /// WGSL spec.: #[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)] pub struct WgslLanguageFeatures: u32 { /// const ReadOnlyAndReadWriteStorageTextures = 1 << 0; /// const Packed4x8IntegerDotProduct = 1 << 1; /// const UnrestrictedPointerParameters = 1 << 2; /// const PointerCompositeAccess = 1 << 3; } } /// Contains the various entry points to start interacting with the system's GPUs. /// /// This is the first thing you create when using wgpu. /// Its primary use is to create [`Adapter`]s and [`Surface`]s. /// /// Does not have to be kept alive. /// /// Corresponds to [WebGPU `GPU`](https://gpuweb.github.io/gpuweb/#gpu-interface). #[derive(Debug, Clone)] pub struct Instance { inner: dispatch::DispatchInstance, } #[cfg(send_sync)] static_assertions::assert_impl_all!(Instance: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(Instance => .inner); impl Default for Instance { /// Creates a new instance of wgpu with default options. /// /// Backends are set to `Backends::all()`, and FXC is chosen as the `dx12_shader_compiler`. /// /// # Panics /// /// If no backend feature for the active target platform is enabled, /// this method will panic, see [`Instance::enabled_backend_features()`]. fn default() -> Self { // TODO: Differentiate constructors here too? Self::new(InstanceDescriptor::new_without_display_handle()) } } impl Instance { /// Create an new instance of wgpu using the given options and enabled backends. /// /// # Panics /// /// - If no backend feature for the active target platform is enabled, /// this method will panic; see [`Instance::enabled_backend_features()`]. #[allow(clippy::allow_attributes, unreachable_code)] pub fn new(desc: InstanceDescriptor) -> Self { if Self::enabled_backend_features().is_empty() { panic!( "No wgpu backend feature that is implemented for the target platform was enabled. \ See `wgpu::Instance::enabled_backend_features()` for more information." ); } #[cfg(webgpu)] { let is_only_available_backend = !cfg!(wgpu_core); let requested_webgpu = desc.backends.contains(Backends::BROWSER_WEBGPU); let support_webgpu = crate::backend::get_browser_gpu_property() .map(|maybe_gpu| maybe_gpu.is_some()) .unwrap_or(false); if is_only_available_backend || (requested_webgpu && support_webgpu) { return Self { inner: crate::backend::ContextWebGpu::new(desc).into(), }; } } #[cfg(wgpu_core)] { return Self { inner: crate::backend::ContextWgpuCore::new(desc).into(), }; } // Silence unused variable warnings without adding _ to the parameter name (which shows up in docs). let _ = desc; unreachable!( "Earlier check of `enabled_backend_features` should have prevented getting here!" ); } /// Returns which backends can be picked for the current build configuration. /// /// The returned set depends on a combination of target platform and enabled features. /// This does *not* do any runtime checks and is exclusively based on compile time information. /// /// `InstanceDescriptor::backends` does not need to be a subset of this, /// but any backend that is not in this set, will not be picked. pub const fn enabled_backend_features() -> Backends { let mut backends = Backends::empty(); // `.set` and `|=` don't work in a `const` context. if cfg!(noop) { backends = backends.union(Backends::NOOP); } if cfg!(vulkan) { backends = backends.union(Backends::VULKAN); } if cfg!(any(gles, webgl)) { backends = backends.union(Backends::GL); } if cfg!(metal) { backends = backends.union(Backends::METAL); } if cfg!(dx12) { backends = backends.union(Backends::DX12); } if cfg!(webgpu) { backends = backends.union(Backends::BROWSER_WEBGPU); } backends } /// Returns the set of [WGSL language extensions] supported by this instance. /// /// [WGSL language extensions]: https://www.w3.org/TR/webgpu/#gpuwgsllanguagefeatures #[cfg(feature = "wgsl")] pub fn wgsl_language_features(&self) -> WgslLanguageFeatures { self.inner.wgsl_language_features() } /// Retrieves all available [`Adapter`]s that match the given [`Backends`]. /// /// # Arguments /// /// - `backends` - Backends from which to enumerate adapters. pub fn enumerate_adapters(&self, backends: Backends) -> impl Future> { let future = self.inner.enumerate_adapters(backends); async move { future .await .iter() .map(|adapter| Adapter { inner: adapter.clone(), }) .collect() } } /// Retrieves an [`Adapter`] which matches the given [`RequestAdapterOptions`]. /// /// Some options are "soft", so treated as non-mandatory. Others are "hard". /// /// If no adapters are found that satisfy all the "hard" options, an error is returned. /// /// When targeting WebGL2, a [`compatible_surface`](RequestAdapterOptions::compatible_surface) /// must be specified; using `RequestAdapterOptions::default()` will not succeed. pub fn request_adapter( &self, options: &RequestAdapterOptions<'_, '_>, ) -> impl Future> + WasmNotSend { let future = self.inner.request_adapter(options); async move { future.await.map(|adapter| Adapter { inner: adapter }) } } /// Creates a new surface targeting a given window/canvas/surface/etc.. /// /// Internally, this creates surfaces for all backends that are enabled for this instance. /// /// See [`SurfaceTarget`] for what targets are supported. /// See [`Instance::create_surface_unsafe()`] for surface creation with unsafe target variants. /// /// Most commonly used are window handles (or provider of windows handles) /// which can be passed directly as they're automatically converted to [`SurfaceTarget`]. pub fn create_surface<'window>( &self, target: impl Into>, ) -> Result, CreateSurfaceError> { // Handle origin (i.e. window) to optionally take ownership of to make the surface outlast the window. let handle_source; let target = target.into(); let mut surface = match target { SurfaceTarget::Window(window) => unsafe { let surface = self.create_surface_unsafe( SurfaceTargetUnsafe::from_window(&window).map_err(|e| CreateSurfaceError { inner: CreateSurfaceErrorKind::RawHandle(e), })?, ); handle_source = Some(window); surface }?, SurfaceTarget::DisplayAndWindow(display_and_window_handle) => unsafe { let surface = self.create_surface_unsafe( SurfaceTargetUnsafe::from_display_and_window( &display_and_window_handle, &display_and_window_handle, ) .map_err(|e| CreateSurfaceError { inner: CreateSurfaceErrorKind::RawHandle(e), })?, ); handle_source = Some(display_and_window_handle); surface }?, #[cfg(web)] SurfaceTarget::Canvas(canvas) => { handle_source = None; let value: &wasm_bindgen::JsValue = &canvas; let obj = core::ptr::NonNull::from(value).cast(); let raw_window_handle = raw_window_handle::WebCanvasWindowHandle::new(obj).into(); // Note that we need to call this while we still have `value` around. // This is safe without storing canvas to `handle_origin` since the surface will create a copy internally. unsafe { self.create_surface_unsafe(SurfaceTargetUnsafe::RawHandle { raw_display_handle: None, raw_window_handle, }) }? } #[cfg(web)] SurfaceTarget::OffscreenCanvas(canvas) => { handle_source = None; let value: &wasm_bindgen::JsValue = &canvas; let obj = core::ptr::NonNull::from(value).cast(); let raw_window_handle = raw_window_handle::WebOffscreenCanvasWindowHandle::new(obj).into(); // Note that we need to call this while we still have `value` around. // This is safe without storing canvas to `handle_origin` since the surface will create a copy internally. unsafe { self.create_surface_unsafe(SurfaceTargetUnsafe::RawHandle { raw_display_handle: None, raw_window_handle, }) }? } }; surface._handle_source = handle_source; Ok(surface) } /// Creates a new surface targeting a given window/canvas/surface/etc. using an unsafe target. /// /// Internally, this creates surfaces for all backends that are enabled for this instance. /// /// See [`SurfaceTargetUnsafe`] for what targets are supported. /// See [`Instance::create_surface`] for surface creation with safe target variants. /// /// # Safety /// /// - See respective [`SurfaceTargetUnsafe`] variants for safety requirements. pub unsafe fn create_surface_unsafe<'window>( &self, target: SurfaceTargetUnsafe, ) -> Result, CreateSurfaceError> { let surface = unsafe { self.inner.create_surface(target)? }; Ok(Surface { _handle_source: None, inner: surface, config: Mutex::new(None), }) } /// Polls all devices. /// /// If `force_wait` is true and this is not running on the web, then this /// function will block until all in-flight buffers have been mapped and /// all submitted commands have finished execution. /// /// Return `true` if all devices' queues are empty, or `false` if there are /// queue submissions still in flight. (Note that, unless access to all /// [`Queue`s] associated with this [`Instance`] is coordinated somehow, /// this information could be out of date by the time the caller receives /// it. `Queue`s can be shared between threads, and other threads could /// submit new work at any time.) /// /// On the web, this is a no-op. `Device`s are automatically polled. /// /// [`Queue`s]: Queue pub fn poll_all(&self, force_wait: bool) -> bool { self.inner.poll_all_devices(force_wait) } /// Generates memory report. /// /// Returns `None` if the feature is not supported by the backend /// which happens only when WebGPU is pre-selected by the instance creation. #[cfg(wgpu_core)] pub fn generate_report(&self) -> Option { self.inner.as_core_opt().map(|ctx| ctx.generate_report()) } } /// Interop with wgpu-hal. #[cfg(wgpu_core)] impl Instance { /// Create an new instance of wgpu from a wgpu-hal instance. This is often useful /// when you need to do backend specific logic, or interop with an existing backend /// instance. /// /// # Types /// /// The type of `A::Instance` depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("Instance")] #[doc = crate::macros::hal_type_metal!("Instance")] #[doc = crate::macros::hal_type_dx12!("Instance")] #[doc = crate::macros::hal_type_gles!("Instance")] /// /// # Safety /// /// - The `hal_instance` must be a valid and usable instance of the backend specified by `A`. /// - wgpu will act like it has complete ownership of this instance, and will destroy it /// when the last reference to the instance, internal or external, is dropped. pub unsafe fn from_hal(hal_instance: A::Instance) -> Self { Self { inner: unsafe { crate::backend::ContextWgpuCore::from_hal_instance::(hal_instance).into() }, } } /// Get the [`wgpu_hal`] instance from this `Instance`. /// /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`], /// and pass that struct to the to the `A` type parameter. /// /// Returns a guard that dereferences to the type of the hal backend /// which implements [`A::Instance`]. /// /// # Types /// #[doc = crate::macros::hal_type_vulkan!("Instance")] #[doc = crate::macros::hal_type_metal!("Instance")] #[doc = crate::macros::hal_type_dx12!("Instance")] #[doc = crate::macros::hal_type_gles!("Instance")] /// /// # Errors /// /// This method will return None if: /// - The instance is not from the backend specified by `A`. /// - The instance is from the `webgpu` or `custom` backend. /// /// # Safety /// /// - The returned resource must not be destroyed unless the guard /// is the last reference to it and it is not in use by the GPU. /// The guard and handle may be dropped at any time however. /// - All the safety requirements of wgpu-hal must be upheld. /// /// [`A::Instance`]: hal::Api::Instance pub unsafe fn as_hal(&self) -> Option<&A::Instance> { self.inner .as_core_opt() .and_then(|ctx| unsafe { ctx.instance_as_hal::() }) } /// Converts a wgpu-hal [`hal::ExposedAdapter`] to a wgpu [`Adapter`]. /// /// # Types /// /// The type of `hal_adapter.adapter` depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("Adapter")] #[doc = crate::macros::hal_type_metal!("Adapter")] #[doc = crate::macros::hal_type_dx12!("Adapter")] #[doc = crate::macros::hal_type_gles!("Adapter")] /// /// # Safety /// /// `hal_adapter` must be created from this instance internal handle. pub unsafe fn create_adapter_from_hal( &self, hal_adapter: hal::ExposedAdapter, ) -> Adapter { let core_instance = self.inner.as_core(); let adapter = unsafe { core_instance.create_adapter_from_hal(hal_adapter) }; let core = backend::wgpu_core::CoreAdapter { context: core_instance.clone(), id: adapter, }; Adapter { inner: core.into() } } } /// Interop with wgpu-core. #[cfg(wgpu_core)] impl Instance { /// Create an new instance of wgpu from a wgpu-core instance. /// /// # Arguments /// /// - `core_instance` - wgpu-core instance. /// /// # Safety /// /// Refer to the creation of wgpu-core Instance. pub unsafe fn from_core(core_instance: wgc::instance::Instance) -> Self { Self { inner: unsafe { crate::backend::ContextWgpuCore::from_core_instance(core_instance).into() }, } } } /// Interop with custom backends. #[cfg(custom)] impl Instance { /// Creates instance from custom context implementation pub fn from_custom(instance: T) -> Self { Self { inner: dispatch::DispatchInstance::Custom(backend::custom::DynContext::new(instance)), } } #[cfg(custom)] /// Returns custom implementation of Instance (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } ================================================ FILE: wgpu/src/api/mod.rs ================================================ //! Types and functions which define our public api and their //! helper functionality. //! //! # Conventions //! //! Each major type gets its own module. The module is laid out as follows: //! //! - The type itself //! - `impl` block for the type //! - `Drop` implementation for the type (if needed) //! - Descriptor types and their subtypes. //! - Any non-public helper types or functions. //! //! # Imports //! //! Because our public api is "flat" (i.e. all types are directly under the `wgpu` module), //! we use a single `crate::*` import at the top of each module to bring in all the types in //! the public api. This is done to: //! - Avoid having to write out a long list of imports for each module. //! - Allow docs to be written naturally, without needing to worry about needing dedicated doc imports. //! - Treat wgpu-types types and wgpu-core types as a single set. mod adapter; mod bind_group; mod bind_group_layout; mod blas; mod buffer; mod command_buffer; /// Not a root type, but common types for command buffer deferral actions. mod command_buffer_actions; mod command_encoder; // Not a root type, but common descriptor types for pipelines. mod common_pipeline; mod compute_pass; mod compute_pipeline; mod device; mod external_texture; mod instance; mod pipeline_cache; mod pipeline_layout; mod query_set; mod queue; mod render_bundle; mod render_bundle_encoder; mod render_pass; mod render_pipeline; mod sampler; mod shader_module; mod surface; mod surface_texture; mod texture; mod texture_view; mod tlas; pub use adapter::*; pub use bind_group::*; pub use bind_group_layout::*; pub use blas::*; pub use buffer::*; pub use command_buffer::*; use command_buffer_actions::*; pub use command_encoder::*; pub use common_pipeline::*; pub use compute_pass::*; pub use compute_pipeline::*; pub use device::*; pub use external_texture::*; pub use instance::*; pub use pipeline_cache::*; pub use pipeline_layout::*; pub use query_set::*; pub use queue::*; pub use render_bundle::*; pub use render_bundle_encoder::*; pub use render_pass::*; pub use render_pipeline::*; pub use sampler::*; pub use shader_module::*; pub use surface::*; pub use surface_texture::*; pub use texture::*; pub use texture_view::*; pub use tlas::*; /// Object debugging label. pub type Label<'a> = Option<&'a str>; /// A cute utility type that works just like `PhantomData`, but also /// implements `Drop`. This forces any lifetimes that are associated /// with the type to be used until the `Drop` impl is ran. This prevents /// lifetimes from being shortened. #[derive(Debug)] pub(crate) struct PhantomDrop(core::marker::PhantomData); impl Default for PhantomDrop { fn default() -> Self { Self(core::marker::PhantomData) } } impl Drop for PhantomDrop { fn drop(&mut self) {} } ================================================ FILE: wgpu/src/api/pipeline_cache.rs ================================================ use alloc::vec::Vec; use crate::*; /// Handle to a pipeline cache, which is used to accelerate /// creating [`RenderPipeline`]s and [`ComputePipeline`]s /// in subsequent executions /// /// This reuse is only applicable for the same or similar devices. /// See [`util::pipeline_cache_key`] for some details and a suggested workflow. /// /// Created using [`Device::create_pipeline_cache`]. /// /// # Background /// /// In most GPU drivers, shader code must be converted into a machine code /// which can be executed on the GPU. /// Generating this machine code can require a lot of computation. /// Pipeline caches allow this computation to be reused between executions /// of the program. /// This can be very useful for reducing program startup time. /// /// Note that most desktop GPU drivers will manage their own caches, /// meaning that little advantage can be gained from this on those platforms. /// However, on some platforms, especially Android, drivers leave this to the /// application to implement. /// /// Unfortunately, drivers do not expose whether they manage their own caches. /// Some reasonable policies for applications to use are: /// - Manage their own pipeline cache on all platforms /// - Only manage pipeline caches on Android /// /// # Usage /// /// This is used as [`RenderPipelineDescriptor::cache`] or [`ComputePipelineDescriptor::cache`]. /// It is valid to use this resource when creating multiple pipelines, in /// which case it will likely cache each of those pipelines. /// It is also valid to create a new cache for each pipeline. /// /// This resource is most useful when the data produced from it (using /// [`PipelineCache::get_data`]) is persisted. /// Care should be taken that pipeline caches are only used for the same device, /// as pipeline caches from compatible devices are unlikely to provide any advantage. /// `util::pipeline_cache_key` can be used as a file/directory name to help ensure that. /// /// It is recommended to store pipeline caches atomically. If persisting to disk, /// this can usually be achieved by creating a temporary file, then moving/[renaming] /// the temporary file over the existing cache /// /// # Storage Usage /// /// There is not currently an API available to reduce the size of a cache. /// This is due to limitations in the underlying graphics APIs used. /// This is especially impactful if your application is being updated, so /// previous caches are no longer being used. /// /// One option to work around this is to regenerate the cache. /// That is, creating the pipelines which your program runs using /// with the stored cached data, then recreating the *same* pipelines /// using a new cache, which your application then store. /// /// # Implementations /// /// This resource currently only works on the following backends: /// - Vulkan /// /// This type is unique to the Rust API of `wgpu`. /// /// [renaming]: std::fs::rename #[derive(Debug, Clone)] pub struct PipelineCache { pub(crate) inner: crate::dispatch::DispatchPipelineCache, } #[cfg(send_sync)] static_assertions::assert_impl_all!(PipelineCache: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(PipelineCache => .inner); impl PipelineCache { /// Get the data associated with this pipeline cache. /// The data format is an implementation detail of `wgpu`. /// The only defined operation on this data setting it as the `data` field /// on [`PipelineCacheDescriptor`], then to [`Device::create_pipeline_cache`]. /// /// This function is unique to the Rust API of `wgpu`. pub fn get_data(&self) -> Option> { self.inner.get_data() } #[cfg(custom)] /// Returns custom implementation of PipelineCache (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } ================================================ FILE: wgpu/src/api/pipeline_layout.rs ================================================ use crate::*; /// Handle to a pipeline layout. /// /// A `PipelineLayout` object describes the available binding groups of a pipeline. /// It can be created with [`Device::create_pipeline_layout`]. /// /// Corresponds to [WebGPU `GPUPipelineLayout`](https://gpuweb.github.io/gpuweb/#gpupipelinelayout). #[derive(Debug, Clone)] pub struct PipelineLayout { pub(crate) inner: dispatch::DispatchPipelineLayout, } #[cfg(send_sync)] static_assertions::assert_impl_all!(PipelineLayout: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(PipelineLayout => .inner); impl PipelineLayout { #[cfg(custom)] /// Returns custom implementation of PipelineLayout (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// Describes a [`PipelineLayout`]. /// /// For use with [`Device::create_pipeline_layout`]. /// /// Corresponds to [WebGPU `GPUPipelineLayoutDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpupipelinelayoutdescriptor). #[derive(Clone, Debug, Default)] pub struct PipelineLayoutDescriptor<'a> { /// Debug label of the pipeline layout. This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// Bind groups that this pipeline uses. The first entry will provide all the bindings for /// "set = 0", second entry will provide all the bindings for "set = 1" etc. pub bind_group_layouts: &'a [Option<&'a BindGroupLayout>], /// The number of bytes of immediate data that are allocated for use /// in the shader. The `var`s in the shader attached to /// this pipeline must be equal or smaller than this size. /// /// If this value is non-zero, [`Features::IMMEDIATES`] must be enabled. pub immediate_size: u32, } #[cfg(send_sync)] static_assertions::assert_impl_all!(PipelineLayoutDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/query_set.rs ================================================ use crate::*; /// Handle to a query set. /// /// A `QuerySet` is an opaque, mutable storage location for the results of queries: /// which are small pieces of information extracted from other operations such as render passes. /// See [`QueryType`] for what types of information can be collected. /// /// Each query writes data into one or more result slots in the `QuerySet`, which must be created /// with a sufficient number of slots for that usage. Each result slot is a an unsigned 64-bit /// number. /// /// Using queries consists of the following steps: /// /// 1. Create a `QuerySet` of the appropriate type and number of query result slots /// using [`Device::create_query_set()`]. /// 2. Pass the `QuerySet` to the commands which will write to it. /// See [`QueryType`] for the possible commands. /// 3. Execute the command [`CommandEncoder::resolve_query_set()`]. /// This converts the opaque data stored in a `QuerySet` into [`u64`]s stored in a [`Buffer`]. /// 4. Make use of that buffer, such as by copying its contents to the CPU /// or reading it from a compute shader. /// /// Corresponds to [WebGPU `GPUQuerySet`](https://gpuweb.github.io/gpuweb/#queryset). #[derive(Debug, Clone)] pub struct QuerySet { pub(crate) inner: dispatch::DispatchQuerySet, } #[cfg(send_sync)] #[cfg(send_sync)] static_assertions::assert_impl_all!(QuerySet: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(QuerySet => .inner); impl QuerySet { #[cfg(custom)] /// Returns custom implementation of QuerySet (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// Describes a [`QuerySet`]. /// /// For use with [`Device::create_query_set`]. /// /// Corresponds to [WebGPU `GPUQuerySetDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpuquerysetdescriptor). pub type QuerySetDescriptor<'a> = wgt::QuerySetDescriptor>; static_assertions::assert_impl_all!(QuerySetDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/queue.rs ================================================ use alloc::boxed::Box; use core::ops::{Deref, RangeBounds}; use crate::{api::DeferredCommandBufferActions, *}; /// Handle to a command queue on a device. /// /// A `Queue` executes recorded [`CommandBuffer`] objects and provides convenience methods /// for writing to [buffers](Queue::write_buffer) and [textures](Queue::write_texture). /// It can be created along with a [`Device`] by calling [`Adapter::request_device`]. /// /// Corresponds to [WebGPU `GPUQueue`](https://gpuweb.github.io/gpuweb/#gpu-queue). #[derive(Debug, Clone)] pub struct Queue { pub(crate) inner: dispatch::DispatchQueue, } #[cfg(send_sync)] static_assertions::assert_impl_all!(Queue: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(Queue => .inner); impl Queue { #[cfg(custom)] /// Returns custom implementation of Queue (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } #[cfg(custom)] /// Creates Queue from custom implementation pub fn from_custom(queue: T) -> Self { Self { inner: dispatch::DispatchQueue::custom(queue), } } } /// Identifier for a particular call to [`Queue::submit`]. Can be used /// as part of an argument to [`Device::poll`] to block for a particular /// submission to finish. /// /// This type is unique to the Rust API of `wgpu`. /// There is no analogue in the WebGPU specification. #[derive(Debug, Clone)] pub struct SubmissionIndex { pub(crate) index: u64, } #[cfg(send_sync)] static_assertions::assert_impl_all!(SubmissionIndex: Send, Sync); /// Passed to [`Device::poll`] to control how and if it should block. pub type PollType = wgt::PollType; #[cfg(send_sync)] static_assertions::assert_impl_all!(PollType: Send, Sync); /// A write-only view into a staging buffer. /// /// This type is what [`Queue::write_buffer_with()`] returns. pub struct QueueWriteBufferView { queue: Queue, buffer: Buffer, offset: BufferAddress, inner: dispatch::DispatchQueueWriteBuffer, } #[cfg(send_sync)] static_assertions::assert_impl_all!(QueueWriteBufferView: Send, Sync); impl QueueWriteBufferView { #[cfg(custom)] /// Returns custom implementation of QueueWriteBufferView (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } impl Drop for QueueWriteBufferView { fn drop(&mut self) { self.queue .inner .write_staging_buffer(&self.buffer.inner, self.offset, &self.inner); } } /// These methods are equivalent to the methods of the same names on [`WriteOnly`]. impl QueueWriteBufferView { /// Returns the length of this view; the number of bytes to be written. pub fn len(&self) -> usize { self.inner.len() } /// Returns `true` if the view has a length of 0. pub fn is_empty(&self) -> bool { self.len() == 0 } /// Returns a [`WriteOnly`] reference to a portion of this. /// /// `.slice(..)` can be used to access the whole data. pub fn slice<'a, S: RangeBounds>(&'a mut self, bounds: S) -> WriteOnly<'a, [u8]> { // SAFETY: // * this is a write mapping // * function signature ensures no aliasing unsafe { self.inner.write_slice() }.into_slice(bounds) } /// Copies all elements from src into `self`. /// /// The length of `src` must be the same as `self`. /// /// This method is equivalent to /// [`self.slice(..).copy_from_slice(src)`][WriteOnly::copy_from_slice]. pub fn copy_from_slice(&mut self, src: &[u8]) { self.slice(..).copy_from_slice(src) } } impl Queue { /// Copies the bytes of `data` into `buffer` starting at `offset`. /// /// The data must be written fully in-bounds, that is, `offset + data.len() <= buffer.len()`. /// /// # Performance considerations /// /// * Calls to `write_buffer()` do *not* submit the transfer to the GPU /// immediately. They begin GPU execution only on the next call to /// [`Queue::submit()`], just before the explicitly submitted commands. /// To get a set of scheduled transfers started immediately, /// it's fine to call `submit` with no command buffers at all: /// /// ```no_run /// # let queue: wgpu::Queue = todo!(); /// # let buffer: wgpu::Buffer = todo!(); /// # let data = [0u8]; /// queue.write_buffer(&buffer, 0, &data); /// queue.submit([]); /// ``` /// /// However, `data` will be immediately copied into staging memory, so the /// caller may discard it any time after this call completes. /// /// * Consider using [`Queue::write_buffer_with()`] instead. /// That method allows you to prepare your data directly within the staging /// memory, rather than first placing it in a separate `[u8]` to be copied. /// That is, `queue.write_buffer(b, offset, data)` is approximately equivalent /// to `queue.write_buffer_with(b, offset, data.len()).copy_from_slice(data)`, /// so use `write_buffer_with()` if you can do something smarter than that /// [`copy_from_slice()`](slice::copy_from_slice). However, for small values /// (e.g. a typical uniform buffer whose contents come from a `struct`), /// there will likely be no difference, since the compiler will be able to /// optimize out unnecessary copies regardless. /// /// * Currently on native platforms, for both of these methods, the staging /// memory will be a new allocation. This will then be released after the /// next submission finishes. To entirely avoid short-lived allocations, you might /// be able to use [`StagingBelt`](crate::util::StagingBelt), /// or buffers you explicitly create, map, and unmap yourself. pub fn write_buffer(&self, buffer: &Buffer, offset: BufferAddress, data: &[u8]) { self.inner.write_buffer(&buffer.inner, offset, data); } /// Prepares to write data to a buffer via a mapped staging buffer. /// /// This operation allocates a temporary buffer and then returns a /// [`QueueWriteBufferView`], which /// /// * dereferences to a `[u8]` of length `size`, and /// * when dropped, schedules a copy of its contents into `buffer` at `offset`. /// /// Therefore, this obtains the same result as [`Queue::write_buffer()`], but may /// allow you to skip one allocation and one copy of your data, if you are able to /// assemble your data directly into the returned [`QueueWriteBufferView`] instead of /// into a separate allocation like a [`Vec`](alloc::vec::Vec) first. /// /// The data must be written fully in-bounds, that is, `offset + size <= buffer.len()`. /// /// # Performance considerations /// /// * For small data not separately heap-allocated, there is no advantage of this /// over [`Queue::write_buffer()`]. /// /// * Reading from the returned view may be slow, and will not yield the current /// contents of `buffer`. You should treat it as “write-only”. /// /// * Dropping the [`QueueWriteBufferView`] does *not* submit the /// transfer to the GPU immediately. The transfer begins only on the next /// call to [`Queue::submit()`] after the view is dropped, just before the /// explicitly submitted commands. To get a set of scheduled transfers started /// immediately, it's fine to call `queue.submit([])` with no command buffers at all. /// /// * Currently on native platforms, the staging memory will be a new allocation, which will /// then be released after the next submission finishes. To entirely avoid short-lived /// allocations, you might be able to use [`StagingBelt`](crate::util::StagingBelt), /// or buffers you explicitly create, map, and unmap yourself. #[must_use] pub fn write_buffer_with( &self, buffer: &Buffer, offset: BufferAddress, size: BufferSize, ) -> Option { profiling::scope!("Queue::write_buffer_with"); self.inner .validate_write_buffer(&buffer.inner, offset, size)?; let staging_buffer = self.inner.create_staging_buffer(size)?; Some(QueueWriteBufferView { queue: self.clone(), buffer: buffer.clone(), offset, inner: staging_buffer, }) } /// Copies the bytes of `data` into a texture. /// /// * `data` contains the texels to be written, which must be in /// [the same format as the texture](TextureFormat). /// * `data_layout` describes the memory layout of `data`, which does not necessarily /// have to have tightly packed rows. /// * `texture` specifies the texture to write into, and the location within the /// texture (coordinate offset, mip level) that will be overwritten. /// * `size` is the size, in texels, of the region to be written. /// /// This method fails if `size` overruns the size of `texture`, or if `data` is too short. /// /// # Performance considerations /// /// This operation has the same performance considerations as [`Queue::write_buffer()`]; /// see its documentation for details. /// /// However, since there is no “mapped texture” like a mapped buffer, /// alternate techniques for writing to textures will generally consist of first copying /// the data to a buffer, then using [`CommandEncoder::copy_buffer_to_texture()`], or in /// some cases a compute shader, to copy texels from that buffer to the texture. pub fn write_texture( &self, texture: TexelCopyTextureInfo<'_>, data: &[u8], data_layout: TexelCopyBufferLayout, size: Extent3d, ) { self.inner.write_texture(texture, data, data_layout, size); } /// Schedule a copy of data from `image` into `texture`. #[cfg(web)] pub fn copy_external_image_to_texture( &self, source: &wgt::CopyExternalImageSourceInfo, dest: wgt::CopyExternalImageDestInfo<&api::Texture>, size: Extent3d, ) { self.inner .copy_external_image_to_texture(source, dest, size); } /// Submits a series of finished command buffers for execution. pub fn submit>( &self, command_buffers: I, ) -> SubmissionIndex { // As submit drains the iterator (even on error), collect deferred actions // from each CommandBuffer along the way. let mut actions = DeferredCommandBufferActions::default(); let mut command_buffers = command_buffers.into_iter().map(|comb| { actions.append(&mut comb.actions.lock()); comb.buffer }); let index = self.inner.submit(&mut command_buffers); // Execute all deferred actions after submit. actions.execute(&self.inner); SubmissionIndex { index } } /// Gets the amount of nanoseconds each tick of a timestamp query represents. /// /// Returns zero if timestamp queries are unsupported. /// /// Timestamp values are represented in nanosecond values on WebGPU, see /// Therefore, this is always 1.0 on the web, but on wgpu-core a manual conversion is required. pub fn get_timestamp_period(&self) -> f32 { self.inner.get_timestamp_period() } /// Registers a callback that is invoked when the previous [`Queue::submit`] finishes executing /// on the GPU. When this callback runs, all mapped-buffer callbacks registered for the same /// submission are guaranteed to have been called. /// /// For the callback to run, either [`queue.submit(..)`][q::s], [`instance.poll_all(..)`][i::p_a], /// or [`device.poll(..)`][d::p] must be called elsewhere in the runtime, possibly integrated into /// an event loop or run on a separate thread. /// /// The callback runs on the thread that first calls one of the above functions after the GPU work /// completes. There are no restrictions on the code you can run in the callback; however, on native /// the polling call will not return until the callback finishes, so keep callbacks short (set flags, /// send messages, etc.). /// /// [q::s]: Queue::submit /// [i::p_a]: Instance::poll_all /// [d::p]: Device::poll pub fn on_submitted_work_done(&self, callback: impl FnOnce() + Send + 'static) { self.inner.on_submitted_work_done(Box::new(callback)); } /// Get the [`wgpu_hal`] device from this `Queue`. /// /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`], /// and pass that struct to the to the `A` type parameter. /// /// Returns a guard that dereferences to the type of the hal backend /// which implements [`A::Queue`]. /// /// # Types /// /// The returned type depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("Queue")] #[doc = crate::macros::hal_type_metal!("Queue")] #[doc = crate::macros::hal_type_dx12!("Queue")] #[doc = crate::macros::hal_type_gles!("Queue")] /// /// # Errors /// /// This method will return None if: /// - The queue is not from the backend specified by `A`. /// - The queue is from the `webgpu` or `custom` backend. /// /// # Safety /// /// - The returned resource must not be destroyed unless the guard /// is the last reference to it and it is not in use by the GPU. /// The guard and handle may be dropped at any time however. /// - All the safety requirements of wgpu-hal must be upheld. /// /// [`A::Queue`]: hal::Api::Queue #[cfg(wgpu_core)] pub unsafe fn as_hal( &self, ) -> Option + WasmNotSendSync> { let queue = self.inner.as_core_opt()?; unsafe { queue.context.queue_as_hal::(queue) } } /// Compact a BLAS, it must have had [`Blas::prepare_compaction_async`] called on it and had the /// callback provided called. /// /// The returned BLAS is more restricted than a normal BLAS because it may not be rebuilt or /// compacted. pub fn compact_blas(&self, blas: &Blas) -> Blas { let (handle, dispatch) = self.inner.compact_blas(&blas.inner); Blas { handle, inner: dispatch, } } } ================================================ FILE: wgpu/src/api/render_bundle.rs ================================================ use crate::*; /// Pre-prepared reusable bundle of GPU operations. /// /// It only supports a handful of render commands, but it makes them reusable. Executing a /// [`RenderBundle`] is often more efficient than issuing the underlying commands manually. /// /// It can be created by use of a [`RenderBundleEncoder`], and executed onto a [`CommandEncoder`] /// using [`RenderPass::execute_bundles`]. /// /// Corresponds to [WebGPU `GPURenderBundle`](https://gpuweb.github.io/gpuweb/#render-bundle). #[derive(Debug, Clone)] pub struct RenderBundle { pub(crate) inner: dispatch::DispatchRenderBundle, } #[cfg(send_sync)] static_assertions::assert_impl_all!(RenderBundle: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(RenderBundle => .inner); impl RenderBundle { #[cfg(custom)] /// Returns custom implementation of RenderBundle (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// Describes a [`RenderBundle`]. /// /// For use with [`RenderBundleEncoder::finish`]. /// /// Corresponds to [WebGPU `GPURenderBundleDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpurenderbundledescriptor). pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor>; static_assertions::assert_impl_all!(RenderBundleDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/render_bundle_encoder.rs ================================================ use core::{marker::PhantomData, num::NonZeroU32, ops::Range}; use crate::dispatch::RenderBundleEncoderInterface; use crate::*; /// Encodes a series of GPU operations into a reusable "render bundle". /// /// It only supports a handful of render commands, but it makes them reusable. /// It can be created with [`Device::create_render_bundle_encoder`]. /// It can be executed onto a [`CommandEncoder`] using [`RenderPass::execute_bundles`]. /// /// Executing a [`RenderBundle`] is often more efficient than issuing the underlying commands /// manually. /// /// Corresponds to [WebGPU `GPURenderBundleEncoder`]( /// https://gpuweb.github.io/gpuweb/#gpurenderbundleencoder). #[derive(Debug)] pub struct RenderBundleEncoder<'a> { pub(crate) inner: dispatch::DispatchRenderBundleEncoder, /// This type should be !Send !Sync, because it represents an allocation on this thread's /// command buffer. pub(crate) _p: PhantomData<(*const u8, &'a ())>, } static_assertions::assert_not_impl_any!(RenderBundleEncoder<'_>: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(RenderBundleEncoder<'_> => .inner); /// Describes a [`RenderBundleEncoder`]. /// /// For use with [`Device::create_render_bundle_encoder`]. /// /// Corresponds to [WebGPU `GPURenderBundleEncoderDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpurenderbundleencoderdescriptor). #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub struct RenderBundleEncoderDescriptor<'a> { /// Debug label of the render bundle encoder. This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// The formats of the color attachments that this render bundle is capable to rendering to. This /// must match the formats of the color attachments in the render pass this render bundle is executed in. pub color_formats: &'a [Option], /// Information about the depth attachment that this render bundle is capable to rendering to. This /// must match the format of the depth attachments in the render pass this render bundle is executed in. pub depth_stencil: Option, /// Sample count this render bundle is capable of rendering to. This must match the pipelines and /// the render passes it is used in. pub sample_count: u32, /// If this render bundle will rendering to multiple array layers in the attachments at the same time. pub multiview: Option, } static_assertions::assert_impl_all!(RenderBundleEncoderDescriptor<'_>: Send, Sync); impl<'a> RenderBundleEncoder<'a> { /// Finishes recording and returns a [`RenderBundle`] that can be executed in other render passes. pub fn finish(self, desc: &RenderBundleDescriptor<'_>) -> RenderBundle { let bundle = match self.inner { #[cfg(wgpu_core)] dispatch::DispatchRenderBundleEncoder::Core(b) => b.finish(desc), #[cfg(webgpu)] dispatch::DispatchRenderBundleEncoder::WebGPU(b) => b.finish(desc), #[cfg(custom)] dispatch::DispatchRenderBundleEncoder::Custom(_) => unimplemented!(), }; RenderBundle { inner: bundle } } /// Sets the active bind group for a given bind group index. The bind group layout /// in the active pipeline when any `draw()` function is called must match the layout of this bind group. /// /// If the bind group have dynamic offsets, provide them in the binding order. pub fn set_bind_group<'b, BG>(&mut self, index: u32, bind_group: BG, offsets: &[DynamicOffset]) where Option<&'b BindGroup>: From, { let bg: Option<&'b BindGroup> = bind_group.into(); let bg = bg.map(|x| &x.inner); self.inner.set_bind_group(index, bg, offsets); } /// Sets the active render pipeline. /// /// Subsequent draw calls will exhibit the behavior defined by `pipeline`. pub fn set_pipeline(&mut self, pipeline: &'a RenderPipeline) { self.inner.set_pipeline(&pipeline.inner); } /// Sets the active index buffer. /// /// Subsequent calls to [`draw_indexed`](RenderBundleEncoder::draw_indexed) on this [`RenderBundleEncoder`] will /// use `buffer` as the source index buffer. pub fn set_index_buffer(&mut self, buffer_slice: BufferSlice<'a>, index_format: IndexFormat) { self.inner.set_index_buffer( &buffer_slice.buffer.inner, index_format, buffer_slice.offset, Some(buffer_slice.size), ); } /// Assign a vertex buffer to a slot. /// /// Subsequent calls to [`draw`] and [`draw_indexed`] on this /// [`RenderBundleEncoder`] will use `buffer` as one of the source vertex buffers. /// /// The `slot` refers to the index of the matching descriptor in /// [`VertexState::buffers`]. /// /// [`draw`]: RenderBundleEncoder::draw /// [`draw_indexed`]: RenderBundleEncoder::draw_indexed pub fn set_vertex_buffer(&mut self, slot: u32, buffer_slice: BufferSlice<'a>) { self.inner.set_vertex_buffer( slot, &buffer_slice.buffer.inner, buffer_slice.offset, Some(buffer_slice.size), ); } /// Draws primitives from the active vertex buffer(s). /// /// The active vertex buffers can be set with [`RenderBundleEncoder::set_vertex_buffer`]. /// Does not use an Index Buffer. If you need this see [`RenderBundleEncoder::draw_indexed`] /// /// Panics if vertices Range is outside of the range of the vertices range of any set vertex buffer. /// /// vertices: The range of vertices to draw. /// instances: Range of Instances to draw. Use 0..1 if instance buffers are not used. /// E.g.of how its used internally /// ```rust ignore /// for instance_id in instance_range { /// for vertex_id in vertex_range { /// let vertex = vertex[vertex_id]; /// vertex_shader(vertex, vertex_id, instance_id); /// } /// } /// ``` pub fn draw(&mut self, vertices: Range, instances: Range) { self.inner.draw(vertices, instances); } /// Draws indexed primitives using the active index buffer and the active vertex buffer(s). /// /// The active index buffer can be set with [`RenderBundleEncoder::set_index_buffer`]. /// The active vertex buffer(s) can be set with [`RenderBundleEncoder::set_vertex_buffer`]. /// /// Panics if indices Range is outside of the range of the indices range of any set index buffer. /// /// indices: The range of indices to draw. /// base_vertex: value added to each index value before indexing into the vertex buffers. /// instances: Range of Instances to draw. Use 0..1 if instance buffers are not used. /// E.g.of how its used internally /// ```rust ignore /// for instance_id in instance_range { /// for index_index in index_range { /// let vertex_id = index_buffer[index_index]; /// let adjusted_vertex_id = vertex_id + base_vertex; /// let vertex = vertex[adjusted_vertex_id]; /// vertex_shader(vertex, adjusted_vertex_id, instance_id); /// } /// } /// ``` pub fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range) { self.inner.draw_indexed(indices, base_vertex, instances); } /// Draws primitives from the active vertex buffer(s) based on the contents of the `indirect_buffer`. /// /// The active vertex buffers can be set with [`RenderBundleEncoder::set_vertex_buffer`]. /// /// The structure expected in `indirect_buffer` must conform to [`DrawIndirectArgs`](crate::util::DrawIndirectArgs). pub fn draw_indirect(&mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress) { self.inner .draw_indirect(&indirect_buffer.inner, indirect_offset); } /// Draws indexed primitives using the active index buffer and the active vertex buffers, /// based on the contents of the `indirect_buffer`. /// /// The active index buffer can be set with [`RenderBundleEncoder::set_index_buffer`], while the active /// vertex buffers can be set with [`RenderBundleEncoder::set_vertex_buffer`]. /// /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirectArgs`](crate::util::DrawIndexedIndirectArgs). pub fn draw_indexed_indirect( &mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress, ) { self.inner .draw_indexed_indirect(&indirect_buffer.inner, indirect_offset); } #[cfg(custom)] /// Returns custom implementation of RenderBundleEncoder (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// [`Features::IMMEDIATES`] must be enabled on the device in order to call these functions. impl RenderBundleEncoder<'_> { /// Set immediate data for subsequent draw calls within the render bundle. /// /// Write the bytes in `data` at offset `offset` within immediate data /// storage. Both `offset` and the length of `data` must be /// multiples of [`crate::IMMEDIATE_DATA_ALIGNMENT`], which is always 4. /// /// For example, if `offset` is `4` and `data` is eight bytes long, this /// call will write `data` to bytes `4..12` of immediate data storage. pub fn set_immediates(&mut self, offset: u32, data: &[u8]) { self.inner.set_immediates(offset, data); } } ================================================ FILE: wgpu/src/api/render_pass.rs ================================================ use core::{num::NonZeroU32, ops::Range}; use crate::{ api::{impl_deferred_command_buffer_actions, SharedDeferredCommandBufferActions}, *, }; pub use wgt::{LoadOp, Operations, StoreOp}; /// In-progress recording of a render pass: a list of render commands in a [`CommandEncoder`]. /// /// It can be created with [`CommandEncoder::begin_render_pass()`], whose [`RenderPassDescriptor`] /// specifies the attachments (textures) that will be rendered to. /// /// Most of the methods on `RenderPass` serve one of two purposes, identifiable by their names: /// /// * `draw_*()`: Drawing (that is, encoding a render command, which, when executed by the GPU, will /// rasterize something and execute shaders). /// * `set_*()`: Setting part of the [render state](https://gpuweb.github.io/gpuweb/#renderstate) /// for future drawing commands. /// /// A render pass may contain any number of drawing commands, and before/between each command the /// render state may be updated however you wish; each drawing command will be executed using the /// render state that has been set when the `draw_*()` function is called. /// /// Corresponds to [WebGPU `GPURenderPassEncoder`]( /// https://gpuweb.github.io/gpuweb/#render-pass-encoder). #[derive(Debug)] pub struct RenderPass<'encoder> { pub(crate) inner: dispatch::DispatchRenderPass, pub(crate) actions: SharedDeferredCommandBufferActions, /// This lifetime is used to protect the [`CommandEncoder`] from being used /// while the pass is alive. This needs to be PhantomDrop to prevent the lifetime /// from being shortened. pub(crate) _encoder_guard: PhantomDrop<&'encoder ()>, } #[cfg(send_sync)] static_assertions::assert_impl_all!(RenderPass<'_>: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(RenderPass<'_> => .inner); impl RenderPass<'_> { /// Drops the lifetime relationship to the parent command encoder, making usage of /// the encoder while this pass is recorded a run-time error instead. /// /// Attention: As long as the render pass has not been ended, any mutating operation on the parent /// command encoder will cause a run-time error and invalidate it! /// By default, the lifetime constraint prevents this, but it can be useful /// to handle this at run time, such as when storing the pass and encoder in the same /// data structure. /// /// This operation has no effect on pass recording. /// It's a safe operation, since [`CommandEncoder`] is in a locked state as long as the pass is active /// regardless of the lifetime constraint or its absence. pub fn forget_lifetime(self) -> RenderPass<'static> { RenderPass { inner: self.inner, actions: self.actions, _encoder_guard: crate::api::PhantomDrop::default(), } } /// Sets the active bind group for a given bind group index. The bind group layout /// in the active pipeline when any `draw_*()` method is called must match the layout of /// this bind group. /// /// If the bind group have dynamic offsets, provide them in binding order. /// These offsets have to be aligned to [`Limits::min_uniform_buffer_offset_alignment`] /// or [`Limits::min_storage_buffer_offset_alignment`] appropriately. /// /// Subsequent draw calls’ shader executions will be able to access data in these bind groups. pub fn set_bind_group<'a, BG>(&mut self, index: u32, bind_group: BG, offsets: &[DynamicOffset]) where Option<&'a BindGroup>: From, { let bg: Option<&'a BindGroup> = bind_group.into(); let bg = bg.map(|bg| &bg.inner); self.inner.set_bind_group(index, bg, offsets); } /// Sets the active render pipeline. /// /// Subsequent draw calls will exhibit the behavior defined by `pipeline`. pub fn set_pipeline(&mut self, pipeline: &RenderPipeline) { self.inner.set_pipeline(&pipeline.inner); } /// Sets the blend color as used by some of the blending modes. /// /// Subsequent blending tests will test against this value. /// If this method has not been called, the blend constant defaults to [`Color::TRANSPARENT`] /// (all components zero). pub fn set_blend_constant(&mut self, color: Color) { self.inner.set_blend_constant(color); } /// Sets the active index buffer. /// /// Subsequent calls to [`draw_indexed`](RenderPass::draw_indexed) on this [`RenderPass`] will /// use `buffer` as the source index buffer. pub fn set_index_buffer(&mut self, buffer_slice: BufferSlice<'_>, index_format: IndexFormat) { self.inner.set_index_buffer( &buffer_slice.buffer.inner, index_format, buffer_slice.offset, Some(buffer_slice.size), ); } /// Assign a vertex buffer to a slot. /// /// Subsequent calls to [`draw`] and [`draw_indexed`] on this /// [`RenderPass`] will use `buffer` as one of the source vertex buffers. /// The format of the data in the buffer is specified by the [`VertexBufferLayout`] in the /// pipeline's [`VertexState`]. /// /// The `slot` refers to the index of the matching descriptor in /// [`VertexState::buffers`]. /// /// [`draw`]: RenderPass::draw /// [`draw_indexed`]: RenderPass::draw_indexed pub fn set_vertex_buffer(&mut self, slot: u32, buffer_slice: BufferSlice<'_>) { self.inner.set_vertex_buffer( slot, &buffer_slice.buffer.inner, buffer_slice.offset, Some(buffer_slice.size), ); } /// Sets the scissor rectangle used during the rasterization stage. /// After transformation into [viewport coordinates](https://www.w3.org/TR/webgpu/#viewport-coordinates). /// /// Subsequent draw calls will discard any fragments which fall outside the scissor rectangle. /// If this method has not been called, the scissor rectangle defaults to the entire bounds of /// the render targets. /// /// The function of the scissor rectangle resembles [`set_viewport()`](Self::set_viewport), /// but it does not affect the coordinate system, only which fragments are discarded. pub fn set_scissor_rect(&mut self, x: u32, y: u32, width: u32, height: u32) { self.inner.set_scissor_rect(x, y, width, height); } /// Sets the viewport used during the rasterization stage to linearly map /// from [normalized device coordinates](https://www.w3.org/TR/webgpu/#ndc) to [viewport coordinates](https://www.w3.org/TR/webgpu/#viewport-coordinates). /// /// Subsequent draw calls will only draw within this region. /// If this method has not been called, the viewport defaults to the entire bounds of the render /// targets. pub fn set_viewport(&mut self, x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32) { self.inner.set_viewport(x, y, w, h, min_depth, max_depth); } /// Sets the stencil reference. /// /// Subsequent stencil tests will test against this value. /// If this method has not been called, the stencil reference value defaults to `0`. pub fn set_stencil_reference(&mut self, reference: u32) { self.inner.set_stencil_reference(reference); } /// Inserts debug marker. pub fn insert_debug_marker(&mut self, label: &str) { self.inner.insert_debug_marker(label); } /// Start record commands and group it into debug marker group. pub fn push_debug_group(&mut self, label: &str) { self.inner.push_debug_group(label); } /// Stops command recording and creates debug group. pub fn pop_debug_group(&mut self) { self.inner.pop_debug_group(); } /// Draws primitives from the active vertex buffer(s). /// /// The active vertex buffer(s) can be set with [`RenderPass::set_vertex_buffer`]. /// This does not use an index buffer. If you need indexed drawing, see [`RenderPass::draw_indexed`] /// /// Panics if `vertices` range is outside of the range of the vertices range of any set vertex buffer. /// /// - `vertices`: The range of vertices to draw. /// - `instances`: Range of instances to draw. Use `0..1` if instance buffers are not used. /// /// E.g.of how its used internally /// ```rust ignore /// for instance_id in instance_range { /// for vertex_id in vertex_range { /// let vertex = vertex[vertex_id]; /// vertex_shader(vertex, vertex_id, instance_id); /// } /// } /// ``` /// /// This drawing command uses the current render state, as set by preceding `set_*()` methods. /// It is not affected by changes to the state that are performed after it is called. pub fn draw(&mut self, vertices: Range, instances: Range) { self.inner.draw(vertices, instances); } /// Draws indexed primitives using the active index buffer and the active vertex buffers. /// /// The active index buffer can be set with [`RenderPass::set_index_buffer`] /// The active vertex buffers can be set with [`RenderPass::set_vertex_buffer`]. /// /// Panics if `indices` range is outside of the range of the indices range of the set index buffer. /// /// - `indices`: The range of indices to draw. /// - `base_vertex`: value added to each index value before indexing into the vertex buffers. /// - `instances`: Range of instances to draw. Use `0..1` if instance buffers are not used. /// /// E.g.of how its used internally /// ```rust ignore /// for instance_id in instance_range { /// for index_index in index_range { /// let vertex_id = index_buffer[index_index]; /// let adjusted_vertex_id = vertex_id + base_vertex; /// let vertex = vertex[adjusted_vertex_id]; /// vertex_shader(vertex, adjusted_vertex_id, instance_id); /// } /// } /// ``` /// /// This drawing command uses the current render state, as set by preceding `set_*()` methods. /// It is not affected by changes to the state that are performed after it is called. pub fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range) { self.inner.draw_indexed(indices, base_vertex, instances); } /// Draws using a mesh pipeline. /// /// The current pipeline must be a mesh pipeline. /// /// If the current pipeline has a task shader, run it with an workgroup for /// every `vec3(i, j, k)` where `i`, `j`, and `k` are between `0` and /// `group_count_x`, `group_count_y`, and `group_count_z`. The invocation with /// index zero in each group is responsible for determining the mesh shader dispatch. /// Its return value indicates the number of workgroups of mesh shaders to invoke. It also /// passes a payload value for them to consume. Because each task workgroup is essentially /// a mesh shader draw call, mesh workgroups dispatched by different task workgroups /// cannot interact in any way, and `workgroup_id` corresponds to its location in the /// calling specific task shader's dispatch group. /// /// If the current pipeline lacks a task shader, run its mesh shader with a /// workgroup for every `vec3(i, j, k)` where `i`, `j`, and `k` are /// between `0` and `group_count_x`, `group_count_y`, and `group_count_z`. /// /// Each mesh shader workgroup outputs a set of vertices and indices for primitives. /// The indices outputted correspond to the vertices outputted by that same workgroup; /// there is no global vertex buffer. These primitives are passed to the rasterizer and /// essentially treated like a vertex shader output, except that the mesh shader may /// choose to cull specific primitives or pass per-primitive non-interpolated values /// to the fragment shader. As such, each primitive is then rendered with the current /// pipeline's fragment shader, if present. Otherwise, [No Color Output mode] is used. /// /// [No Color Output mode]: https://www.w3.org/TR/webgpu/#no-color-output pub fn draw_mesh_tasks(&mut self, group_count_x: u32, group_count_y: u32, group_count_z: u32) { self.inner .draw_mesh_tasks(group_count_x, group_count_y, group_count_z); } /// Draws primitives from the active vertex buffer(s) based on the contents of the `indirect_buffer`. /// /// This is like calling [`RenderPass::draw`] but the contents of the call are specified in the `indirect_buffer`. /// The structure expected in `indirect_buffer` must conform to [`DrawIndirectArgs`](crate::util::DrawIndirectArgs). /// /// Calling this requires the device support [`DownlevelFlags::INDIRECT_EXECUTION`]. pub fn draw_indirect(&mut self, indirect_buffer: &Buffer, indirect_offset: BufferAddress) { self.inner .draw_indirect(&indirect_buffer.inner, indirect_offset); } /// Draws indexed primitives using the active index buffer and the active vertex buffers, /// based on the contents of the `indirect_buffer`. /// /// This is like calling [`RenderPass::draw_indexed`] but the contents of the call are specified in the `indirect_buffer`. /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirectArgs`](crate::util::DrawIndexedIndirectArgs). /// /// Calling this requires the device support [`DownlevelFlags::INDIRECT_EXECUTION`]. pub fn draw_indexed_indirect( &mut self, indirect_buffer: &Buffer, indirect_offset: BufferAddress, ) { self.inner .draw_indexed_indirect(&indirect_buffer.inner, indirect_offset); } /// Draws using a mesh pipeline, /// based on the contents of the `indirect_buffer` /// /// This is like calling [`RenderPass::draw_mesh_tasks`] but the contents of the call are specified in the `indirect_buffer`. /// The structure expected in the `indirect_buffer` must conform to [`DispatchIndirectArgs`](crate::util::DispatchIndirectArgs). /// /// Indirect drawing has some caveats depending on the features available. We are not currently able to validate /// these and issue an error. /// /// See details on the individual flags for more information. pub fn draw_mesh_tasks_indirect( &mut self, indirect_buffer: &Buffer, indirect_offset: BufferAddress, ) { self.inner .draw_mesh_tasks_indirect(&indirect_buffer.inner, indirect_offset); } impl_deferred_command_buffer_actions!(); /// Execute a [render bundle][RenderBundle], which is a set of pre-recorded commands /// that can be run together. /// /// Commands in the bundle do not inherit this render pass's current render state, and after the /// bundle has executed, the state is **cleared** (reset to defaults, not the previous state). pub fn execute_bundles<'a, I: IntoIterator>( &mut self, render_bundles: I, ) { let mut render_bundles = render_bundles.into_iter().map(|rb| &rb.inner); self.inner.execute_bundles(&mut render_bundles); } /// Dispatches multiple draw calls from the active vertex buffer(s) based on the contents of the `indirect_buffer`. /// `count` draw calls are issued. /// /// The active vertex buffers can be set with [`RenderPass::set_vertex_buffer`]. /// /// The structure expected in `indirect_buffer` must conform to [`DrawIndirectArgs`](crate::util::DrawIndirectArgs). /// These draw structures are expected to be tightly packed. /// /// Calling this requires the device support [`DownlevelFlags::INDIRECT_EXECUTION`]. /// /// This drawing command uses the current render state, as set by preceding `set_*()` methods. /// It is not affected by changes to the state that are performed after it is called. pub fn multi_draw_indirect( &mut self, indirect_buffer: &Buffer, indirect_offset: BufferAddress, count: u32, ) { self.inner .multi_draw_indirect(&indirect_buffer.inner, indirect_offset, count); } /// Dispatches multiple draw calls from the active index buffer and the active vertex buffers, /// based on the contents of the `indirect_buffer`. `count` draw calls are issued. /// /// The active index buffer can be set with [`RenderPass::set_index_buffer`], while the active /// vertex buffers can be set with [`RenderPass::set_vertex_buffer`]. /// /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirectArgs`](crate::util::DrawIndexedIndirectArgs). /// These draw structures are expected to be tightly packed. /// /// Calling this requires the device support [`DownlevelFlags::INDIRECT_EXECUTION`]. /// /// This drawing command uses the current render state, as set by preceding `set_*()` methods. /// It is not affected by changes to the state that are performed after it is called. pub fn multi_draw_indexed_indirect( &mut self, indirect_buffer: &Buffer, indirect_offset: BufferAddress, count: u32, ) { self.inner .multi_draw_indexed_indirect(&indirect_buffer.inner, indirect_offset, count); } /// Dispatches multiple draw calls based on the contents of the `indirect_buffer`. /// `count` draw calls are issued. /// /// The structure expected in the `indirect_buffer` must conform to [`DispatchIndirectArgs`](crate::util::DispatchIndirectArgs). /// /// This drawing command uses the current render state, as set by preceding `set_*()` methods. /// It is not affected by changes to the state that are performed after it is called. pub fn multi_draw_mesh_tasks_indirect( &mut self, indirect_buffer: &Buffer, indirect_offset: BufferAddress, count: u32, ) { self.inner .multi_draw_mesh_tasks_indirect(&indirect_buffer.inner, indirect_offset, count); } #[cfg(custom)] /// Returns custom implementation of RenderPass (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// [`Features::MULTI_DRAW_INDIRECT_COUNT`] must be enabled on the device in order to call these functions. impl RenderPass<'_> { /// Dispatches multiple draw calls from the active vertex buffer(s) based on the contents of the `indirect_buffer`. /// The count buffer is read to determine how many draws to issue. /// /// The indirect buffer must be long enough to account for `max_count` draws, however only `count` /// draws will be read. If `count` is greater than `max_count`, `max_count` will be used. /// /// The active vertex buffers can be set with [`RenderPass::set_vertex_buffer`]. /// /// The structure expected in `indirect_buffer` must conform to [`DrawIndirectArgs`](crate::util::DrawIndirectArgs). /// These draw structures are expected to be tightly packed. /// /// The structure expected in `count_buffer` is the following: /// /// ```rust /// #[repr(C)] /// struct DrawIndirectCount { /// count: u32, // Number of draw calls to issue. /// } /// ``` /// /// This drawing command uses the current render state, as set by preceding `set_*()` methods. /// It is not affected by changes to the state that are performed after it is called. pub fn multi_draw_indirect_count( &mut self, indirect_buffer: &Buffer, indirect_offset: BufferAddress, count_buffer: &Buffer, count_offset: BufferAddress, max_count: u32, ) { self.inner.multi_draw_indirect_count( &indirect_buffer.inner, indirect_offset, &count_buffer.inner, count_offset, max_count, ); } /// Dispatches multiple draw calls from the active index buffer and the active vertex buffers, /// based on the contents of the `indirect_buffer`. The count buffer is read to determine how many draws to issue. /// /// The indirect buffer must be long enough to account for `max_count` draws, however only `count` /// draws will be read. If `count` is greater than `max_count`, `max_count` will be used. /// /// The active index buffer can be set with [`RenderPass::set_index_buffer`], while the active /// vertex buffers can be set with [`RenderPass::set_vertex_buffer`]. /// /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirectArgs`](crate::util::DrawIndexedIndirectArgs). /// /// These draw structures are expected to be tightly packed. /// /// The structure expected in `count_buffer` is the following: /// /// ```rust /// #[repr(C)] /// struct DrawIndexedIndirectCount { /// count: u32, // Number of draw calls to issue. /// } /// ``` /// /// This drawing command uses the current render state, as set by preceding `set_*()` methods. /// It is not affected by changes to the state that are performed after it is called. pub fn multi_draw_indexed_indirect_count( &mut self, indirect_buffer: &Buffer, indirect_offset: BufferAddress, count_buffer: &Buffer, count_offset: BufferAddress, max_count: u32, ) { self.inner.multi_draw_indexed_indirect_count( &indirect_buffer.inner, indirect_offset, &count_buffer.inner, count_offset, max_count, ); } /// Dispatches multiple draw calls based on the contents of the `indirect_buffer`. The count buffer is read to determine how many draws to issue. /// /// The indirect buffer must be long enough to account for `max_count` draws, however only `count` /// draws will be read. If `count` is greater than `max_count`, `max_count` will be used. /// /// The structure expected in the `indirect_buffer` must conform to [`DispatchIndirectArgs`](crate::util::DispatchIndirectArgs). /// /// These draw structures are expected to be tightly packed. /// /// This drawing command uses the current render state, as set by preceding `set_*()` methods. /// It is not affected by changes to the state that are performed after it is called. pub fn multi_draw_mesh_tasks_indirect_count( &mut self, indirect_buffer: &Buffer, indirect_offset: BufferAddress, count_buffer: &Buffer, count_offset: BufferAddress, max_count: u32, ) { self.inner.multi_draw_mesh_tasks_indirect_count( &indirect_buffer.inner, indirect_offset, &count_buffer.inner, count_offset, max_count, ); } } /// [`Features::IMMEDIATES`] must be enabled on the device in order to call these functions. impl RenderPass<'_> { /// Set immediate data for subsequent draw calls. /// /// Write the bytes in `data` at offset `offset` within immediate data /// storage. Both `offset` and the length of `data` must be /// multiples of [`crate::IMMEDIATE_DATA_ALIGNMENT`], which is always 4. /// /// For example, if `offset` is `4` and `data` is eight bytes long, this /// call will write `data` to bytes `4..12` of immediate data storage. pub fn set_immediates(&mut self, offset: u32, data: &[u8]) { self.inner.set_immediates(offset, data); } } /// [`Features::TIMESTAMP_QUERY_INSIDE_PASSES`] must be enabled on the device in order to call these functions. impl RenderPass<'_> { /// Issue a timestamp command at this point in the queue. The /// timestamp will be written to the specified query set, at the specified index. /// /// Must be multiplied by [`Queue::get_timestamp_period`] to get /// the value in nanoseconds. Absolute values have no meaning, /// but timestamps can be subtracted to get the time it takes /// for a string of operations to complete. pub fn write_timestamp(&mut self, query_set: &QuerySet, query_index: u32) { self.inner.write_timestamp(&query_set.inner, query_index); } } impl RenderPass<'_> { /// Start a occlusion query on this render pass. It can be ended with /// [`end_occlusion_query`](Self::end_occlusion_query). /// Occlusion queries may not be nested. pub fn begin_occlusion_query(&mut self, query_index: u32) { self.inner.begin_occlusion_query(query_index); } /// End the occlusion query on this render pass. It can be started with /// [`begin_occlusion_query`](Self::begin_occlusion_query). /// Occlusion queries may not be nested. pub fn end_occlusion_query(&mut self) { self.inner.end_occlusion_query(); } } /// [`Features::PIPELINE_STATISTICS_QUERY`] must be enabled on the device in order to call these functions. impl RenderPass<'_> { /// Start a pipeline statistics query on this render pass. It can be ended with /// [`end_pipeline_statistics_query`](Self::end_pipeline_statistics_query). /// Pipeline statistics queries may not be nested. /// /// The amount of information collected by this query, and the space occupied in the query set, /// is determined by the [`PipelineStatisticsTypes`] the query set was created with. /// `query_index` is the index of the first query result slot that will be written to, and /// `query_set` must have sufficient size to hold all results written starting at that slot. pub fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, query_index: u32) { self.inner .begin_pipeline_statistics_query(&query_set.inner, query_index); } /// End the pipeline statistics query on this render pass. It can be started with /// [`begin_pipeline_statistics_query`](Self::begin_pipeline_statistics_query). /// Pipeline statistics queries may not be nested. pub fn end_pipeline_statistics_query(&mut self) { self.inner.end_pipeline_statistics_query(); } } /// Describes the timestamp writes of a render pass. /// /// For use with [`RenderPassDescriptor`]. /// At least one of [`Self::beginning_of_pass_write_index`] and [`Self::end_of_pass_write_index`] /// must be `Some`. /// /// Corresponds to [WebGPU `GPURenderPassTimestampWrite`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpurenderpasstimestampwrites). #[derive(Clone, Debug)] pub struct RenderPassTimestampWrites<'a> { /// The query set to write to. pub query_set: &'a QuerySet, /// The index of the query set at which a start timestamp of this pass is written, if any. pub beginning_of_pass_write_index: Option, /// The index of the query set at which an end timestamp of this pass is written, if any. pub end_of_pass_write_index: Option, } #[cfg(send_sync)] static_assertions::assert_impl_all!(RenderPassTimestampWrites<'_>: Send, Sync); /// Describes a color attachment to a [`RenderPass`]. /// /// For use with [`RenderPassDescriptor`]. /// /// Corresponds to [WebGPU `GPURenderPassColorAttachment`]( /// https://gpuweb.github.io/gpuweb/#color-attachments). #[derive(Clone, Debug)] pub struct RenderPassColorAttachment<'tex> { /// The view to use as an attachment. pub view: &'tex TextureView, /// The depth slice index of a 3D view. It must not be provided if the view is not 3D. pub depth_slice: Option, /// The view that will receive the resolved output if multisampling is used. /// /// If set, it is always written to, regardless of how [`Self::ops`] is configured. pub resolve_target: Option<&'tex TextureView>, /// What operations will be performed on this color attachment. pub ops: Operations, } #[cfg(send_sync)] static_assertions::assert_impl_all!(RenderPassColorAttachment<'_>: Send, Sync); /// Describes a depth/stencil attachment to a [`RenderPass`]. /// /// For use with [`RenderPassDescriptor`]. /// /// Corresponds to [WebGPU `GPURenderPassDepthStencilAttachment`]( /// https://gpuweb.github.io/gpuweb/#depth-stencil-attachments). #[derive(Clone, Debug)] pub struct RenderPassDepthStencilAttachment<'tex> { /// The view to use as an attachment. pub view: &'tex TextureView, /// What operations will be performed on the depth part of the attachment. pub depth_ops: Option>, /// What operations will be performed on the stencil part of the attachment. pub stencil_ops: Option>, } #[cfg(send_sync)] static_assertions::assert_impl_all!(RenderPassDepthStencilAttachment<'_>: Send, Sync); /// Describes the attachments of a render pass. /// /// For use with [`CommandEncoder::begin_render_pass`]. /// /// Corresponds to [WebGPU `GPURenderPassDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpurenderpassdescriptor). #[derive(Clone, Debug, Default)] pub struct RenderPassDescriptor<'a> { /// Debug label of the render pass. This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// The color attachments of the render pass. pub color_attachments: &'a [Option>], /// The depth and stencil attachment of the render pass, if any. pub depth_stencil_attachment: Option>, /// Defines which timestamp values will be written for this pass, and where to write them to. /// /// Requires [`Features::TIMESTAMP_QUERY`] to be enabled. pub timestamp_writes: Option>, /// Defines where the occlusion query results will be stored for this pass. pub occlusion_query_set: Option<&'a QuerySet>, /// The mask of multiview image layers to use for this render pass. For example, if you wish /// to render to the first 2 layers, you would use 3=0b11. If you wanted ro render to only the /// 2nd layer, you would use 2=0b10. If you aren't using multiview this should be `None`. /// /// Note that setting bits higher than the number of texture layers is a validation error. /// /// This doesn't influence load/store/clear/etc operations, as those are defined for attachments, /// therefore affecting all attachments. Meaning, this affects only any shaders executed on the `RenderPass`. pub multiview_mask: Option, } #[cfg(send_sync)] static_assertions::assert_impl_all!(RenderPassDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/render_pipeline.rs ================================================ use core::num::NonZeroU32; use crate::*; /// Handle to a rendering (graphics) pipeline. /// /// A `RenderPipeline` object represents a graphics pipeline and its stages, bindings, vertex /// buffers and targets. It can be created with [`Device::create_render_pipeline`]. /// /// Corresponds to [WebGPU `GPURenderPipeline`](https://gpuweb.github.io/gpuweb/#render-pipeline). #[derive(Debug, Clone)] pub struct RenderPipeline { pub(crate) inner: dispatch::DispatchRenderPipeline, } #[cfg(send_sync)] static_assertions::assert_impl_all!(RenderPipeline: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(RenderPipeline => .inner); impl RenderPipeline { /// Get an object representing the bind group layout at a given index. /// /// If this pipeline was created with a [default layout][RenderPipelineDescriptor::layout], then /// bind groups created with the returned `BindGroupLayout` can only be used with this pipeline. /// /// This method will raise a validation error if there is no bind group layout at `index`. pub fn get_bind_group_layout(&self, index: u32) -> BindGroupLayout { let layout = self.inner.get_bind_group_layout(index); BindGroupLayout { inner: layout } } #[cfg(custom)] /// Returns custom implementation of RenderPipeline (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// Specifies an interpretation of the bytes of a vertex buffer as vertex attributes. /// /// Use this in a [`RenderPipelineDescriptor`] to describe the format of the vertex buffers that /// are passed to [`RenderPass::set_vertex_buffer()`]. /// /// Corresponds to [WebGPU `GPUVertexBufferLayout`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpuvertexbufferlayout). /// /// # Example /// /// The following example defines a `struct` with three fields, /// and a [`VertexBufferLayout`] that contains [`VertexAttribute`]s for each field, /// using the [`vertex_attr_array!`] macro to compute attribute offsets: /// /// ``` /// #[repr(C, packed)] /// struct Vertex { /// foo: [f32; 2], /// bar: f32, /// baz: [u16; 4], /// } /// /// impl Vertex { /// /// Layout to use with a buffer whose contents are a `[Vertex]`. /// pub const LAYOUT: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { /// array_stride: size_of::() as wgpu::BufferAddress, /// step_mode: wgpu::VertexStepMode::Vertex, /// attributes: &wgpu::vertex_attr_array![ /// 0 => Float32x2, /// 1 => Float32, /// 2 => Uint16x4, /// ], /// }; /// } /// /// # assert_eq!(Vertex::LAYOUT.attributes[2].offset, Vertex::LAYOUT.array_stride - 2 * 4); #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub struct VertexBufferLayout<'a> { /// The stride, in bytes, between elements of this buffer (between vertices). /// /// This must be a multiple of [`VERTEX_ALIGNMENT`]. pub array_stride: BufferAddress, /// How often this vertex buffer is "stepped" forward. pub step_mode: VertexStepMode, /// The list of attributes which comprise a single vertex. pub attributes: &'a [VertexAttribute], } static_assertions::assert_impl_all!(VertexBufferLayout<'_>: Send, Sync); /// Describes the vertex processing in a render pipeline. /// /// For use in [`RenderPipelineDescriptor`]. /// /// Corresponds to [WebGPU `GPUVertexState`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpuvertexstate). #[derive(Clone, Debug)] pub struct VertexState<'a> { /// The compiled shader module for this stage. pub module: &'a ShaderModule, /// The name of the entry point in the compiled shader to use. /// /// If [`Some`], there must be a vertex-stage shader entry point with this name in `module`. /// Otherwise, expect exactly one vertex-stage entry point in `module`, which will be /// selected. // NOTE: keep phrasing in sync. with `ComputePipelineDescriptor::entry_point` // NOTE: keep phrasing in sync. with `FragmentState::entry_point` pub entry_point: Option<&'a str>, /// Advanced options for when this pipeline is compiled /// /// This implements `Default`, and for most users can be set to `Default::default()` pub compilation_options: PipelineCompilationOptions<'a>, /// The format of any vertex buffers used with this pipeline via /// [`RenderPass::set_vertex_buffer()`]. /// /// The attribute locations and types specified in this layout must match the /// locations and types of the inputs to the `entry_point` function. pub buffers: &'a [VertexBufferLayout<'a>], } #[cfg(send_sync)] static_assertions::assert_impl_all!(VertexState<'_>: Send, Sync); /// Describes the fragment processing in a render pipeline. /// /// For use in [`RenderPipelineDescriptor`]. /// /// Corresponds to [WebGPU `GPUFragmentState`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpufragmentstate). #[derive(Clone, Debug)] pub struct FragmentState<'a> { /// The compiled shader module for this stage. pub module: &'a ShaderModule, /// The name of the entry point in the compiled shader to use. /// /// If [`Some`], there must be a `@fragment` shader entry point with this name in `module`. /// Otherwise, expect exactly one fragment-stage entry point in `module`, which will be /// selected. // NOTE: keep phrasing in sync. with `ComputePipelineDescriptor::entry_point` // NOTE: keep phrasing in sync. with `VertexState::entry_point` pub entry_point: Option<&'a str>, /// Advanced options for when this pipeline is compiled /// /// This implements `Default`, and for most users can be set to `Default::default()` pub compilation_options: PipelineCompilationOptions<'a>, /// The color state of the render targets. pub targets: &'a [Option], } #[cfg(send_sync)] static_assertions::assert_impl_all!(FragmentState<'_>: Send, Sync); /// Describes the task shader stage in a mesh shader pipeline. /// /// For use in [`MeshPipelineDescriptor`] #[derive(Clone, Debug)] pub struct TaskState<'a> { /// The compiled shader module for this stage. pub module: &'a ShaderModule, /// The name of the task shader entry point in the shader module to use. /// /// If [`Some`], there must be a task shader entry point with the given name /// in `module`. Otherwise, there must be exactly one task shader entry /// point in `module`, which will be selected. pub entry_point: Option<&'a str>, /// Advanced options for when this pipeline is compiled. /// /// This implements `Default`, and for most users can be set to `Default::default()` pub compilation_options: PipelineCompilationOptions<'a>, } #[cfg(send_sync)] static_assertions::assert_impl_all!(TaskState<'_>: Send, Sync); /// Describes the mesh shader stage in a mesh shader pipeline. /// /// For use in [`MeshPipelineDescriptor`] #[derive(Clone, Debug)] pub struct MeshState<'a> { /// The compiled shader module for this stage. pub module: &'a ShaderModule, /// The name of the entry point in the compiled shader to use. /// /// If [`Some`], there must be a vertex-stage shader entry point with this name in `module`. /// Otherwise, expect exactly one vertex-stage entry point in `module`, which will be /// selected. pub entry_point: Option<&'a str>, /// Advanced options for when this pipeline is compiled /// /// This implements `Default`, and for most users can be set to `Default::default()` pub compilation_options: PipelineCompilationOptions<'a>, } #[cfg(send_sync)] static_assertions::assert_impl_all!(MeshState<'_>: Send, Sync); /// Describes a render (graphics) pipeline. /// /// For use with [`Device::create_render_pipeline`]. /// /// Corresponds to [WebGPU `GPURenderPipelineDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpurenderpipelinedescriptor). #[derive(Clone, Debug)] pub struct RenderPipelineDescriptor<'a> { /// Debug label of the pipeline. This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// The layout of bind groups for this pipeline. /// /// If this is set, then [`Device::create_render_pipeline`] will raise a validation error if /// the layout doesn't match what the shader module(s) expect. /// /// Using the same [`PipelineLayout`] for many [`RenderPipeline`] or [`ComputePipeline`] /// pipelines guarantees that you don't have to rebind any resources when switching between /// those pipelines. /// /// ## Default pipeline layout /// /// If `layout` is `None`, then the pipeline has a [default layout] created and used instead. /// The default layout is deduced from the shader modules. /// /// You can use [`RenderPipeline::get_bind_group_layout`] to create bind groups for use with the /// default layout. However, these bind groups cannot be used with any other pipelines. This is /// convenient for simple pipelines, but using an explicit layout is recommended in most cases. /// /// [default layout]: https://www.w3.org/TR/webgpu/#default-pipeline-layout pub layout: Option<&'a PipelineLayout>, /// The compiled vertex stage, its entry point, and the input buffers layout. pub vertex: VertexState<'a>, /// The properties of the pipeline at the primitive assembly and rasterization level. pub primitive: PrimitiveState, /// The effect of draw calls on the depth and stencil aspects of the output target, if any. pub depth_stencil: Option, /// The multi-sampling properties of the pipeline. pub multisample: MultisampleState, /// The compiled fragment stage, its entry point, and the color targets. pub fragment: Option>, /// If the pipeline will be used with a multiview render pass, this indicates what multiview /// mask the render pass will be used with. The masks must match exactly. /// /// For example, if you wish to render to the first 2 layers, you would use 3=0b11. If you /// wanted to render to only the 2nd layer, you would use 2=0b10. If you aren't using /// multiview this should be `None`. pub multiview_mask: Option, /// The pipeline cache to use when creating this pipeline. pub cache: Option<&'a PipelineCache>, } #[cfg(send_sync)] static_assertions::assert_impl_all!(RenderPipelineDescriptor<'_>: Send, Sync); /// Describes a mesh shader (graphics) pipeline. /// /// For use with [`Device::create_mesh_pipeline`]. A mesh pipeline is very much /// like a render pipeline, except that instead of [`RenderPass::draw`] it is /// invoked with [`RenderPass::draw_mesh_tasks`], and instead of a vertex shader /// and a fragment shader: /// /// - [`task`] specifies an optional task shader entry point, which determines how /// many groups of mesh shaders to dispatch. /// /// - [`mesh`] specifies a mesh shader entry point, which generates groups of /// primitives to draw /// /// - [`fragment`] specifies as fragment shader for drawing those primitives, /// just like in an ordinary render pipeline. /// /// The key difference is that, whereas a vertex shader is invoked on the /// elements of vertex buffers, the task shader gets to decide how many mesh /// shader workgroups to make, and then each mesh shader workgroup gets to /// decide which primitives it wants to generate, and what their vertex /// attributes are. Task and mesh shaders can use whatever they please as /// inputs, like a compute shader. However, they cannot use specialized vertex /// or index buffers. /// /// A mesh pipeline is invoked by [`RenderPass::draw_mesh_tasks`], which looks /// like a compute shader dispatch with [`ComputePass::dispatch_workgroups`]: /// you pass `x`, `y`, and `z` values indicating the number of task shaders to /// invoke in parallel. The output value of the first thread in a task shader /// workgroup determines how many mesh workgroups should be dispatched from there. /// Those mesh workgroups also get a special payload passed from the task shader. /// /// If the task shader is omitted, then the (`x`, `y`, `z`) parameters to /// `draw_mesh_tasks` are used to decide how many invocations of the mesh shader /// to invoke directly, without a task payload. /// /// [vertex formats]: wgpu_types::VertexFormat /// [`task`]: Self::task /// [`mesh`]: Self::mesh /// [`fragment`]: Self::fragment #[derive(Clone, Debug)] pub struct MeshPipelineDescriptor<'a> { /// Debug label of the pipeline. This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// The layout of bind groups for this pipeline. /// /// If this is set, then [`Device::create_render_pipeline`] will raise a validation error if /// the layout doesn't match what the shader module(s) expect. /// /// Using the same [`PipelineLayout`] for many [`RenderPipeline`] or [`ComputePipeline`] /// pipelines guarantees that you don't have to rebind any resources when switching between /// those pipelines. /// /// ## Default pipeline layout /// /// If `layout` is `None`, then the pipeline has a [default layout] created and used instead. /// The default layout is deduced from the shader modules. /// /// You can use [`RenderPipeline::get_bind_group_layout`] to create bind groups for use with the /// default layout. However, these bind groups cannot be used with any other pipelines. This is /// convenient for simple pipelines, but using an explicit layout is recommended in most cases. /// /// [default layout]: https://www.w3.org/TR/webgpu/#default-pipeline-layout pub layout: Option<&'a PipelineLayout>, /// The mesh pipeline's task shader. /// /// If this is `None`, the mesh pipeline has no task shader. Executing a /// mesh drawing command simply dispatches a grid of mesh shaders directly. /// /// [`draw_mesh_tasks`]: RenderPass::draw_mesh_tasks pub task: Option>, /// The compiled mesh stage and its entry point pub mesh: MeshState<'a>, /// The properties of the pipeline at the primitive assembly and rasterization level. pub primitive: PrimitiveState, /// The effect of draw calls on the depth and stencil aspects of the output target, if any. pub depth_stencil: Option, /// The multi-sampling properties of the pipeline. pub multisample: MultisampleState, /// The compiled fragment stage, its entry point, and the color targets. pub fragment: Option>, /// If the pipeline will be used with a multiview render pass, this indicates how many array /// layers the attachments will have. pub multiview: Option, /// The pipeline cache to use when creating this pipeline. pub cache: Option<&'a PipelineCache>, } #[cfg(send_sync)] static_assertions::assert_impl_all!(MeshPipelineDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/sampler.rs ================================================ use crate::*; /// Handle to a sampler. /// /// A `Sampler` object defines how a pipeline will sample from a [`TextureView`]. Samplers define /// image filters (including anisotropy) and address (wrapping) modes, among other things. See /// the documentation for [`SamplerDescriptor`] for more information. /// /// It can be created with [`Device::create_sampler`]. /// /// Corresponds to [WebGPU `GPUSampler`](https://gpuweb.github.io/gpuweb/#sampler-interface). #[derive(Debug, Clone)] pub struct Sampler { pub(crate) inner: dispatch::DispatchSampler, } #[cfg(send_sync)] static_assertions::assert_impl_all!(Sampler: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(Sampler => .inner); impl Sampler { #[cfg(custom)] /// Returns custom implementation of Sampler (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// Describes a [`Sampler`]. /// /// For use with [`Device::create_sampler`]. /// /// Corresponds to [WebGPU `GPUSamplerDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpusamplerdescriptor). pub type SamplerDescriptor<'a> = wgt::SamplerDescriptor>; static_assertions::assert_impl_all!(SamplerDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/shader_module.rs ================================================ use alloc::{string::String, vec::Vec}; use core::{future::Future, marker::PhantomData}; use crate::*; /// Handle to a compiled shader module. /// /// A `ShaderModule` represents a compiled shader module on the GPU. It can be created by passing /// source code to [`Device::create_shader_module`]. MSL shader or SPIR-V binary can also be passed /// directly using [`Device::create_shader_module_passthrough`]. Shader modules are used to define /// programmable stages of a pipeline. /// /// Corresponds to [WebGPU `GPUShaderModule`](https://gpuweb.github.io/gpuweb/#shader-module). #[derive(Debug, Clone)] pub struct ShaderModule { pub(crate) inner: dispatch::DispatchShaderModule, } #[cfg(send_sync)] static_assertions::assert_impl_all!(ShaderModule: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(ShaderModule => .inner); impl ShaderModule { /// Get the compilation info for the shader module. pub fn get_compilation_info(&self) -> impl Future + WasmNotSend { self.inner.get_compilation_info() } #[cfg(custom)] /// Returns custom implementation of ShaderModule (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// Compilation information for a shader module. /// /// Corresponds to [WebGPU `GPUCompilationInfo`](https://gpuweb.github.io/gpuweb/#gpucompilationinfo). /// The source locations use bytes, and index a UTF-8 encoded string. #[derive(Debug, Clone)] pub struct CompilationInfo { /// The messages from the shader compilation process. pub messages: Vec, } /// A single message from the shader compilation process. /// /// Roughly corresponds to [`GPUCompilationMessage`](https://www.w3.org/TR/webgpu/#gpucompilationmessage), /// except that the location uses UTF-8 for all positions. #[derive(Debug, Clone)] pub struct CompilationMessage { /// The text of the message. pub message: String, /// The type of the message. pub message_type: CompilationMessageType, /// Where in the source code the message points at. pub location: Option, } /// The type of a compilation message. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CompilationMessageType { /// An error message. Error, /// A warning message. Warning, /// An informational message. Info, } /// A human-readable representation for a span, tailored for text source. /// /// Roughly corresponds to the positional members of [`GPUCompilationMessage`][gcm] from /// the WebGPU specification, except /// - `offset` and `length` are in bytes (UTF-8 code units), instead of UTF-16 code units. /// - `line_position` is in bytes (UTF-8 code units), and is usually not directly intended for humans. /// /// [gcm]: https://www.w3.org/TR/webgpu/#gpucompilationmessage #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct SourceLocation { /// 1-based line number. pub line_number: u32, /// 1-based column in code units (in bytes) of the start of the span. /// Remember to convert accordingly when displaying to the user. pub line_position: u32, /// 0-based Offset in code units (in bytes) of the start of the span. pub offset: u32, /// Length in code units (in bytes) of the span. pub length: u32, } #[cfg(all(feature = "wgsl", wgpu_core))] impl From> for CompilationInfo { fn from(value: crate::naga::error::ShaderError) -> Self { use alloc::{string::ToString, vec}; CompilationInfo { messages: vec![CompilationMessage { message: value.to_string(), message_type: CompilationMessageType::Error, location: value.inner.location(&value.source).map(Into::into), }], } } } #[cfg(feature = "glsl")] impl From> for CompilationInfo { fn from(value: naga::error::ShaderError) -> Self { use alloc::string::ToString; let messages = value .inner .errors .into_iter() .map(|err| CompilationMessage { message: err.to_string(), message_type: CompilationMessageType::Error, location: err.location(&value.source).map(Into::into), }) .collect(); CompilationInfo { messages } } } #[cfg(feature = "spirv")] impl From> for CompilationInfo { fn from(value: naga::error::ShaderError) -> Self { use alloc::{string::ToString, vec}; CompilationInfo { messages: vec![CompilationMessage { message: value.to_string(), message_type: CompilationMessageType::Error, location: None, }], } } } #[cfg(any(wgpu_core, naga))] impl From< crate::naga::error::ShaderError>, > for CompilationInfo { fn from( value: crate::naga::error::ShaderError< crate::naga::WithSpan, >, ) -> Self { use alloc::{string::ToString, vec}; CompilationInfo { messages: vec![CompilationMessage { message: value.to_string(), message_type: CompilationMessageType::Error, location: value.inner.location(&value.source).map(Into::into), }], } } } #[cfg(any(wgpu_core, naga))] impl From for SourceLocation { fn from(value: crate::naga::SourceLocation) -> Self { SourceLocation { length: value.length, offset: value.offset, line_number: value.line_number, line_position: value.line_position, } } } /// Source of a shader module. /// /// The source will be parsed and validated. /// /// Any necessary shader translation (e.g. from WGSL to SPIR-V or vice versa) /// will be done internally by wgpu. /// /// This type is unique to the Rust API of `wgpu`. In the WebGPU specification, /// only WGSL source code strings are accepted. #[cfg_attr(feature = "naga-ir", expect(clippy::large_enum_variant))] #[derive(Clone, Debug)] #[non_exhaustive] pub enum ShaderSource<'a> { /// SPIR-V module represented as a slice of words. /// /// See also: [`util::make_spirv`], [`include_spirv`] #[cfg(feature = "spirv")] SpirV(alloc::borrow::Cow<'a, [u32]>), /// GLSL module as a string slice. /// /// Note: GLSL is not yet fully supported and must be a specific ShaderStage. #[cfg(feature = "glsl")] Glsl { /// The source code of the shader. shader: alloc::borrow::Cow<'a, str>, /// The shader stage that the shader targets. For example, `naga::ShaderStage::Vertex` stage: naga::ShaderStage, /// Key-value pairs to represent defines sent to the glsl preprocessor. /// /// If the same name is defined multiple times, the last value is used. defines: &'a [(&'a str, &'a str)], }, /// WGSL module as a string slice. #[cfg(feature = "wgsl")] Wgsl(alloc::borrow::Cow<'a, str>), /// Naga module. #[cfg(feature = "naga-ir")] Naga(alloc::borrow::Cow<'static, naga::Module>), /// Dummy variant because `Naga` doesn't have a lifetime and without enough active features it /// could be the last one active. #[doc(hidden)] Dummy(PhantomData<&'a ()>), } static_assertions::assert_impl_all!(ShaderSource<'_>: Send, Sync); /// Descriptor for use with [`Device::create_shader_module`]. /// /// Corresponds to [WebGPU `GPUShaderModuleDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpushadermoduledescriptor). #[derive(Clone, Debug)] pub struct ShaderModuleDescriptor<'a> { /// Debug label of the shader module. This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// Source code for the shader. pub source: ShaderSource<'a>, } static_assertions::assert_impl_all!(ShaderModuleDescriptor<'_>: Send, Sync); /// Descriptor for a shader module given by any of several sources. /// At least one of the shader types that may be used by the backend must be `Some` /// /// This type is unique to the Rust API of `wgpu`. In the WebGPU specification, /// only WGSL source code strings are accepted. pub type ShaderModuleDescriptorPassthrough<'a> = wgt::CreateShaderModuleDescriptorPassthrough<'a, Label<'a>>; ================================================ FILE: wgpu/src/api/surface.rs ================================================ use alloc::{boxed::Box, string::String, vec, vec::Vec}; #[cfg(wgpu_core)] use core::ops::Deref; use core::{error, fmt}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use crate::util::Mutex; use crate::*; /// Describes a [`Surface`]. /// /// For use with [`Surface::configure`]. /// /// Corresponds to [WebGPU `GPUCanvasConfiguration`]( /// https://gpuweb.github.io/gpuweb/#canvas-configuration). pub type SurfaceConfiguration = wgt::SurfaceConfiguration>; static_assertions::assert_impl_all!(SurfaceConfiguration: Send, Sync); /// Handle to a presentable surface. /// /// A `Surface` represents a platform-specific surface (e.g. a window) onto which rendered images may /// be presented. A `Surface` may be created with the function [`Instance::create_surface`]. /// /// This type is unique to the Rust API of `wgpu`. In the WebGPU specification, /// [`GPUCanvasContext`](https://gpuweb.github.io/gpuweb/#canvas-context) /// serves a similar role. pub struct Surface<'window> { /// Additional surface data returned by [`InstanceInterface::create_surface`][cs]. /// /// [cs]: crate::dispatch::InstanceInterface::create_surface pub(crate) inner: dispatch::DispatchSurface, // Stores the latest `SurfaceConfiguration` that was set using `Surface::configure`. // It is required to set the attributes of the `SurfaceTexture` in the // `Surface::get_current_texture` method. // Because the `Surface::configure` method operates on an immutable reference this type has to // be wrapped in a mutex and since the configuration is only supplied after the surface has // been created is is additionally wrapped in an option. pub(crate) config: Mutex>, /// Optionally, keep the source of the handle used for the surface alive. /// /// This is useful for platforms where the surface is created from a window and the surface /// would become invalid when the window is dropped. /// /// SAFETY: This field must be dropped *after* all other fields to ensure proper cleanup. pub(crate) _handle_source: Option>, } impl Surface<'_> { /// Returns the capabilities of the surface when used with the given adapter. /// /// Returns specified values (see [`SurfaceCapabilities`]) if surface is incompatible with the adapter. pub fn get_capabilities(&self, adapter: &Adapter) -> SurfaceCapabilities { self.inner.get_capabilities(&adapter.inner) } /// Return a default `SurfaceConfiguration` from width and height to use for the [`Surface`] with this adapter. /// /// Returns None if the surface isn't supported by this adapter pub fn get_default_config( &self, adapter: &Adapter, width: u32, height: u32, ) -> Option { let caps = self.get_capabilities(adapter); Some(SurfaceConfiguration { usage: wgt::TextureUsages::RENDER_ATTACHMENT, format: *caps.formats.first()?, width, height, desired_maximum_frame_latency: 2, present_mode: *caps.present_modes.first()?, alpha_mode: wgt::CompositeAlphaMode::Auto, view_formats: vec![], }) } /// Initializes [`Surface`] for presentation. /// /// If the surface is already configured, this will wait for the GPU to come idle /// before recreating the swapchain to prevent race conditions. /// /// # Validation Errors /// - Submissions that happen _during_ the configure may cause the /// internal wait-for-idle to fail, raising a validation error. /// /// # Panics /// /// - A old [`SurfaceTexture`] is still alive referencing an old surface. /// - Texture format requested is unsupported on the surface. /// - `config.width` or `config.height` is zero. pub fn configure(&self, device: &Device, config: &SurfaceConfiguration) { self.inner.configure(&device.inner, config); let mut conf = self.config.lock(); *conf = Some(config.clone()); } /// Returns the current configuration of [`Surface`], if configured. /// /// This is similar to [WebGPU `GPUcCanvasContext::getConfiguration`](https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-getconfiguration). pub fn get_configuration(&self) -> Option { self.config.lock().clone() } /// Returns the next texture to be presented by the surface for drawing. /// /// In order to present the [`SurfaceTexture`] returned by this method, /// first a [`Queue::submit`] needs to be done with some work rendering to this texture. /// Then [`SurfaceTexture::present`] needs to be called. /// /// If a [`SurfaceTexture`] referencing this surface is alive when [`Surface::configure()`] /// is called, the configure call will panic. /// /// See the documentation of [`CurrentSurfaceTexture`] for how each possible result /// should be handled. pub fn get_current_texture(&self) -> CurrentSurfaceTexture { let (texture, status, detail) = self.inner.get_current_texture(); let suboptimal = match status { SurfaceStatus::Good => false, SurfaceStatus::Suboptimal => true, SurfaceStatus::Timeout => return CurrentSurfaceTexture::Timeout, SurfaceStatus::Occluded => return CurrentSurfaceTexture::Occluded, SurfaceStatus::Outdated => return CurrentSurfaceTexture::Outdated, SurfaceStatus::Lost => return CurrentSurfaceTexture::Lost, SurfaceStatus::Validation => return CurrentSurfaceTexture::Validation, }; let guard = self.config.lock(); let config = guard .as_ref() .expect("This surface has not been configured yet."); let descriptor = TextureDescriptor { label: None, size: Extent3d { width: config.width, height: config.height, depth_or_array_layers: 1, }, format: config.format, usage: config.usage, mip_level_count: 1, sample_count: 1, dimension: TextureDimension::D2, view_formats: &[], }; match texture { Some(texture) => { let surface_texture = SurfaceTexture { texture: Texture { inner: texture, descriptor, }, presented: false, detail, }; if suboptimal { CurrentSurfaceTexture::Suboptimal(surface_texture) } else { CurrentSurfaceTexture::Success(surface_texture) } } None => CurrentSurfaceTexture::Lost, } } /// Get the [`wgpu_hal`] surface from this `Surface`. /// /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`], /// and pass that struct to the to the `A` type parameter. /// /// Returns a guard that dereferences to the type of the hal backend /// which implements [`A::Surface`]. /// /// # Types /// /// The returned type depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("Surface")] #[doc = crate::macros::hal_type_metal!("Surface")] #[doc = crate::macros::hal_type_dx12!("Surface")] #[doc = crate::macros::hal_type_gles!("Surface")] /// /// # Errors /// /// This method will return None if: /// - The surface is not from the backend specified by `A`. /// - The surface is from the `webgpu` or `custom` backend. /// /// # Safety /// /// - The returned resource must not be destroyed unless the guard /// is the last reference to it and it is not in use by the GPU. /// The guard and handle may be dropped at any time however. /// - All the safety requirements of wgpu-hal must be upheld. /// /// [`A::Surface`]: hal::Api::Surface #[cfg(wgpu_core)] pub unsafe fn as_hal( &self, ) -> Option + WasmNotSendSync> { let core_surface = self.inner.as_core_opt()?; unsafe { core_surface.context.surface_as_hal::(core_surface) } } #[cfg(custom)] /// Returns custom implementation of Surface (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } // This custom implementation is required because [`Surface::_surface`] doesn't // require [`Debug`](fmt::Debug), which we should not require from the user. impl fmt::Debug for Surface<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Surface") .field( "_handle_source", &if self._handle_source.is_some() { "Some" } else { "None" }, ) .field("inner", &self.inner) .field("config", &self.config) .finish() } } #[cfg(send_sync)] static_assertions::assert_impl_all!(Surface<'_>: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(Surface<'_> => .inner); /// [`Send`]/[`Sync`] blanket trait for [`HasWindowHandle`] used in [`SurfaceTarget`]. pub trait WindowHandle: HasWindowHandle + WasmNotSendSync {} impl WindowHandle for T {} /// Super trait for a pair of display and window handles as used in [`SurfaceTarget`]. pub trait DisplayAndWindowHandle: WindowHandle + HasDisplayHandle {} impl DisplayAndWindowHandle for T where T: WindowHandle + HasDisplayHandle {} /// The window/canvas/surface/swap-chain/etc. a surface is attached to, for use with safe surface creation. /// /// This is either a window or an actual web canvas depending on the platform and /// enabled features. /// Refer to the individual variants for more information. /// /// See also [`SurfaceTargetUnsafe`] for unsafe variants. #[non_exhaustive] pub enum SurfaceTarget<'window> { /// Window and display handle producer. /// /// If the specified display and window handle are not supported by any of the backends, then the surface /// will not be supported by any adapters. /// /// # Errors /// /// - On WebGL2: surface creation returns an error if the browser does not support WebGL2, /// or declines to provide GPU access (such as due to a resource shortage). /// /// # Panics /// /// - On macOS/Metal: will panic if not called on the main thread. /// - On web: will panic if the [`HasWindowHandle`] does not properly refer to a /// canvas element. /// - On all platforms: If [`crate::InstanceDescriptor::display`] was not [`None`] /// but its value is not identical to that returned by [`HasDisplayHandle::display_handle()`]. DisplayAndWindow(Box), /// Window handle producer. /// /// [`HasWindowHandle`]-only version of [`SurfaceTarget::DisplayAndWindow`]. /// /// This requires that the display handle was already passed through /// [`crate::InstanceDescriptor::display`]. Window(Box), /// Surface from a `web_sys::HtmlCanvasElement`. /// /// The `canvas` argument must be a valid `` element to /// create a surface upon. /// /// # Errors /// /// - On WebGL2: surface creation will return an error if the browser does not support WebGL2, /// or declines to provide GPU access (such as due to a resource shortage). #[cfg(web)] Canvas(web_sys::HtmlCanvasElement), /// Surface from a `web_sys::OffscreenCanvas`. /// /// The `canvas` argument must be a valid `OffscreenCanvas` object /// to create a surface upon. /// /// # Errors /// /// - On WebGL2: surface creation will return an error if the browser does not support WebGL2, /// or declines to provide GPU access (such as due to a resource shortage). #[cfg(web)] OffscreenCanvas(web_sys::OffscreenCanvas), } impl<'a> SurfaceTarget<'a> { /// Constructor for [`Self::Window`] without consuming a display handle pub fn from_window_without_display(window: impl WindowHandle + 'a) -> Self { Self::Window(Box::new(window)) } } impl<'a, T> From for SurfaceTarget<'a> where T: DisplayAndWindowHandle + 'a, { fn from(window: T) -> Self { Self::DisplayAndWindow(Box::new(window)) } } /// The window/canvas/surface/swap-chain/etc. a surface is attached to, for use with unsafe surface creation. /// /// This is either a window or an actual web canvas depending on the platform and /// enabled features. /// Refer to the individual variants for more information. /// /// See also [`SurfaceTarget`] for safe variants. #[non_exhaustive] pub enum SurfaceTargetUnsafe { /// Raw window & display handle. /// /// If the specified display and window handle are not supported by any of the backends, then the surface /// will not be supported by any adapters. /// /// If the `raw_display_handle` is not [`None`] here and was not [`None`] in /// [`crate::InstanceDescriptor::display`], their values _must_ be identical. /// /// # Safety /// /// - `raw_window_handle` & `raw_display_handle` must be valid objects to create a surface upon. /// - `raw_window_handle` & `raw_display_handle` must remain valid until after the returned /// [`Surface`] is dropped. RawHandle { /// Raw display handle, underlying display must outlive the surface created from this. raw_display_handle: Option, /// Raw window handle, underlying window must outlive the surface created from this. raw_window_handle: raw_window_handle::RawWindowHandle, }, /// Surface from a DRM device. /// /// If the specified DRM configuration is not supported by any of the backends, then the surface /// will not be supported by any adapters. /// /// # Safety /// /// - All parameters must point to valid DRM values and remain valid for as long as the resulting [`Surface`] exists. /// - The file descriptor (`fd`), plane, connector, and mode configuration must be valid and compatible. #[cfg(all(unix, not(target_vendor = "apple"), not(target_family = "wasm")))] Drm { /// The file descriptor of the DRM device. fd: i32, /// The plane index on which to create the surface. plane: u32, /// The ID of the connector associated with the selected mode. connector_id: u32, /// The display width of the selected mode. width: u32, /// The display height of the selected mode. height: u32, /// The display refresh rate of the selected mode multiplied by 1000 (e.g., 60Hz → 60000). refresh_rate: u32, }, /// Surface from `CoreAnimationLayer`. /// /// # Safety /// /// - layer must be a valid object to create a surface upon. #[cfg(metal)] CoreAnimationLayer(*mut core::ffi::c_void), /// Surface from `IDCompositionVisual`. /// /// # Safety /// /// - visual must be a valid `IDCompositionVisual` to create a surface upon. Its refcount will be incremented internally and kept live as long as the resulting [`Surface`] is live. #[cfg(dx12)] CompositionVisual(*mut core::ffi::c_void), /// Surface from DX12 `DirectComposition` handle. /// /// /// /// # Safety /// /// - surface_handle must be a valid `DirectComposition` handle to create a surface upon. Its lifetime **will not** be internally managed: this handle **should not** be freed before /// the resulting [`Surface`] is destroyed. #[cfg(dx12)] SurfaceHandle(*mut core::ffi::c_void), /// Surface from DX12 `SwapChainPanel`. /// /// # Safety /// /// - visual must be a valid SwapChainPanel to create a surface upon. Its refcount will be incremented internally and kept live as long as the resulting [`Surface`] is live. #[cfg(dx12)] SwapChainPanel(*mut core::ffi::c_void), } impl SurfaceTargetUnsafe { /// Creates a [`SurfaceTargetUnsafe::RawHandle`] from a display and window. /// /// The `display` is optional and may be omitted if it was also passed to /// [`crate::InstanceDescriptor::display`]. If passed to both it must (currently) be identical. /// /// # Safety /// /// - `display` must outlive the resulting surface target /// (and subsequently the surface created for this target). /// - `window` must outlive the resulting surface target /// (and subsequently the surface created for this target). pub unsafe fn from_display_and_window( display: &impl HasDisplayHandle, window: &impl HasWindowHandle, ) -> Result { Ok(Self::RawHandle { raw_display_handle: Some(display.display_handle()?.as_raw()), raw_window_handle: window.window_handle()?.as_raw(), }) } /// Creates a [`SurfaceTargetUnsafe::RawHandle`] from a window. /// /// # Safety /// /// - `window` must outlive the resulting surface target /// (and subsequently the surface created for this target). pub unsafe fn from_window( window: &impl HasWindowHandle, ) -> Result { Ok(Self::RawHandle { raw_display_handle: None, raw_window_handle: window.window_handle()?.as_raw(), }) } } /// [`Instance::create_surface()`] or a related function failed. #[derive(Clone, Debug)] #[non_exhaustive] pub struct CreateSurfaceError { pub(crate) inner: CreateSurfaceErrorKind, } #[derive(Clone, Debug)] pub(crate) enum CreateSurfaceErrorKind { /// Error from [`wgpu_hal`]. #[cfg(wgpu_core)] Hal(wgc::instance::CreateSurfaceError), /// Error from WebGPU surface creation. #[cfg_attr(not(webgpu), expect(dead_code))] Web(String), /// Error when trying to get a [`RawDisplayHandle`][rdh] or a /// [`RawWindowHandle`][rwh] from a [`SurfaceTarget`]. /// /// [rdh]: raw_window_handle::RawDisplayHandle /// [rwh]: raw_window_handle::RawWindowHandle RawHandle(raw_window_handle::HandleError), } static_assertions::assert_impl_all!(CreateSurfaceError: Send, Sync); impl fmt::Display for CreateSurfaceError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.inner { #[cfg(wgpu_core)] CreateSurfaceErrorKind::Hal(e) => e.fmt(f), CreateSurfaceErrorKind::Web(e) => e.fmt(f), CreateSurfaceErrorKind::RawHandle(e) => e.fmt(f), } } } impl error::Error for CreateSurfaceError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match &self.inner { #[cfg(wgpu_core)] CreateSurfaceErrorKind::Hal(e) => e.source(), CreateSurfaceErrorKind::Web(_) => None, #[cfg(feature = "std")] CreateSurfaceErrorKind::RawHandle(e) => e.source(), #[cfg(not(feature = "std"))] CreateSurfaceErrorKind::RawHandle(_) => None, } } } #[cfg(wgpu_core)] impl From for CreateSurfaceError { fn from(e: wgc::instance::CreateSurfaceError) -> Self { Self { inner: CreateSurfaceErrorKind::Hal(e), } } } ================================================ FILE: wgpu/src/api/surface_texture.rs ================================================ use crate::*; /// Surface texture that can be rendered to. /// Result of a successful call to [`Surface::get_current_texture`]. /// /// This type is unique to the Rust API of `wgpu`. In the WebGPU specification, /// the [`GPUCanvasContext`](https://gpuweb.github.io/gpuweb/#canvas-context) provides /// a texture without any additional information. #[derive(Debug, Clone)] pub struct SurfaceTexture { /// Accessible view of the frame. pub texture: Texture, pub(crate) presented: bool, pub(crate) detail: dispatch::DispatchSurfaceOutputDetail, } #[cfg(send_sync)] static_assertions::assert_impl_all!(SurfaceTexture: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(SurfaceTexture => .texture.inner); impl SurfaceTexture { /// Schedule this texture to be presented on the owning surface. /// /// Needs to be called after any work on the texture is scheduled via [`Queue::submit`]. /// /// # Platform dependent behavior /// /// On Wayland, `present` will attach a `wl_buffer` to the underlying `wl_surface` and commit the new surface /// state. If it is desired to do things such as request a frame callback, scale the surface using the viewporter /// or synchronize other double buffered state, then these operations should be done before the call to `present`. pub fn present(mut self) { self.presented = true; self.detail.present(); } #[cfg(custom)] /// Returns custom implementation of SurfaceTexture (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.detail.as_custom() } } impl Drop for SurfaceTexture { fn drop(&mut self) { if !self.presented && !thread_panicking() { self.detail.texture_discard(); } } } /// Result of a call to [`Surface::get_current_texture`]. /// /// See variant documentation for how to handle each case. #[derive(Debug)] pub enum CurrentSurfaceTexture { /// Successfully acquired a surface texture with no issues. Success(SurfaceTexture), /// Successfully acquired a surface texture, but texture no longer matches the properties of the underlying surface. /// It's highly recommended to call [`Surface::configure`] again for optimal performance. Suboptimal(SurfaceTexture), /// A timeout was encountered while trying to acquire the next frame. /// /// Applications should skip the current frame and try again later. Timeout, /// The window is occluded (e.g. minimized or behind another window). /// /// Applications should skip the current frame and try again once the window /// is no longer occluded. Occluded, /// The underlying surface has changed, and therefore the surface configuration is outdated. /// /// Call [`Surface::configure()`] and try again. Outdated, /// The surface has been lost and needs to be recreated. /// /// If the device as a whole is lost (see [`set_device_lost_callback()`][crate::Device::set_device_lost_callback]), then /// you need to recreate the device and all resources. /// Otherwise, call [`Instance::create_surface()`] to recreate the surface, /// then [`Surface::configure()`], and try again. Lost, /// A validation error inside [`Surface::get_current_texture()`] was raised /// and caught by an [error scope](crate::Device::push_error_scope) or /// [`on_uncaptured_error()`][crate::Device::on_uncaptured_error]. /// /// Applications should attend to the validation error and try again. Validation, } fn thread_panicking() -> bool { cfg_if::cfg_if! { if #[cfg(std)] { std::thread::panicking() } else if #[cfg(panic = "abort")] { // If `panic = "abort"` then a thread _cannot_ be observably panicking by definition. false } else { // TODO: This is potentially overly pessimistic; it may be appropriate to instead allow a // texture to not be discarded. // Alternatively, this could _also_ be a `panic!`, since we only care if the thread is panicking // when the surface has not been presented. compile_error!( "cannot determine if a thread is panicking without either `panic = \"abort\"` or `std`" ); } } } ================================================ FILE: wgpu/src/api/texture.rs ================================================ #[cfg(wgpu_core)] use core::ops::Deref; use crate::*; /// Handle to a texture on the GPU. /// /// It can be created with [`Device::create_texture`]. /// /// Corresponds to [WebGPU `GPUTexture`](https://gpuweb.github.io/gpuweb/#texture-interface). #[derive(Debug, Clone)] pub struct Texture { pub(crate) inner: dispatch::DispatchTexture, pub(crate) descriptor: TextureDescriptor<'static>, } #[cfg(send_sync)] static_assertions::assert_impl_all!(Texture: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(Texture => .inner); impl Texture { /// Get the [`wgpu_hal`] texture from this `Texture`. /// /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`], /// and pass that struct to the to the `A` type parameter. /// /// Returns a guard that dereferences to the type of the hal backend /// which implements [`A::Texture`]. /// /// # Types /// /// The returned type depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("Texture")] #[doc = crate::macros::hal_type_metal!("Texture")] #[doc = crate::macros::hal_type_dx12!("Texture")] #[doc = crate::macros::hal_type_gles!("Texture")] /// /// # Deadlocks /// /// - The returned guard holds a read-lock on a device-local "destruction" /// lock, which will cause all calls to `destroy` to block until the /// guard is released. /// /// # Errors /// /// This method will return None if: /// - The texture is not from the backend specified by `A`. /// - The texture is from the `webgpu` or `custom` backend. /// - The texture has had [`Self::destroy()`] called on it. /// /// # Safety /// /// - The returned resource must not be destroyed unless the guard /// is the last reference to it and it is not in use by the GPU. /// The guard and handle may be dropped at any time however. /// - All the safety requirements of wgpu-hal must be upheld. /// /// [`A::Texture`]: hal::Api::Texture #[cfg(wgpu_core)] pub unsafe fn as_hal(&self) -> Option> { let texture = self.inner.as_core_opt()?; unsafe { texture.context.texture_as_hal::(texture) } } #[cfg(custom)] /// Returns custom implementation of Texture (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } #[cfg(custom)] /// Creates a texture from already created custom implementation with the given description pub fn from_custom( texture: T, desc: &TextureDescriptor<'_>, ) -> Self { Self { inner: dispatch::DispatchTexture::custom(texture), descriptor: TextureDescriptor { label: None, view_formats: &[], ..desc.clone() }, } } /// Creates a view of this texture, specifying an interpretation of its texels and /// possibly a subset of its layers and mip levels. /// /// Texture views are needed to use a texture as a binding in a [`BindGroup`] /// or as an attachment in a [`RenderPass`]. pub fn create_view(&self, desc: &TextureViewDescriptor<'_>) -> TextureView { let view = self.inner.create_view(desc); TextureView { inner: view, texture: self.clone(), } } /// Destroy the associated native resources as soon as possible. pub fn destroy(&self) { self.inner.destroy(); } /// Make an `TexelCopyTextureInfo` representing the whole texture. pub fn as_image_copy(&self) -> TexelCopyTextureInfo<'_> { TexelCopyTextureInfo { texture: self, mip_level: 0, origin: Origin3d::ZERO, aspect: TextureAspect::All, } } /// Returns the size of this `Texture`. /// /// This is always equal to the `size` that was specified when creating the texture. pub fn size(&self) -> Extent3d { self.descriptor.size } /// Returns the width of this `Texture`. /// /// This is always equal to the `size.width` that was specified when creating the texture. pub fn width(&self) -> u32 { self.descriptor.size.width } /// Returns the height of this `Texture`. /// /// This is always equal to the `size.height` that was specified when creating the texture. pub fn height(&self) -> u32 { self.descriptor.size.height } /// Returns the depth or layer count of this `Texture`. /// /// This is always equal to the `size.depth_or_array_layers` that was specified when creating the texture. pub fn depth_or_array_layers(&self) -> u32 { self.descriptor.size.depth_or_array_layers } /// Returns the mip_level_count of this `Texture`. /// /// This is always equal to the `mip_level_count` that was specified when creating the texture. pub fn mip_level_count(&self) -> u32 { self.descriptor.mip_level_count } /// Returns the sample_count of this `Texture`. /// /// This is always equal to the `sample_count` that was specified when creating the texture. pub fn sample_count(&self) -> u32 { self.descriptor.sample_count } /// Returns the dimension of this `Texture`. /// /// This is always equal to the `dimension` that was specified when creating the texture. pub fn dimension(&self) -> TextureDimension { self.descriptor.dimension } /// Returns the format of this `Texture`. /// /// This is always equal to the `format` that was specified when creating the texture. pub fn format(&self) -> TextureFormat { self.descriptor.format } /// Returns the allowed usages of this `Texture`. /// /// This is always equal to the `usage` that was specified when creating the texture. pub fn usage(&self) -> TextureUsages { self.descriptor.usage } } /// Describes a [`Texture`]. /// /// For use with [`Device::create_texture`]. /// /// Corresponds to [WebGPU `GPUTextureDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gputexturedescriptor). pub type TextureDescriptor<'a> = wgt::TextureDescriptor, &'a [TextureFormat]>; static_assertions::assert_impl_all!(TextureDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/texture_view.rs ================================================ #[cfg(wgpu_core)] use core::ops::Deref; use crate::*; /// Handle to a texture view. /// /// A `TextureView` object refers to a [`Texture`], or a subset of its layers and mip levels, and /// specifies an interpretation of the texture’s texels, which is needed to use a texture as a /// binding in a [`BindGroup`] or as an attachment in a [`RenderPass`]. /// It can be created using [`Texture::create_view()`], which accepts a [`TextureViewDescriptor`] /// specifying the properties of the view. /// /// Corresponds to [WebGPU `GPUTextureView`](https://gpuweb.github.io/gpuweb/#gputextureview). #[derive(Debug, Clone)] pub struct TextureView { pub(crate) inner: dispatch::DispatchTextureView, pub(crate) texture: Texture, } #[cfg(send_sync)] static_assertions::assert_impl_all!(TextureView: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(TextureView => .inner); impl TextureView { /// Returns the [`Texture`] that this `TextureView` refers to. /// /// All wgpu resources are refcounted, so you can own the returned [`Texture`] /// by cloning it. pub fn texture(&self) -> &Texture { &self.texture } /// Get the [`wgpu_hal`] texture view from this `TextureView`. /// /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`], /// and pass that struct to the to the `A` type parameter. /// /// Returns a guard that dereferences to the type of the hal backend /// which implements [`A::TextureView`]. /// /// # Types /// /// The returned type depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("TextureView")] #[doc = crate::macros::hal_type_metal!("TextureView")] #[doc = crate::macros::hal_type_dx12!("TextureView")] #[doc = crate::macros::hal_type_gles!("TextureView")] /// /// # Deadlocks /// /// - The returned guard holds a read-lock on a device-local "destruction" /// lock, which will cause all calls to `destroy` to block until the /// guard is released. /// /// # Errors /// /// This method will return None if: /// - The texture view is not from the backend specified by `A`. /// - The texture view is from the `webgpu` or `custom` backend. /// - The texture this view points to has had [`Texture::destroy()`] called on it. /// /// # Safety /// /// - The returned resource must not be destroyed unless the guard /// is the last reference to it and it is not in use by the GPU. /// The guard and handle may be dropped at any time however. /// - All the safety requirements of wgpu-hal must be upheld. /// /// [`A::TextureView`]: hal::Api::TextureView #[cfg(wgpu_core)] pub unsafe fn as_hal(&self) -> Option> { let view = self.inner.as_core_opt()?; unsafe { view.context.texture_view_as_hal::(view) } } #[cfg(custom)] /// Returns custom implementation of TextureView (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// Describes a [`TextureView`]. /// /// For use with [`Texture::create_view`]. /// /// Corresponds to [WebGPU `GPUTextureViewDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gputextureviewdescriptor). pub type TextureViewDescriptor<'a> = wgt::TextureViewDescriptor>; static_assertions::assert_impl_all!(TextureViewDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/tlas.rs ================================================ use crate::{api::blas::TlasInstance, dispatch}; use crate::{BindingResource, Label}; use alloc::vec::Vec; #[cfg(wgpu_core)] use core::ops::Deref; use core::ops::{Index, IndexMut, Range}; use wgt::WasmNotSendSync; /// Descriptor to create top level acceleration structures. pub type CreateTlasDescriptor<'a> = wgt::CreateTlasDescriptor>; static_assertions::assert_impl_all!(CreateTlasDescriptor<'_>: Send, Sync); #[derive(Debug, Clone)] /// Top Level Acceleration Structure (TLAS). /// /// A TLAS contains a series of [TLAS instances], which are a reference to /// a BLAS and a transformation matrix placing the geometry in the world. /// /// A TLAS also contains an extra set of TLAS instances in a device readable form, you cant interact /// directly with these, instead you have to build the TLAS with [TLAS instances]. /// /// [TLAS instances]: TlasInstance pub struct Tlas { pub(crate) inner: dispatch::DispatchTlas, pub(crate) instances: Vec>, pub(crate) lowest_unmodified: u32, } static_assertions::assert_impl_all!(Tlas: WasmNotSendSync); crate::cmp::impl_eq_ord_hash_proxy!(Tlas => .inner); impl Tlas { /// Get the [`wgpu_hal`] acceleration structure from this `Tlas`. /// /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`], /// and pass that struct to the to the `A` type parameter. /// /// Returns a guard that dereferences to the type of the hal backend /// which implements [`A::AccelerationStructure`]. /// /// # Types /// /// The returned type depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("AccelerationStructure")] #[doc = crate::macros::hal_type_metal!("AccelerationStructure")] #[doc = crate::macros::hal_type_dx12!("AccelerationStructure")] #[doc = crate::macros::hal_type_gles!("AccelerationStructure")] /// /// # Deadlocks /// /// - The returned guard holds a read-lock on a device-local "destruction" /// lock, which will cause all calls to `destroy` to block until the /// guard is released. /// /// # Errors /// /// This method will return None if: /// - The acceleration structure is not from the backend specified by `A`. /// - The acceleration structure is from the `webgpu` or `custom` backend. /// /// # Safety /// /// - The returned resource must not be destroyed unless the guard /// is the last reference to it and it is not in use by the GPU. /// The guard and handle may be dropped at any time however. /// - All the safety requirements of wgpu-hal must be upheld. /// /// [`A::AccelerationStructure`]: hal::Api::AccelerationStructure #[cfg(wgpu_core)] pub unsafe fn as_hal( &mut self, ) -> Option> { let tlas = self.inner.as_core_opt()?; unsafe { tlas.context.tlas_as_hal::(tlas) } } #[cfg(custom)] /// Returns custom implementation of Tlas (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } /// Get a reference to all instances. pub fn get(&self) -> &[Option] { &self.instances } /// Get a mutable slice to a range of instances. /// Returns None if the range is out of bounds. /// All elements from the lowest accessed index up are marked as modified. // this recommendation is not useful yet, but is likely to be when ability to update arrives or possible optimisations for building get implemented. /// For best performance it is recommended to prefer access to low elements and modify higher elements as little as possible. /// This can be done by ordering instances from the most to the least used. It is recommended /// to use [`Self::index_mut`] unless the option if out of bounds is required pub fn get_mut_slice(&mut self, range: Range) -> Option<&mut [Option]> { if range.end > self.instances.len() { return None; } if range.end as u32 > self.lowest_unmodified { self.lowest_unmodified = range.end as u32; } Some(&mut self.instances[range]) } /// Get a single mutable reference to an instance. /// Returns None if the range is out of bounds. /// All elements from the lowest accessed index up are marked as modified. // this recommendation is not useful yet, but is likely to be when ability to update arrives or possible optimisations for building get implemented. /// For best performance it is recommended to prefer access to low elements and modify higher elements as little as possible. /// This can be done by ordering instances from the most to the least used. It is recommended /// to use [`Self::index_mut`] unless the option if out of bounds is required pub fn get_mut_single(&mut self, index: usize) -> Option<&mut Option> { if index >= self.instances.len() { return None; } if index as u32 + 1 > self.lowest_unmodified { self.lowest_unmodified = index as u32 + 1; } Some(&mut self.instances[index]) } /// Get the binding resource for the underling acceleration structure, to be used when creating a [`BindGroup`] /// /// [`BindGroup`]: super::BindGroup pub fn as_binding(&self) -> BindingResource<'_> { BindingResource::AccelerationStructure(self) } } impl Index for Tlas { type Output = Option; fn index(&self, index: usize) -> &Self::Output { self.instances.index(index) } } impl Index> for Tlas { type Output = [Option]; fn index(&self, index: Range) -> &Self::Output { self.instances.index(index) } } impl IndexMut for Tlas { fn index_mut(&mut self, index: usize) -> &mut Self::Output { let idx = self.instances.index_mut(index); if index as u32 + 1 > self.lowest_unmodified { self.lowest_unmodified = index as u32 + 1; } idx } } impl IndexMut> for Tlas { fn index_mut(&mut self, index: Range) -> &mut Self::Output { let idx = self.instances.index_mut(index.clone()); if index.end > self.lowest_unmodified as usize { self.lowest_unmodified = index.end as u32; } idx } } ================================================ FILE: wgpu/src/backend/custom.rs ================================================ //! Provides wrappers custom backend implementations #![allow(ambiguous_wide_pointer_comparisons)] pub use crate::dispatch::*; use alloc::sync::Arc; macro_rules! dyn_type { // cloning of arc forbidden // but we still use it to provide Eq,Ord,Hash implementations (pub mut struct $name:ident(dyn $interface:tt)) => { #[derive(Debug)] pub(crate) struct $name(Arc); crate::cmp::impl_eq_ord_hash_arc_address!($name => .0); impl $name { pub(crate) fn new(t: T) -> Self { Self(Arc::new(t)) } #[allow(clippy::allow_attributes, dead_code)] pub(crate) fn downcast(&self) -> Option<&T> { self.0.as_ref().as_any().downcast_ref() } } impl core::ops::Deref for $name { type Target = dyn $interface; #[inline] fn deref(&self) -> &Self::Target { self.0.as_ref() } } impl core::ops::DerefMut for $name { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { Arc::get_mut(&mut self.0).expect("") } } }; // cloning of arc is allowed (pub ref struct $name:ident(dyn $interface:tt)) => { #[derive(Debug, Clone)] pub(crate) struct $name(Arc); crate::cmp::impl_eq_ord_hash_arc_address!($name => .0); impl $name { pub(crate) fn new(t: T) -> Self { Self(Arc::new(t)) } pub(crate) fn downcast(&self) -> Option<&T> { self.0.as_ref().as_any().downcast_ref() } } impl core::ops::Deref for $name { type Target = dyn $interface; #[inline] fn deref(&self) -> &Self::Target { self.0.as_ref() } } }; } dyn_type!(pub ref struct DynContext(dyn InstanceInterface)); dyn_type!(pub ref struct DynAdapter(dyn AdapterInterface)); dyn_type!(pub ref struct DynDevice(dyn DeviceInterface)); dyn_type!(pub ref struct DynQueue(dyn QueueInterface)); dyn_type!(pub ref struct DynShaderModule(dyn ShaderModuleInterface)); dyn_type!(pub ref struct DynBindGroupLayout(dyn BindGroupLayoutInterface)); dyn_type!(pub ref struct DynBindGroup(dyn BindGroupInterface)); dyn_type!(pub ref struct DynTextureView(dyn TextureViewInterface)); dyn_type!(pub ref struct DynSampler(dyn SamplerInterface)); dyn_type!(pub ref struct DynBuffer(dyn BufferInterface)); dyn_type!(pub ref struct DynTexture(dyn TextureInterface)); dyn_type!(pub ref struct DynExternalTexture(dyn ExternalTextureInterface)); dyn_type!(pub ref struct DynBlas(dyn BlasInterface)); dyn_type!(pub ref struct DynTlas(dyn TlasInterface)); dyn_type!(pub ref struct DynQuerySet(dyn QuerySetInterface)); dyn_type!(pub ref struct DynPipelineLayout(dyn PipelineLayoutInterface)); dyn_type!(pub ref struct DynRenderPipeline(dyn RenderPipelineInterface)); dyn_type!(pub ref struct DynComputePipeline(dyn ComputePipelineInterface)); dyn_type!(pub ref struct DynPipelineCache(dyn PipelineCacheInterface)); dyn_type!(pub mut struct DynCommandEncoder(dyn CommandEncoderInterface)); dyn_type!(pub mut struct DynComputePass(dyn ComputePassInterface)); dyn_type!(pub mut struct DynRenderPass(dyn RenderPassInterface)); dyn_type!(pub mut struct DynCommandBuffer(dyn CommandBufferInterface)); dyn_type!(pub mut struct DynRenderBundleEncoder(dyn RenderBundleEncoderInterface)); dyn_type!(pub ref struct DynRenderBundle(dyn RenderBundleInterface)); dyn_type!(pub ref struct DynSurface(dyn SurfaceInterface)); dyn_type!(pub ref struct DynSurfaceOutputDetail(dyn SurfaceOutputDetailInterface)); dyn_type!(pub mut struct DynQueueWriteBuffer(dyn QueueWriteBufferInterface)); dyn_type!(pub mut struct DynBufferMappedRange(dyn BufferMappedRangeInterface)); ================================================ FILE: wgpu/src/backend/mod.rs ================================================ #[cfg(webgpu)] pub mod webgpu; #[cfg(webgpu)] pub(crate) use webgpu::{get_browser_gpu_property, ContextWebGpu}; #[cfg(wgpu_core)] pub mod wgpu_core; #[cfg(wgpu_core)] pub(crate) use wgpu_core::ContextWgpuCore; #[cfg(custom)] pub mod custom; ================================================ FILE: wgpu/src/backend/webgpu/defined_non_null_js_value.rs ================================================ use core::ops::{Deref, DerefMut}; use wasm_bindgen::JsValue; /// Derefs to a [`JsValue`] that's known not to be `undefined` or `null`. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DefinedNonNullJsValue(T); impl DefinedNonNullJsValue where T: AsRef, { pub fn new(value: T) -> Option { if value.as_ref().is_undefined() || value.as_ref().is_null() { None } else { Some(Self(value)) } } } impl Deref for DefinedNonNullJsValue { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for DefinedNonNullJsValue { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl AsRef for DefinedNonNullJsValue { fn as_ref(&self) -> &T { &self.0 } } impl AsMut for DefinedNonNullJsValue { fn as_mut(&mut self) -> &mut T { &mut self.0 } } ================================================ FILE: wgpu/src/backend/webgpu/ext_bindings.rs ================================================ //! Extension bindings for WebGPU. //! //! These contain ideomatic Rust extension traits for various parts of the WebGPU //! bindings that are missing, need to be improved, or otherwise need to be different //! from the generated web_sys bindings. use crate::backend::webgpu::webgpu_sys; use wasm_bindgen::prelude::*; /// Extension trait for [`web_sys::Navigator`] and [`web_sys::WorkerNavigator`] to /// access the `gpu` property. pub trait NavigatorGpu { /// Get the `gpu` property. /// /// This is intentionally a free function, to prevent overload conflicts with /// the method if it is enabled in web-sys itself. fn gpu(navigator: &Self) -> webgpu_sys::Gpu; } // --- Bindings for `Navigator` --- #[wasm_bindgen] extern "C" { /// Create a fake class which we tell wasm-bindgen has access to the `gpu` property. #[wasm_bindgen] type NavigatorWithGpu; #[wasm_bindgen(method, getter)] fn gpu(ext: &NavigatorWithGpu) -> webgpu_sys::Gpu; } impl NavigatorGpu for web_sys::Navigator { fn gpu(navigator: &Self) -> webgpu_sys::Gpu { // Must be an unchecked ref as this class does not exist at runtime. let extension: &NavigatorWithGpu = navigator.unchecked_ref(); extension.gpu() } } impl NavigatorGpu for web_sys::WorkerNavigator { fn gpu(navigator: &Self) -> webgpu_sys::Gpu { // Must be an unchecked ref as this class does not exist at runtime. let extension: &NavigatorWithGpu = navigator.unchecked_ref(); extension.gpu() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_Gpu.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPU , typescript_type = "GPU")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `Gpu` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPU)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `Gpu`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type Gpu; # [wasm_bindgen (structural , method , getter , js_class = "GPU" , js_name = wgslLanguageFeatures)] #[doc = "Getter for the `wgslLanguageFeatures` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPU/wgslLanguageFeatures)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `Gpu`, `WgslLanguageFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn wgsl_language_features(this: &Gpu) -> WgslLanguageFeatures; # [wasm_bindgen (method , structural , js_class = "GPU" , js_name = getPreferredCanvasFormat)] #[doc = "The `getPreferredCanvasFormat()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPU/getPreferredCanvasFormat)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `Gpu`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn get_preferred_canvas_format(this: &Gpu) -> GpuTextureFormat; # [wasm_bindgen (method , structural , js_class = "GPU" , js_name = requestAdapter)] #[doc = "The `requestAdapter()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPU/requestAdapter)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `Gpu`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn request_adapter(this: &Gpu) -> ::js_sys::Promise; # [wasm_bindgen (method , structural , js_class = "GPU" , js_name = requestAdapter)] #[doc = "The `requestAdapter()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPU/requestAdapter)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `Gpu`, `GpuRequestAdapterOptions`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn request_adapter_with_options( this: &Gpu, options: &GpuRequestAdapterOptions, ) -> ::js_sys::Promise; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuAdapter.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUAdapter , typescript_type = "GPUAdapter")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuAdapter` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUAdapter)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAdapter`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuAdapter; # [wasm_bindgen (structural , method , getter , js_class = "GPUAdapter" , js_name = features)] #[doc = "Getter for the `features` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUAdapter/features)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAdapter`, `GpuSupportedFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn features(this: &GpuAdapter) -> GpuSupportedFeatures; # [wasm_bindgen (structural , method , getter , js_class = "GPUAdapter" , js_name = limits)] #[doc = "Getter for the `limits` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUAdapter/limits)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAdapter`, `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn limits(this: &GpuAdapter) -> GpuSupportedLimits; # [wasm_bindgen (structural , method , getter , js_class = "GPUAdapter" , js_name = info)] #[doc = "Getter for the `info` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUAdapter/info)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAdapter`, `GpuAdapterInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn info(this: &GpuAdapter) -> GpuAdapterInfo; # [wasm_bindgen (structural , method , getter , js_class = "GPUAdapter" , js_name = isFallbackAdapter)] #[doc = "Getter for the `isFallbackAdapter` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUAdapter/isFallbackAdapter)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAdapter`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn is_fallback_adapter(this: &GpuAdapter) -> bool; # [wasm_bindgen (method , structural , js_class = "GPUAdapter" , js_name = requestDevice)] #[doc = "The `requestDevice()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUAdapter/requestDevice)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAdapter`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn request_device(this: &GpuAdapter) -> ::js_sys::Promise; # [wasm_bindgen (method , structural , js_class = "GPUAdapter" , js_name = requestDevice)] #[doc = "The `requestDevice()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUAdapter/requestDevice)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAdapter`, `GpuDeviceDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn request_device_with_descriptor( this: &GpuAdapter, descriptor: &GpuDeviceDescriptor, ) -> ::js_sys::Promise; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuAdapterInfo.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUAdapterInfo , typescript_type = "GPUAdapterInfo")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuAdapterInfo` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUAdapterInfo)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAdapterInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuAdapterInfo; # [wasm_bindgen (structural , method , getter , js_class = "GPUAdapterInfo" , js_name = vendor)] #[doc = "Getter for the `vendor` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUAdapterInfo/vendor)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAdapterInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn vendor(this: &GpuAdapterInfo) -> ::alloc::string::String; # [wasm_bindgen (structural , method , getter , js_class = "GPUAdapterInfo" , js_name = architecture)] #[doc = "Getter for the `architecture` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUAdapterInfo/architecture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAdapterInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn architecture(this: &GpuAdapterInfo) -> ::alloc::string::String; # [wasm_bindgen (structural , method , getter , js_class = "GPUAdapterInfo" , js_name = device)] #[doc = "Getter for the `device` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUAdapterInfo/device)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAdapterInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn device(this: &GpuAdapterInfo) -> ::alloc::string::String; # [wasm_bindgen (structural , method , getter , js_class = "GPUAdapterInfo" , js_name = description)] #[doc = "Getter for the `description` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUAdapterInfo/description)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAdapterInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn description(this: &GpuAdapterInfo) -> ::alloc::string::String; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuAddressMode.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuAddressMode` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAddressMode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuAddressMode { ClampToEdge = "clamp-to-edge", Repeat = "repeat", MirrorRepeat = "mirror-repeat", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuAutoLayoutMode.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuAutoLayoutMode` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAutoLayoutMode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuAutoLayoutMode { Auto = "auto", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBindGroup.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUBindGroup , typescript_type = "GPUBindGroup")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuBindGroup` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuBindGroup; # [wasm_bindgen (structural , method , getter , js_class = "GPUBindGroup" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBindGroup/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuBindGroup) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUBindGroup" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBindGroup/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuBindGroup, value: &str); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBindGroupDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUBindGroupDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuBindGroupDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuBindGroupDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuBindGroupDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuBindGroupDescriptor, val: &str); #[doc = "Get the `entries` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "entries")] pub fn get_entries(this: &GpuBindGroupDescriptor) -> ::js_sys::Array; #[doc = "Change the `entries` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "entries")] pub fn set_entries(this: &GpuBindGroupDescriptor, val: &::wasm_bindgen::JsValue); #[doc = "Get the `layout` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupDescriptor`, `GpuBindGroupLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "layout")] pub fn get_layout(this: &GpuBindGroupDescriptor) -> GpuBindGroupLayout; #[doc = "Change the `layout` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupDescriptor`, `GpuBindGroupLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "layout")] pub fn set_layout(this: &GpuBindGroupDescriptor, val: &GpuBindGroupLayout); } impl GpuBindGroupDescriptor { #[doc = "Construct a new `GpuBindGroupDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupDescriptor`, `GpuBindGroupLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(entries: &::wasm_bindgen::JsValue, layout: &GpuBindGroupLayout) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_entries(entries); ret.set_layout(layout); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_entries()` instead."] pub fn entries(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_entries(val); self } #[deprecated = "Use `set_layout()` instead."] pub fn layout(&mut self, val: &GpuBindGroupLayout) -> &mut Self { self.set_layout(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBindGroupEntry.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUBindGroupEntry)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuBindGroupEntry` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupEntry`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuBindGroupEntry; #[doc = "Get the `binding` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupEntry`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "binding")] pub fn get_binding(this: &GpuBindGroupEntry) -> u32; #[doc = "Change the `binding` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupEntry`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "binding")] pub fn set_binding(this: &GpuBindGroupEntry, val: u32); #[doc = "Get the `resource` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupEntry`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "resource")] pub fn get_resource(this: &GpuBindGroupEntry) -> ::wasm_bindgen::JsValue; #[doc = "Change the `resource` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupEntry`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "resource")] pub fn set_resource(this: &GpuBindGroupEntry, val: &::wasm_bindgen::JsValue); } impl GpuBindGroupEntry { #[doc = "Construct a new `GpuBindGroupEntry`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupEntry`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(binding: u32, resource: &::wasm_bindgen::JsValue) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_binding(binding); ret.set_resource(resource); ret } #[deprecated = "Use `set_binding()` instead."] pub fn binding(&mut self, val: u32) -> &mut Self { self.set_binding(val); self } #[deprecated = "Use `set_resource()` instead."] pub fn resource(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_resource(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBindGroupLayout.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUBindGroupLayout , typescript_type = "GPUBindGroupLayout")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuBindGroupLayout` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBindGroupLayout)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuBindGroupLayout; # [wasm_bindgen (structural , method , getter , js_class = "GPUBindGroupLayout" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBindGroupLayout/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuBindGroupLayout) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUBindGroupLayout" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBindGroupLayout/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuBindGroupLayout, value: &str); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBindGroupLayoutDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUBindGroupLayoutDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuBindGroupLayoutDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuBindGroupLayoutDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuBindGroupLayoutDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuBindGroupLayoutDescriptor, val: &str); #[doc = "Get the `entries` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "entries")] pub fn get_entries(this: &GpuBindGroupLayoutDescriptor) -> ::js_sys::Array; #[doc = "Change the `entries` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "entries")] pub fn set_entries(this: &GpuBindGroupLayoutDescriptor, val: &::wasm_bindgen::JsValue); } impl GpuBindGroupLayoutDescriptor { #[doc = "Construct a new `GpuBindGroupLayoutDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(entries: &::wasm_bindgen::JsValue) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_entries(entries); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_entries()` instead."] pub fn entries(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_entries(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBindGroupLayoutEntry.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUBindGroupLayoutEntry)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuBindGroupLayoutEntry` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuBindGroupLayoutEntry; #[doc = "Get the `binding` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "binding")] pub fn get_binding(this: &GpuBindGroupLayoutEntry) -> u32; #[doc = "Change the `binding` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "binding")] pub fn set_binding(this: &GpuBindGroupLayoutEntry, val: u32); #[doc = "Get the `buffer` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`, `GpuBufferBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "buffer")] pub fn get_buffer(this: &GpuBindGroupLayoutEntry) -> Option; #[doc = "Change the `buffer` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`, `GpuBufferBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "buffer")] pub fn set_buffer(this: &GpuBindGroupLayoutEntry, val: &GpuBufferBindingLayout); #[doc = "Get the `externalTexture` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`, `GpuExternalTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "externalTexture")] pub fn get_external_texture( this: &GpuBindGroupLayoutEntry, ) -> Option; #[doc = "Change the `externalTexture` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`, `GpuExternalTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "externalTexture")] pub fn set_external_texture( this: &GpuBindGroupLayoutEntry, val: &GpuExternalTextureBindingLayout, ); #[doc = "Get the `sampler` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`, `GpuSamplerBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "sampler")] pub fn get_sampler(this: &GpuBindGroupLayoutEntry) -> Option; #[doc = "Change the `sampler` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`, `GpuSamplerBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "sampler")] pub fn set_sampler(this: &GpuBindGroupLayoutEntry, val: &GpuSamplerBindingLayout); #[doc = "Get the `storageTexture` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`, `GpuStorageTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "storageTexture")] pub fn get_storage_texture( this: &GpuBindGroupLayoutEntry, ) -> Option; #[doc = "Change the `storageTexture` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`, `GpuStorageTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "storageTexture")] pub fn set_storage_texture( this: &GpuBindGroupLayoutEntry, val: &GpuStorageTextureBindingLayout, ); #[doc = "Get the `texture` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`, `GpuTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "texture")] pub fn get_texture(this: &GpuBindGroupLayoutEntry) -> Option; #[doc = "Change the `texture` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`, `GpuTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "texture")] pub fn set_texture(this: &GpuBindGroupLayoutEntry, val: &GpuTextureBindingLayout); #[doc = "Get the `visibility` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "visibility")] pub fn get_visibility(this: &GpuBindGroupLayoutEntry) -> u32; #[doc = "Change the `visibility` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "visibility")] pub fn set_visibility(this: &GpuBindGroupLayoutEntry, val: u32); } impl GpuBindGroupLayoutEntry { #[doc = "Construct a new `GpuBindGroupLayoutEntry`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayoutEntry`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(binding: u32, visibility: u32) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_binding(binding); ret.set_visibility(visibility); ret } #[deprecated = "Use `set_binding()` instead."] pub fn binding(&mut self, val: u32) -> &mut Self { self.set_binding(val); self } #[deprecated = "Use `set_buffer()` instead."] pub fn buffer(&mut self, val: &GpuBufferBindingLayout) -> &mut Self { self.set_buffer(val); self } #[deprecated = "Use `set_external_texture()` instead."] pub fn external_texture(&mut self, val: &GpuExternalTextureBindingLayout) -> &mut Self { self.set_external_texture(val); self } #[deprecated = "Use `set_sampler()` instead."] pub fn sampler(&mut self, val: &GpuSamplerBindingLayout) -> &mut Self { self.set_sampler(val); self } #[deprecated = "Use `set_storage_texture()` instead."] pub fn storage_texture(&mut self, val: &GpuStorageTextureBindingLayout) -> &mut Self { self.set_storage_texture(val); self } #[deprecated = "Use `set_texture()` instead."] pub fn texture(&mut self, val: &GpuTextureBindingLayout) -> &mut Self { self.set_texture(val); self } #[deprecated = "Use `set_visibility()` instead."] pub fn visibility(&mut self, val: u32) -> &mut Self { self.set_visibility(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBlendComponent.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUBlendComponent)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuBlendComponent` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendComponent`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuBlendComponent; #[doc = "Get the `dstFactor` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendComponent`, `GpuBlendFactor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "dstFactor")] pub fn get_dst_factor(this: &GpuBlendComponent) -> Option; #[doc = "Change the `dstFactor` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendComponent`, `GpuBlendFactor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "dstFactor")] pub fn set_dst_factor(this: &GpuBlendComponent, val: GpuBlendFactor); #[doc = "Get the `operation` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendComponent`, `GpuBlendOperation`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "operation")] pub fn get_operation(this: &GpuBlendComponent) -> Option; #[doc = "Change the `operation` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendComponent`, `GpuBlendOperation`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "operation")] pub fn set_operation(this: &GpuBlendComponent, val: GpuBlendOperation); #[doc = "Get the `srcFactor` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendComponent`, `GpuBlendFactor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "srcFactor")] pub fn get_src_factor(this: &GpuBlendComponent) -> Option; #[doc = "Change the `srcFactor` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendComponent`, `GpuBlendFactor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "srcFactor")] pub fn set_src_factor(this: &GpuBlendComponent, val: GpuBlendFactor); } impl GpuBlendComponent { #[doc = "Construct a new `GpuBlendComponent`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendComponent`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_dst_factor()` instead."] pub fn dst_factor(&mut self, val: GpuBlendFactor) -> &mut Self { self.set_dst_factor(val); self } #[deprecated = "Use `set_operation()` instead."] pub fn operation(&mut self, val: GpuBlendOperation) -> &mut Self { self.set_operation(val); self } #[deprecated = "Use `set_src_factor()` instead."] pub fn src_factor(&mut self, val: GpuBlendFactor) -> &mut Self { self.set_src_factor(val); self } } impl Default for GpuBlendComponent { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBlendFactor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuBlendFactor` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendFactor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuBlendFactor { Zero = "zero", One = "one", Src = "src", OneMinusSrc = "one-minus-src", SrcAlpha = "src-alpha", OneMinusSrcAlpha = "one-minus-src-alpha", Dst = "dst", OneMinusDst = "one-minus-dst", DstAlpha = "dst-alpha", OneMinusDstAlpha = "one-minus-dst-alpha", SrcAlphaSaturated = "src-alpha-saturated", Constant = "constant", OneMinusConstant = "one-minus-constant", Src1 = "src1", OneMinusSrc1 = "one-minus-src1", Src1Alpha = "src1-alpha", OneMinusSrc1Alpha = "one-minus-src1-alpha", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBlendOperation.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuBlendOperation` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendOperation`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuBlendOperation { Add = "add", Subtract = "subtract", ReverseSubtract = "reverse-subtract", Min = "min", Max = "max", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBlendState.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUBlendState)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuBlendState` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuBlendState; #[doc = "Get the `alpha` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendComponent`, `GpuBlendState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "alpha")] pub fn get_alpha(this: &GpuBlendState) -> GpuBlendComponent; #[doc = "Change the `alpha` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendComponent`, `GpuBlendState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "alpha")] pub fn set_alpha(this: &GpuBlendState, val: &GpuBlendComponent); #[doc = "Get the `color` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendComponent`, `GpuBlendState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "color")] pub fn get_color(this: &GpuBlendState) -> GpuBlendComponent; #[doc = "Change the `color` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendComponent`, `GpuBlendState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "color")] pub fn set_color(this: &GpuBlendState, val: &GpuBlendComponent); } impl GpuBlendState { #[doc = "Construct a new `GpuBlendState`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendComponent`, `GpuBlendState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(alpha: &GpuBlendComponent, color: &GpuBlendComponent) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_alpha(alpha); ret.set_color(color); ret } #[deprecated = "Use `set_alpha()` instead."] pub fn alpha(&mut self, val: &GpuBlendComponent) -> &mut Self { self.set_alpha(val); self } #[deprecated = "Use `set_color()` instead."] pub fn color(&mut self, val: &GpuBlendComponent) -> &mut Self { self.set_color(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBuffer.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUBuffer , typescript_type = "GPUBuffer")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuBuffer` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuBuffer; # [wasm_bindgen (structural , method , getter , js_class = "GPUBuffer" , js_name = size)] #[doc = "Getter for the `size` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/size)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn size(this: &GpuBuffer) -> f64; # [wasm_bindgen (structural , method , getter , js_class = "GPUBuffer" , js_name = usage)] #[doc = "Getter for the `usage` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/usage)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn usage(this: &GpuBuffer) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUBuffer" , js_name = mapState)] #[doc = "Getter for the `mapState` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/mapState)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuBufferMapState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn map_state(this: &GpuBuffer) -> GpuBufferMapState; # [wasm_bindgen (structural , method , getter , js_class = "GPUBuffer" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuBuffer) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUBuffer" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuBuffer, value: &str); # [wasm_bindgen (method , structural , js_class = "GPUBuffer" , js_name = destroy)] #[doc = "The `destroy()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/destroy)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn destroy(this: &GpuBuffer); # [wasm_bindgen (catch , method , structural , js_class = "GPUBuffer" , js_name = getMappedRange)] #[doc = "The `getMappedRange()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/getMappedRange)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn get_mapped_range(this: &GpuBuffer) -> Result<::js_sys::ArrayBuffer, JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUBuffer" , js_name = getMappedRange)] #[doc = "The `getMappedRange()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/getMappedRange)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn get_mapped_range_with_u32( this: &GpuBuffer, offset: u32, ) -> Result<::js_sys::ArrayBuffer, JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUBuffer" , js_name = getMappedRange)] #[doc = "The `getMappedRange()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/getMappedRange)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn get_mapped_range_with_f64( this: &GpuBuffer, offset: f64, ) -> Result<::js_sys::ArrayBuffer, JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUBuffer" , js_name = getMappedRange)] #[doc = "The `getMappedRange()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/getMappedRange)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn get_mapped_range_with_u32_and_u32( this: &GpuBuffer, offset: u32, size: u32, ) -> Result<::js_sys::ArrayBuffer, JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUBuffer" , js_name = getMappedRange)] #[doc = "The `getMappedRange()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/getMappedRange)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn get_mapped_range_with_f64_and_u32( this: &GpuBuffer, offset: f64, size: u32, ) -> Result<::js_sys::ArrayBuffer, JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUBuffer" , js_name = getMappedRange)] #[doc = "The `getMappedRange()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/getMappedRange)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn get_mapped_range_with_u32_and_f64( this: &GpuBuffer, offset: u32, size: f64, ) -> Result<::js_sys::ArrayBuffer, JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUBuffer" , js_name = getMappedRange)] #[doc = "The `getMappedRange()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/getMappedRange)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn get_mapped_range_with_f64_and_f64( this: &GpuBuffer, offset: f64, size: f64, ) -> Result<::js_sys::ArrayBuffer, JsValue>; # [wasm_bindgen (method , structural , js_class = "GPUBuffer" , js_name = mapAsync)] #[doc = "The `mapAsync()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/mapAsync)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn map_async(this: &GpuBuffer, mode: u32) -> ::js_sys::Promise; # [wasm_bindgen (method , structural , js_class = "GPUBuffer" , js_name = mapAsync)] #[doc = "The `mapAsync()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/mapAsync)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn map_async_with_u32(this: &GpuBuffer, mode: u32, offset: u32) -> ::js_sys::Promise; # [wasm_bindgen (method , structural , js_class = "GPUBuffer" , js_name = mapAsync)] #[doc = "The `mapAsync()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/mapAsync)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn map_async_with_f64(this: &GpuBuffer, mode: u32, offset: f64) -> ::js_sys::Promise; # [wasm_bindgen (method , structural , js_class = "GPUBuffer" , js_name = mapAsync)] #[doc = "The `mapAsync()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/mapAsync)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn map_async_with_u32_and_u32( this: &GpuBuffer, mode: u32, offset: u32, size: u32, ) -> ::js_sys::Promise; # [wasm_bindgen (method , structural , js_class = "GPUBuffer" , js_name = mapAsync)] #[doc = "The `mapAsync()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/mapAsync)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn map_async_with_f64_and_u32( this: &GpuBuffer, mode: u32, offset: f64, size: u32, ) -> ::js_sys::Promise; # [wasm_bindgen (method , structural , js_class = "GPUBuffer" , js_name = mapAsync)] #[doc = "The `mapAsync()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/mapAsync)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn map_async_with_u32_and_f64( this: &GpuBuffer, mode: u32, offset: u32, size: f64, ) -> ::js_sys::Promise; # [wasm_bindgen (method , structural , js_class = "GPUBuffer" , js_name = mapAsync)] #[doc = "The `mapAsync()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/mapAsync)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn map_async_with_f64_and_f64( this: &GpuBuffer, mode: u32, offset: f64, size: f64, ) -> ::js_sys::Promise; # [wasm_bindgen (method , structural , js_class = "GPUBuffer" , js_name = unmap)] #[doc = "The `unmap()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/unmap)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn unmap(this: &GpuBuffer); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBufferBinding.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUBufferBinding)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuBufferBinding` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBinding`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuBufferBinding; #[doc = "Get the `buffer` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuBufferBinding`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "buffer")] pub fn get_buffer(this: &GpuBufferBinding) -> GpuBuffer; #[doc = "Change the `buffer` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuBufferBinding`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "buffer")] pub fn set_buffer(this: &GpuBufferBinding, val: &GpuBuffer); #[doc = "Get the `offset` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBinding`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "offset")] pub fn get_offset(this: &GpuBufferBinding) -> Option; #[doc = "Change the `offset` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBinding`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "offset")] pub fn set_offset(this: &GpuBufferBinding, val: f64); #[doc = "Get the `size` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBinding`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "size")] pub fn get_size(this: &GpuBufferBinding) -> Option; #[doc = "Change the `size` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBinding`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "size")] pub fn set_size(this: &GpuBufferBinding, val: f64); } impl GpuBufferBinding { #[doc = "Construct a new `GpuBufferBinding`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuBufferBinding`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(buffer: &GpuBuffer) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_buffer(buffer); ret } #[deprecated = "Use `set_buffer()` instead."] pub fn buffer(&mut self, val: &GpuBuffer) -> &mut Self { self.set_buffer(val); self } #[deprecated = "Use `set_offset()` instead."] pub fn offset(&mut self, val: f64) -> &mut Self { self.set_offset(val); self } #[deprecated = "Use `set_size()` instead."] pub fn size(&mut self, val: f64) -> &mut Self { self.set_size(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBufferBindingLayout.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUBufferBindingLayout)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuBufferBindingLayout` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuBufferBindingLayout; #[doc = "Get the `hasDynamicOffset` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "hasDynamicOffset")] pub fn get_has_dynamic_offset(this: &GpuBufferBindingLayout) -> Option; #[doc = "Change the `hasDynamicOffset` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "hasDynamicOffset")] pub fn set_has_dynamic_offset(this: &GpuBufferBindingLayout, val: bool); #[doc = "Get the `minBindingSize` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "minBindingSize")] pub fn get_min_binding_size(this: &GpuBufferBindingLayout) -> Option; #[doc = "Change the `minBindingSize` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "minBindingSize")] pub fn set_min_binding_size(this: &GpuBufferBindingLayout, val: f64); #[doc = "Get the `type` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBindingLayout`, `GpuBufferBindingType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "type")] pub fn get_type(this: &GpuBufferBindingLayout) -> Option; #[doc = "Change the `type` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBindingLayout`, `GpuBufferBindingType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "type")] pub fn set_type(this: &GpuBufferBindingLayout, val: GpuBufferBindingType); } impl GpuBufferBindingLayout { #[doc = "Construct a new `GpuBufferBindingLayout`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_has_dynamic_offset()` instead."] pub fn has_dynamic_offset(&mut self, val: bool) -> &mut Self { self.set_has_dynamic_offset(val); self } #[deprecated = "Use `set_min_binding_size()` instead."] pub fn min_binding_size(&mut self, val: f64) -> &mut Self { self.set_min_binding_size(val); self } #[deprecated = "Use `set_type()` instead."] pub fn type_(&mut self, val: GpuBufferBindingType) -> &mut Self { self.set_type(val); self } } impl Default for GpuBufferBindingLayout { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBufferBindingType.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuBufferBindingType` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferBindingType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuBufferBindingType { Uniform = "uniform", Storage = "storage", ReadOnlyStorage = "read-only-storage", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBufferDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUBufferDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuBufferDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuBufferDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuBufferDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuBufferDescriptor, val: &str); #[doc = "Get the `mappedAtCreation` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "mappedAtCreation")] pub fn get_mapped_at_creation(this: &GpuBufferDescriptor) -> Option; #[doc = "Change the `mappedAtCreation` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "mappedAtCreation")] pub fn set_mapped_at_creation(this: &GpuBufferDescriptor, val: bool); #[doc = "Get the `size` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "size")] pub fn get_size(this: &GpuBufferDescriptor) -> f64; #[doc = "Change the `size` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "size")] pub fn set_size(this: &GpuBufferDescriptor, val: f64); #[doc = "Get the `usage` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "usage")] pub fn get_usage(this: &GpuBufferDescriptor) -> u32; #[doc = "Change the `usage` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "usage")] pub fn set_usage(this: &GpuBufferDescriptor, val: u32); } impl GpuBufferDescriptor { #[doc = "Construct a new `GpuBufferDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(size: f64, usage: u32) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_size(size); ret.set_usage(usage); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_mapped_at_creation()` instead."] pub fn mapped_at_creation(&mut self, val: bool) -> &mut Self { self.set_mapped_at_creation(val); self } #[deprecated = "Use `set_size()` instead."] pub fn size(&mut self, val: f64) -> &mut Self { self.set_size(val); self } #[deprecated = "Use `set_usage()` instead."] pub fn usage(&mut self, val: u32) -> &mut Self { self.set_usage(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuBufferMapState.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuBufferMapState` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBufferMapState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuBufferMapState { Unmapped = "unmapped", Pending = "pending", Mapped = "mapped", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCanvasAlphaMode.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuCanvasAlphaMode` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasAlphaMode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuCanvasAlphaMode { Opaque = "opaque", Premultiplied = "premultiplied", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCanvasConfiguration.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUCanvasConfiguration)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuCanvasConfiguration` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuCanvasConfiguration; #[doc = "Get the `alphaMode` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasAlphaMode`, `GpuCanvasConfiguration`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "alphaMode")] pub fn get_alpha_mode(this: &GpuCanvasConfiguration) -> Option; #[doc = "Change the `alphaMode` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasAlphaMode`, `GpuCanvasConfiguration`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "alphaMode")] pub fn set_alpha_mode(this: &GpuCanvasConfiguration, val: GpuCanvasAlphaMode); #[doc = "Get the `device` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`, `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "device")] pub fn get_device(this: &GpuCanvasConfiguration) -> GpuDevice; #[doc = "Change the `device` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`, `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "device")] pub fn set_device(this: &GpuCanvasConfiguration, val: &GpuDevice); #[doc = "Get the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "format")] pub fn get_format(this: &GpuCanvasConfiguration) -> GpuTextureFormat; #[doc = "Change the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "format")] pub fn set_format(this: &GpuCanvasConfiguration, val: GpuTextureFormat); #[doc = "Get the `toneMapping` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`, `GpuCanvasToneMapping`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "toneMapping")] pub fn get_tone_mapping(this: &GpuCanvasConfiguration) -> Option; #[doc = "Change the `toneMapping` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`, `GpuCanvasToneMapping`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "toneMapping")] pub fn set_tone_mapping(this: &GpuCanvasConfiguration, val: &GpuCanvasToneMapping); #[doc = "Get the `usage` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "usage")] pub fn get_usage(this: &GpuCanvasConfiguration) -> Option; #[doc = "Change the `usage` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "usage")] pub fn set_usage(this: &GpuCanvasConfiguration, val: u32); #[doc = "Get the `viewFormats` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "viewFormats")] pub fn get_view_formats(this: &GpuCanvasConfiguration) -> Option<::js_sys::Array>; #[doc = "Change the `viewFormats` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "viewFormats")] pub fn set_view_formats(this: &GpuCanvasConfiguration, val: &::wasm_bindgen::JsValue); } impl GpuCanvasConfiguration { #[doc = "Construct a new `GpuCanvasConfiguration`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`, `GpuDevice`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(device: &GpuDevice, format: GpuTextureFormat) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_device(device); ret.set_format(format); ret } #[deprecated = "Use `set_alpha_mode()` instead."] pub fn alpha_mode(&mut self, val: GpuCanvasAlphaMode) -> &mut Self { self.set_alpha_mode(val); self } #[deprecated = "Use `set_device()` instead."] pub fn device(&mut self, val: &GpuDevice) -> &mut Self { self.set_device(val); self } #[deprecated = "Use `set_format()` instead."] pub fn format(&mut self, val: GpuTextureFormat) -> &mut Self { self.set_format(val); self } #[deprecated = "Use `set_tone_mapping()` instead."] pub fn tone_mapping(&mut self, val: &GpuCanvasToneMapping) -> &mut Self { self.set_tone_mapping(val); self } #[deprecated = "Use `set_usage()` instead."] pub fn usage(&mut self, val: u32) -> &mut Self { self.set_usage(val); self } #[deprecated = "Use `set_view_formats()` instead."] pub fn view_formats(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_view_formats(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCanvasContext.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUCanvasContext , typescript_type = "GPUCanvasContext")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuCanvasContext` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCanvasContext)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasContext`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuCanvasContext; # [wasm_bindgen (structural , method , getter , js_class = "GPUCanvasContext" , js_name = canvas)] #[doc = "Getter for the `canvas` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCanvasContext/canvas)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasContext`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn canvas(this: &GpuCanvasContext) -> ::js_sys::Object; # [wasm_bindgen (catch , method , structural , js_class = "GPUCanvasContext" , js_name = configure)] #[doc = "The `configure()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCanvasContext/configure)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`, `GpuCanvasContext`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn configure( this: &GpuCanvasContext, configuration: &GpuCanvasConfiguration, ) -> Result<(), JsValue>; # [wasm_bindgen (method , structural , js_class = "GPUCanvasContext" , js_name = getConfiguration)] #[doc = "The `getConfiguration()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCanvasContext/getConfiguration)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasConfiguration`, `GpuCanvasContext`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn get_configuration(this: &GpuCanvasContext) -> Option; # [wasm_bindgen (catch , method , structural , js_class = "GPUCanvasContext" , js_name = getCurrentTexture)] #[doc = "The `getCurrentTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCanvasContext/getCurrentTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasContext`, `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn get_current_texture(this: &GpuCanvasContext) -> Result; # [wasm_bindgen (method , structural , js_class = "GPUCanvasContext" , js_name = unconfigure)] #[doc = "The `unconfigure()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCanvasContext/unconfigure)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasContext`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn unconfigure(this: &GpuCanvasContext); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCanvasToneMapping.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUCanvasToneMapping)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuCanvasToneMapping` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasToneMapping`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuCanvasToneMapping; #[doc = "Get the `mode` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasToneMapping`, `GpuCanvasToneMappingMode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "mode")] pub fn get_mode(this: &GpuCanvasToneMapping) -> Option; #[doc = "Change the `mode` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasToneMapping`, `GpuCanvasToneMappingMode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "mode")] pub fn set_mode(this: &GpuCanvasToneMapping, val: GpuCanvasToneMappingMode); } impl GpuCanvasToneMapping { #[doc = "Construct a new `GpuCanvasToneMapping`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasToneMapping`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_mode()` instead."] pub fn mode(&mut self, val: GpuCanvasToneMappingMode) -> &mut Self { self.set_mode(val); self } } impl Default for GpuCanvasToneMapping { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCanvasToneMappingMode.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuCanvasToneMappingMode` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCanvasToneMappingMode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuCanvasToneMappingMode { Standard = "standard", Extended = "extended", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuColorDict.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUColorDict)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuColorDict` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuColorDict; #[doc = "Get the `a` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "a")] pub fn get_a(this: &GpuColorDict) -> f64; #[doc = "Change the `a` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "a")] pub fn set_a(this: &GpuColorDict, val: f64); #[doc = "Get the `b` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "b")] pub fn get_b(this: &GpuColorDict) -> f64; #[doc = "Change the `b` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "b")] pub fn set_b(this: &GpuColorDict, val: f64); #[doc = "Get the `g` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "g")] pub fn get_g(this: &GpuColorDict) -> f64; #[doc = "Change the `g` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "g")] pub fn set_g(this: &GpuColorDict, val: f64); #[doc = "Get the `r` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "r")] pub fn get_r(this: &GpuColorDict) -> f64; #[doc = "Change the `r` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "r")] pub fn set_r(this: &GpuColorDict, val: f64); } impl GpuColorDict { #[doc = "Construct a new `GpuColorDict`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(a: f64, b: f64, g: f64, r: f64) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_a(a); ret.set_b(b); ret.set_g(g); ret.set_r(r); ret } #[deprecated = "Use `set_a()` instead."] pub fn a(&mut self, val: f64) -> &mut Self { self.set_a(val); self } #[deprecated = "Use `set_b()` instead."] pub fn b(&mut self, val: f64) -> &mut Self { self.set_b(val); self } #[deprecated = "Use `set_g()` instead."] pub fn g(&mut self, val: f64) -> &mut Self { self.set_g(val); self } #[deprecated = "Use `set_r()` instead."] pub fn r(&mut self, val: f64) -> &mut Self { self.set_r(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuColorTargetState.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUColorTargetState)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuColorTargetState` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorTargetState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuColorTargetState; #[doc = "Get the `blend` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendState`, `GpuColorTargetState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "blend")] pub fn get_blend(this: &GpuColorTargetState) -> Option; #[doc = "Change the `blend` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBlendState`, `GpuColorTargetState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "blend")] pub fn set_blend(this: &GpuColorTargetState, val: &GpuBlendState); #[doc = "Get the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorTargetState`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "format")] pub fn get_format(this: &GpuColorTargetState) -> GpuTextureFormat; #[doc = "Change the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorTargetState`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "format")] pub fn set_format(this: &GpuColorTargetState, val: GpuTextureFormat); #[doc = "Get the `writeMask` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorTargetState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "writeMask")] pub fn get_write_mask(this: &GpuColorTargetState) -> Option; #[doc = "Change the `writeMask` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorTargetState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "writeMask")] pub fn set_write_mask(this: &GpuColorTargetState, val: u32); } impl GpuColorTargetState { #[doc = "Construct a new `GpuColorTargetState`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorTargetState`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(format: GpuTextureFormat) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_format(format); ret } #[deprecated = "Use `set_blend()` instead."] pub fn blend(&mut self, val: &GpuBlendState) -> &mut Self { self.set_blend(val); self } #[deprecated = "Use `set_format()` instead."] pub fn format(&mut self, val: GpuTextureFormat) -> &mut Self { self.set_format(val); self } #[deprecated = "Use `set_write_mask()` instead."] pub fn write_mask(&mut self, val: u32) -> &mut Self { self.set_write_mask(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCommandBuffer.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUCommandBuffer , typescript_type = "GPUCommandBuffer")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuCommandBuffer` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuCommandBuffer; # [wasm_bindgen (structural , method , getter , js_class = "GPUCommandBuffer" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandBuffer/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuCommandBuffer) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUCommandBuffer" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandBuffer/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandBuffer`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuCommandBuffer, value: &str); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCommandBufferDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUCommandBufferDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuCommandBufferDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuCommandBufferDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuCommandBufferDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuCommandBufferDescriptor, val: &str); } impl GpuCommandBufferDescriptor { #[doc = "Construct a new `GpuCommandBufferDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandBufferDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } } impl Default for GpuCommandBufferDescriptor { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCommandEncoder.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUCommandEncoder , typescript_type = "GPUCommandEncoder")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuCommandEncoder` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuCommandEncoder; # [wasm_bindgen (structural , method , getter , js_class = "GPUCommandEncoder" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuCommandEncoder) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUCommandEncoder" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuCommandEncoder, value: &str); # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = beginComputePass)] #[doc = "The `beginComputePass()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/beginComputePass)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`, `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn begin_compute_pass(this: &GpuCommandEncoder) -> GpuComputePassEncoder; # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = beginComputePass)] #[doc = "The `beginComputePass()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/beginComputePass)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`, `GpuComputePassDescriptor`, `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn begin_compute_pass_with_descriptor( this: &GpuCommandEncoder, descriptor: &GpuComputePassDescriptor, ) -> GpuComputePassEncoder; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = beginRenderPass)] #[doc = "The `beginRenderPass()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/beginRenderPass)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`, `GpuRenderPassDescriptor`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn begin_render_pass( this: &GpuCommandEncoder, descriptor: &GpuRenderPassDescriptor, ) -> Result; # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = clearBuffer)] #[doc = "The `clearBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/clearBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn clear_buffer(this: &GpuCommandEncoder, buffer: &GpuBuffer); # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = clearBuffer)] #[doc = "The `clearBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/clearBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn clear_buffer_with_u32(this: &GpuCommandEncoder, buffer: &GpuBuffer, offset: u32); # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = clearBuffer)] #[doc = "The `clearBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/clearBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn clear_buffer_with_f64(this: &GpuCommandEncoder, buffer: &GpuBuffer, offset: f64); # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = clearBuffer)] #[doc = "The `clearBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/clearBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn clear_buffer_with_u32_and_u32( this: &GpuCommandEncoder, buffer: &GpuBuffer, offset: u32, size: u32, ); # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = clearBuffer)] #[doc = "The `clearBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/clearBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn clear_buffer_with_f64_and_u32( this: &GpuCommandEncoder, buffer: &GpuBuffer, offset: f64, size: u32, ); # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = clearBuffer)] #[doc = "The `clearBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/clearBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn clear_buffer_with_u32_and_f64( this: &GpuCommandEncoder, buffer: &GpuBuffer, offset: u32, size: f64, ); # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = clearBuffer)] #[doc = "The `clearBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/clearBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn clear_buffer_with_f64_and_f64( this: &GpuCommandEncoder, buffer: &GpuBuffer, offset: f64, size: f64, ); # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToBuffer)] #[doc = "The `copyBufferToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_buffer_with_u32_and_u32( this: &GpuCommandEncoder, source: &GpuBuffer, source_offset: u32, destination: &GpuBuffer, destination_offset: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToBuffer)] #[doc = "The `copyBufferToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_buffer_with_f64_and_u32( this: &GpuCommandEncoder, source: &GpuBuffer, source_offset: f64, destination: &GpuBuffer, destination_offset: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToBuffer)] #[doc = "The `copyBufferToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_buffer_with_u32_and_f64( this: &GpuCommandEncoder, source: &GpuBuffer, source_offset: u32, destination: &GpuBuffer, destination_offset: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToBuffer)] #[doc = "The `copyBufferToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_buffer_with_f64_and_f64( this: &GpuCommandEncoder, source: &GpuBuffer, source_offset: f64, destination: &GpuBuffer, destination_offset: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToBuffer)] #[doc = "The `copyBufferToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_buffer_with_u32_and_u32_and_u32( this: &GpuCommandEncoder, source: &GpuBuffer, source_offset: u32, destination: &GpuBuffer, destination_offset: u32, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToBuffer)] #[doc = "The `copyBufferToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_buffer_with_f64_and_u32_and_u32( this: &GpuCommandEncoder, source: &GpuBuffer, source_offset: f64, destination: &GpuBuffer, destination_offset: u32, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToBuffer)] #[doc = "The `copyBufferToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_buffer_with_u32_and_f64_and_u32( this: &GpuCommandEncoder, source: &GpuBuffer, source_offset: u32, destination: &GpuBuffer, destination_offset: f64, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToBuffer)] #[doc = "The `copyBufferToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_buffer_with_f64_and_f64_and_u32( this: &GpuCommandEncoder, source: &GpuBuffer, source_offset: f64, destination: &GpuBuffer, destination_offset: f64, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToBuffer)] #[doc = "The `copyBufferToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_buffer_with_u32_and_u32_and_f64( this: &GpuCommandEncoder, source: &GpuBuffer, source_offset: u32, destination: &GpuBuffer, destination_offset: u32, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToBuffer)] #[doc = "The `copyBufferToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_buffer_with_f64_and_u32_and_f64( this: &GpuCommandEncoder, source: &GpuBuffer, source_offset: f64, destination: &GpuBuffer, destination_offset: u32, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToBuffer)] #[doc = "The `copyBufferToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_buffer_with_u32_and_f64_and_f64( this: &GpuCommandEncoder, source: &GpuBuffer, source_offset: u32, destination: &GpuBuffer, destination_offset: f64, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToBuffer)] #[doc = "The `copyBufferToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_buffer_with_f64_and_f64_and_f64( this: &GpuCommandEncoder, source: &GpuBuffer, source_offset: f64, destination: &GpuBuffer, destination_offset: f64, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToTexture)] #[doc = "The `copyBufferToTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`, `GpuTexelCopyBufferInfo`, `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_texture_with_u32_sequence( this: &GpuCommandEncoder, source: &GpuTexelCopyBufferInfo, destination: &GpuTexelCopyTextureInfo, copy_size: &::wasm_bindgen::JsValue, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyBufferToTexture)] #[doc = "The `copyBufferToTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyBufferToTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`, `GpuExtent3dDict`, `GpuTexelCopyBufferInfo`, `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_buffer_to_texture_with_gpu_extent_3d_dict( this: &GpuCommandEncoder, source: &GpuTexelCopyBufferInfo, destination: &GpuTexelCopyTextureInfo, copy_size: &GpuExtent3dDict, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyTextureToBuffer)] #[doc = "The `copyTextureToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyTextureToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`, `GpuTexelCopyBufferInfo`, `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_texture_to_buffer_with_u32_sequence( this: &GpuCommandEncoder, source: &GpuTexelCopyTextureInfo, destination: &GpuTexelCopyBufferInfo, copy_size: &::wasm_bindgen::JsValue, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyTextureToBuffer)] #[doc = "The `copyTextureToBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyTextureToBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`, `GpuExtent3dDict`, `GpuTexelCopyBufferInfo`, `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_texture_to_buffer_with_gpu_extent_3d_dict( this: &GpuCommandEncoder, source: &GpuTexelCopyTextureInfo, destination: &GpuTexelCopyBufferInfo, copy_size: &GpuExtent3dDict, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyTextureToTexture)] #[doc = "The `copyTextureToTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyTextureToTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`, `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_texture_to_texture_with_u32_sequence( this: &GpuCommandEncoder, source: &GpuTexelCopyTextureInfo, destination: &GpuTexelCopyTextureInfo, copy_size: &::wasm_bindgen::JsValue, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUCommandEncoder" , js_name = copyTextureToTexture)] #[doc = "The `copyTextureToTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/copyTextureToTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`, `GpuExtent3dDict`, `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_texture_to_texture_with_gpu_extent_3d_dict( this: &GpuCommandEncoder, source: &GpuTexelCopyTextureInfo, destination: &GpuTexelCopyTextureInfo, copy_size: &GpuExtent3dDict, ) -> Result<(), JsValue>; # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = finish)] #[doc = "The `finish()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/finish)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandBuffer`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn finish(this: &GpuCommandEncoder) -> GpuCommandBuffer; # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = finish)] #[doc = "The `finish()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/finish)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandBuffer`, `GpuCommandBufferDescriptor`, `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn finish_with_descriptor( this: &GpuCommandEncoder, descriptor: &GpuCommandBufferDescriptor, ) -> GpuCommandBuffer; # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = resolveQuerySet)] #[doc = "The `resolveQuerySet()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/resolveQuerySet)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`, `GpuQuerySet`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn resolve_query_set_with_u32( this: &GpuCommandEncoder, query_set: &GpuQuerySet, first_query: u32, query_count: u32, destination: &GpuBuffer, destination_offset: u32, ); # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = resolveQuerySet)] #[doc = "The `resolveQuerySet()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/resolveQuerySet)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuCommandEncoder`, `GpuQuerySet`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn resolve_query_set_with_f64( this: &GpuCommandEncoder, query_set: &GpuQuerySet, first_query: u32, query_count: u32, destination: &GpuBuffer, destination_offset: f64, ); # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = insertDebugMarker)] #[doc = "The `insertDebugMarker()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/insertDebugMarker)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn insert_debug_marker(this: &GpuCommandEncoder, marker_label: &str); # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = popDebugGroup)] #[doc = "The `popDebugGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/popDebugGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn pop_debug_group(this: &GpuCommandEncoder); # [wasm_bindgen (method , structural , js_class = "GPUCommandEncoder" , js_name = pushDebugGroup)] #[doc = "The `pushDebugGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/pushDebugGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn push_debug_group(this: &GpuCommandEncoder, group_label: &str); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCommandEncoderDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUCommandEncoderDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuCommandEncoderDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuCommandEncoderDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuCommandEncoderDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuCommandEncoderDescriptor, val: &str); } impl GpuCommandEncoderDescriptor { #[doc = "Construct a new `GpuCommandEncoderDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } } impl Default for GpuCommandEncoderDescriptor { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCompareFunction.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuCompareFunction` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompareFunction`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuCompareFunction { Never = "never", Less = "less", Equal = "equal", LessEqual = "less-equal", Greater = "greater", NotEqual = "not-equal", GreaterEqual = "greater-equal", Always = "always", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCompilationInfo.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUCompilationInfo , typescript_type = "GPUCompilationInfo")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuCompilationInfo` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCompilationInfo)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompilationInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuCompilationInfo; # [wasm_bindgen (structural , method , getter , js_class = "GPUCompilationInfo" , js_name = messages)] #[doc = "Getter for the `messages` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCompilationInfo/messages)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompilationInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn messages(this: &GpuCompilationInfo) -> ::js_sys::Array; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCompilationMessage.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUCompilationMessage , typescript_type = "GPUCompilationMessage")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuCompilationMessage` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCompilationMessage)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompilationMessage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuCompilationMessage; # [wasm_bindgen (structural , method , getter , js_class = "GPUCompilationMessage" , js_name = message)] #[doc = "Getter for the `message` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCompilationMessage/message)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompilationMessage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn message(this: &GpuCompilationMessage) -> ::alloc::string::String; # [wasm_bindgen (structural , method , getter , js_class = "GPUCompilationMessage" , js_name = type)] #[doc = "Getter for the `type` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCompilationMessage/type)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompilationMessage`, `GpuCompilationMessageType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn type_(this: &GpuCompilationMessage) -> GpuCompilationMessageType; # [wasm_bindgen (structural , method , getter , js_class = "GPUCompilationMessage" , js_name = lineNum)] #[doc = "Getter for the `lineNum` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCompilationMessage/lineNum)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompilationMessage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn line_num(this: &GpuCompilationMessage) -> f64; # [wasm_bindgen (structural , method , getter , js_class = "GPUCompilationMessage" , js_name = linePos)] #[doc = "Getter for the `linePos` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCompilationMessage/linePos)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompilationMessage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn line_pos(this: &GpuCompilationMessage) -> f64; # [wasm_bindgen (structural , method , getter , js_class = "GPUCompilationMessage" , js_name = offset)] #[doc = "Getter for the `offset` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCompilationMessage/offset)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompilationMessage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn offset(this: &GpuCompilationMessage) -> f64; # [wasm_bindgen (structural , method , getter , js_class = "GPUCompilationMessage" , js_name = length)] #[doc = "Getter for the `length` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUCompilationMessage/length)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompilationMessage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn length(this: &GpuCompilationMessage) -> f64; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCompilationMessageType.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuCompilationMessageType` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompilationMessageType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuCompilationMessageType { Error = "error", Warning = "warning", Info = "info", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuComputePassDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUComputePassDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuComputePassDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuComputePassDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuComputePassDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuComputePassDescriptor, val: &str); #[doc = "Get the `timestampWrites` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassDescriptor`, `GpuComputePassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "timestampWrites")] pub fn get_timestamp_writes( this: &GpuComputePassDescriptor, ) -> Option; #[doc = "Change the `timestampWrites` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassDescriptor`, `GpuComputePassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "timestampWrites")] pub fn set_timestamp_writes( this: &GpuComputePassDescriptor, val: &GpuComputePassTimestampWrites, ); } impl GpuComputePassDescriptor { #[doc = "Construct a new `GpuComputePassDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_timestamp_writes()` instead."] pub fn timestamp_writes(&mut self, val: &GpuComputePassTimestampWrites) -> &mut Self { self.set_timestamp_writes(val); self } } impl Default for GpuComputePassDescriptor { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuComputePassEncoder.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUComputePassEncoder , typescript_type = "GPUComputePassEncoder")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuComputePassEncoder` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuComputePassEncoder; # [wasm_bindgen (structural , method , getter , js_class = "GPUComputePassEncoder" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuComputePassEncoder) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUComputePassEncoder" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuComputePassEncoder, value: &str); # [wasm_bindgen (method , structural , js_class = "GPUComputePassEncoder" , js_name = dispatchWorkgroups)] #[doc = "The `dispatchWorkgroups()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/dispatchWorkgroups)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn dispatch_workgroups(this: &GpuComputePassEncoder, workgroup_count_x: u32); # [wasm_bindgen (method , structural , js_class = "GPUComputePassEncoder" , js_name = dispatchWorkgroups)] #[doc = "The `dispatchWorkgroups()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/dispatchWorkgroups)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn dispatch_workgroups_with_workgroup_count_y( this: &GpuComputePassEncoder, workgroup_count_x: u32, workgroup_count_y: u32, ); # [wasm_bindgen (method , structural , js_class = "GPUComputePassEncoder" , js_name = dispatchWorkgroups)] #[doc = "The `dispatchWorkgroups()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/dispatchWorkgroups)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn dispatch_workgroups_with_workgroup_count_y_and_workgroup_count_z( this: &GpuComputePassEncoder, workgroup_count_x: u32, workgroup_count_y: u32, workgroup_count_z: u32, ); # [wasm_bindgen (method , structural , js_class = "GPUComputePassEncoder" , js_name = dispatchWorkgroupsIndirect)] #[doc = "The `dispatchWorkgroupsIndirect()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/dispatchWorkgroupsIndirect)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn dispatch_workgroups_indirect_with_u32( this: &GpuComputePassEncoder, indirect_buffer: &GpuBuffer, indirect_offset: u32, ); # [wasm_bindgen (method , structural , js_class = "GPUComputePassEncoder" , js_name = dispatchWorkgroupsIndirect)] #[doc = "The `dispatchWorkgroupsIndirect()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/dispatchWorkgroupsIndirect)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn dispatch_workgroups_indirect_with_f64( this: &GpuComputePassEncoder, indirect_buffer: &GpuBuffer, indirect_offset: f64, ); # [wasm_bindgen (method , structural , js_class = "GPUComputePassEncoder" , js_name = end)] #[doc = "The `end()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/end)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn end(this: &GpuComputePassEncoder); # [wasm_bindgen (method , structural , js_class = "GPUComputePassEncoder" , js_name = setPipeline)] #[doc = "The `setPipeline()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/setPipeline)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassEncoder`, `GpuComputePipeline`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_pipeline(this: &GpuComputePassEncoder, pipeline: &GpuComputePipeline); # [wasm_bindgen (method , structural , js_class = "GPUComputePassEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group( this: &GpuComputePassEncoder, index: u32, bind_group: Option<&GpuBindGroup>, ); # [wasm_bindgen (method , structural , js_class = "GPUComputePassEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_sequence( this: &GpuComputePassEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets: &::wasm_bindgen::JsValue, ); # [wasm_bindgen (catch , method , structural , js_class = "GPUComputePassEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_slice_and_u32_and_dynamic_offsets_data_length( this: &GpuComputePassEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets_data: &[u32], dynamic_offsets_data_start: u32, dynamic_offsets_data_length: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUComputePassEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_array_and_u32_and_dynamic_offsets_data_length( this: &GpuComputePassEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets_data: &::js_sys::Uint32Array, dynamic_offsets_data_start: u32, dynamic_offsets_data_length: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUComputePassEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_slice_and_f64_and_dynamic_offsets_data_length( this: &GpuComputePassEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets_data: &[u32], dynamic_offsets_data_start: f64, dynamic_offsets_data_length: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUComputePassEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_array_and_f64_and_dynamic_offsets_data_length( this: &GpuComputePassEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets_data: &::js_sys::Uint32Array, dynamic_offsets_data_start: f64, dynamic_offsets_data_length: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (method , structural , js_class = "GPUComputePassEncoder" , js_name = insertDebugMarker)] #[doc = "The `insertDebugMarker()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/insertDebugMarker)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn insert_debug_marker(this: &GpuComputePassEncoder, marker_label: &str); # [wasm_bindgen (method , structural , js_class = "GPUComputePassEncoder" , js_name = popDebugGroup)] #[doc = "The `popDebugGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/popDebugGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn pop_debug_group(this: &GpuComputePassEncoder); # [wasm_bindgen (method , structural , js_class = "GPUComputePassEncoder" , js_name = pushDebugGroup)] #[doc = "The `pushDebugGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/pushDebugGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn push_debug_group(this: &GpuComputePassEncoder, group_label: &str); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuComputePassTimestampWrites.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUComputePassTimestampWrites)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuComputePassTimestampWrites` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuComputePassTimestampWrites; #[doc = "Get the `beginningOfPassWriteIndex` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "beginningOfPassWriteIndex")] pub fn get_beginning_of_pass_write_index(this: &GpuComputePassTimestampWrites) -> Option; #[doc = "Change the `beginningOfPassWriteIndex` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "beginningOfPassWriteIndex")] pub fn set_beginning_of_pass_write_index(this: &GpuComputePassTimestampWrites, val: u32); #[doc = "Get the `endOfPassWriteIndex` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "endOfPassWriteIndex")] pub fn get_end_of_pass_write_index(this: &GpuComputePassTimestampWrites) -> Option; #[doc = "Change the `endOfPassWriteIndex` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "endOfPassWriteIndex")] pub fn set_end_of_pass_write_index(this: &GpuComputePassTimestampWrites, val: u32); #[doc = "Get the `querySet` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassTimestampWrites`, `GpuQuerySet`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "querySet")] pub fn get_query_set(this: &GpuComputePassTimestampWrites) -> GpuQuerySet; #[doc = "Change the `querySet` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassTimestampWrites`, `GpuQuerySet`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "querySet")] pub fn set_query_set(this: &GpuComputePassTimestampWrites, val: &GpuQuerySet); } impl GpuComputePassTimestampWrites { #[doc = "Construct a new `GpuComputePassTimestampWrites`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePassTimestampWrites`, `GpuQuerySet`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(query_set: &GpuQuerySet) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_query_set(query_set); ret } #[deprecated = "Use `set_beginning_of_pass_write_index()` instead."] pub fn beginning_of_pass_write_index(&mut self, val: u32) -> &mut Self { self.set_beginning_of_pass_write_index(val); self } #[deprecated = "Use `set_end_of_pass_write_index()` instead."] pub fn end_of_pass_write_index(&mut self, val: u32) -> &mut Self { self.set_end_of_pass_write_index(val); self } #[deprecated = "Use `set_query_set()` instead."] pub fn query_set(&mut self, val: &GpuQuerySet) -> &mut Self { self.set_query_set(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuComputePipeline.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUComputePipeline , typescript_type = "GPUComputePipeline")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuComputePipeline` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePipeline)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePipeline`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuComputePipeline; # [wasm_bindgen (structural , method , getter , js_class = "GPUComputePipeline" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePipeline/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePipeline`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuComputePipeline) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUComputePipeline" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePipeline/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePipeline`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuComputePipeline, value: &str); # [wasm_bindgen (method , structural , js_class = "GPUComputePipeline" , js_name = getBindGroupLayout)] #[doc = "The `getBindGroupLayout()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePipeline/getBindGroupLayout)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayout`, `GpuComputePipeline`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn get_bind_group_layout(this: &GpuComputePipeline, index: u32) -> GpuBindGroupLayout; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuComputePipelineDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUComputePipelineDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuComputePipelineDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuComputePipelineDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuComputePipelineDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuComputePipelineDescriptor, val: &str); #[doc = "Get the `layout` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "layout")] pub fn get_layout(this: &GpuComputePipelineDescriptor) -> ::wasm_bindgen::JsValue; #[doc = "Change the `layout` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "layout")] pub fn set_layout(this: &GpuComputePipelineDescriptor, val: &::wasm_bindgen::JsValue); #[doc = "Get the `compute` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePipelineDescriptor`, `GpuProgrammableStage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "compute")] pub fn get_compute(this: &GpuComputePipelineDescriptor) -> GpuProgrammableStage; #[doc = "Change the `compute` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePipelineDescriptor`, `GpuProgrammableStage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "compute")] pub fn set_compute(this: &GpuComputePipelineDescriptor, val: &GpuProgrammableStage); } impl GpuComputePipelineDescriptor { #[doc = "Construct a new `GpuComputePipelineDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePipelineDescriptor`, `GpuProgrammableStage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(layout: &::wasm_bindgen::JsValue, compute: &GpuProgrammableStage) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_layout(layout); ret.set_compute(compute); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_layout()` instead."] pub fn layout(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_layout(val); self } #[deprecated = "Use `set_compute()` instead."] pub fn compute(&mut self, val: &GpuProgrammableStage) -> &mut Self { self.set_compute(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCopyExternalImageDestInfo.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUCopyExternalImageDestInfo)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuCopyExternalImageDestInfo` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuCopyExternalImageDestInfo; #[doc = "Get the `aspect` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`, `GpuTextureAspect`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "aspect")] pub fn get_aspect(this: &GpuCopyExternalImageDestInfo) -> Option; #[doc = "Change the `aspect` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`, `GpuTextureAspect`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "aspect")] pub fn set_aspect(this: &GpuCopyExternalImageDestInfo, val: GpuTextureAspect); #[doc = "Get the `mipLevel` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "mipLevel")] pub fn get_mip_level(this: &GpuCopyExternalImageDestInfo) -> Option; #[doc = "Change the `mipLevel` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "mipLevel")] pub fn set_mip_level(this: &GpuCopyExternalImageDestInfo, val: u32); #[doc = "Get the `origin` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "origin")] pub fn get_origin(this: &GpuCopyExternalImageDestInfo) -> ::wasm_bindgen::JsValue; #[doc = "Change the `origin` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "origin")] pub fn set_origin(this: &GpuCopyExternalImageDestInfo, val: &::wasm_bindgen::JsValue); #[doc = "Get the `texture` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`, `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "texture")] pub fn get_texture(this: &GpuCopyExternalImageDestInfo) -> GpuTexture; #[doc = "Change the `texture` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`, `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "texture")] pub fn set_texture(this: &GpuCopyExternalImageDestInfo, val: &GpuTexture); #[doc = "Get the `premultipliedAlpha` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "premultipliedAlpha")] pub fn get_premultiplied_alpha(this: &GpuCopyExternalImageDestInfo) -> Option; #[doc = "Change the `premultipliedAlpha` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "premultipliedAlpha")] pub fn set_premultiplied_alpha(this: &GpuCopyExternalImageDestInfo, val: bool); } impl GpuCopyExternalImageDestInfo { #[doc = "Construct a new `GpuCopyExternalImageDestInfo`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`, `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(texture: &GpuTexture) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_texture(texture); ret } #[deprecated = "Use `set_aspect()` instead."] pub fn aspect(&mut self, val: GpuTextureAspect) -> &mut Self { self.set_aspect(val); self } #[deprecated = "Use `set_mip_level()` instead."] pub fn mip_level(&mut self, val: u32) -> &mut Self { self.set_mip_level(val); self } #[deprecated = "Use `set_origin()` instead."] pub fn origin(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_origin(val); self } #[deprecated = "Use `set_texture()` instead."] pub fn texture(&mut self, val: &GpuTexture) -> &mut Self { self.set_texture(val); self } #[deprecated = "Use `set_premultiplied_alpha()` instead."] pub fn premultiplied_alpha(&mut self, val: bool) -> &mut Self { self.set_premultiplied_alpha(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCopyExternalImageSourceInfo.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUCopyExternalImageSourceInfo)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuCopyExternalImageSourceInfo` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageSourceInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuCopyExternalImageSourceInfo; #[doc = "Get the `flipY` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageSourceInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "flipY")] pub fn get_flip_y(this: &GpuCopyExternalImageSourceInfo) -> Option; #[doc = "Change the `flipY` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageSourceInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "flipY")] pub fn set_flip_y(this: &GpuCopyExternalImageSourceInfo, val: bool); #[doc = "Get the `origin` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageSourceInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "origin")] pub fn get_origin(this: &GpuCopyExternalImageSourceInfo) -> ::wasm_bindgen::JsValue; #[doc = "Change the `origin` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageSourceInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "origin")] pub fn set_origin(this: &GpuCopyExternalImageSourceInfo, val: &::wasm_bindgen::JsValue); #[doc = "Get the `source` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageSourceInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "source")] pub fn get_source(this: &GpuCopyExternalImageSourceInfo) -> ::js_sys::Object; #[doc = "Change the `source` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageSourceInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "source")] pub fn set_source(this: &GpuCopyExternalImageSourceInfo, val: &::js_sys::Object); } impl GpuCopyExternalImageSourceInfo { #[doc = "Construct a new `GpuCopyExternalImageSourceInfo`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageSourceInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(source: &::js_sys::Object) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_source(source); ret } #[deprecated = "Use `set_flip_y()` instead."] pub fn flip_y(&mut self, val: bool) -> &mut Self { self.set_flip_y(val); self } #[deprecated = "Use `set_origin()` instead."] pub fn origin(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_origin(val); self } #[deprecated = "Use `set_source()` instead."] pub fn source(&mut self, val: &::js_sys::Object) -> &mut Self { self.set_source(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuCullMode.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuCullMode` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCullMode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuCullMode { None = "none", Front = "front", Back = "back", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuDepthStencilState.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUDepthStencilState)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuDepthStencilState` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuDepthStencilState; #[doc = "Get the `depthBias` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthBias")] pub fn get_depth_bias(this: &GpuDepthStencilState) -> Option; #[doc = "Change the `depthBias` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthBias")] pub fn set_depth_bias(this: &GpuDepthStencilState, val: i32); #[doc = "Get the `depthBiasClamp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthBiasClamp")] pub fn get_depth_bias_clamp(this: &GpuDepthStencilState) -> Option; #[doc = "Change the `depthBiasClamp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthBiasClamp")] pub fn set_depth_bias_clamp(this: &GpuDepthStencilState, val: f32); #[doc = "Get the `depthBiasSlopeScale` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthBiasSlopeScale")] pub fn get_depth_bias_slope_scale(this: &GpuDepthStencilState) -> Option; #[doc = "Change the `depthBiasSlopeScale` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthBiasSlopeScale")] pub fn set_depth_bias_slope_scale(this: &GpuDepthStencilState, val: f32); #[doc = "Get the `depthCompare` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompareFunction`, `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthCompare")] pub fn get_depth_compare(this: &GpuDepthStencilState) -> Option; #[doc = "Change the `depthCompare` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompareFunction`, `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthCompare")] pub fn set_depth_compare(this: &GpuDepthStencilState, val: GpuCompareFunction); #[doc = "Get the `depthWriteEnabled` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthWriteEnabled")] pub fn get_depth_write_enabled(this: &GpuDepthStencilState) -> Option; #[doc = "Change the `depthWriteEnabled` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthWriteEnabled")] pub fn set_depth_write_enabled(this: &GpuDepthStencilState, val: bool); #[doc = "Get the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "format")] pub fn get_format(this: &GpuDepthStencilState) -> GpuTextureFormat; #[doc = "Change the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "format")] pub fn set_format(this: &GpuDepthStencilState, val: GpuTextureFormat); #[doc = "Get the `stencilBack` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`, `GpuStencilFaceState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "stencilBack")] pub fn get_stencil_back(this: &GpuDepthStencilState) -> Option; #[doc = "Change the `stencilBack` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`, `GpuStencilFaceState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "stencilBack")] pub fn set_stencil_back(this: &GpuDepthStencilState, val: &GpuStencilFaceState); #[doc = "Get the `stencilFront` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`, `GpuStencilFaceState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "stencilFront")] pub fn get_stencil_front(this: &GpuDepthStencilState) -> Option; #[doc = "Change the `stencilFront` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`, `GpuStencilFaceState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "stencilFront")] pub fn set_stencil_front(this: &GpuDepthStencilState, val: &GpuStencilFaceState); #[doc = "Get the `stencilReadMask` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "stencilReadMask")] pub fn get_stencil_read_mask(this: &GpuDepthStencilState) -> Option; #[doc = "Change the `stencilReadMask` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "stencilReadMask")] pub fn set_stencil_read_mask(this: &GpuDepthStencilState, val: u32); #[doc = "Get the `stencilWriteMask` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "stencilWriteMask")] pub fn get_stencil_write_mask(this: &GpuDepthStencilState) -> Option; #[doc = "Change the `stencilWriteMask` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "stencilWriteMask")] pub fn set_stencil_write_mask(this: &GpuDepthStencilState, val: u32); } impl GpuDepthStencilState { #[doc = "Construct a new `GpuDepthStencilState`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(format: GpuTextureFormat) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_format(format); ret } #[deprecated = "Use `set_depth_bias()` instead."] pub fn depth_bias(&mut self, val: i32) -> &mut Self { self.set_depth_bias(val); self } #[deprecated = "Use `set_depth_bias_clamp()` instead."] pub fn depth_bias_clamp(&mut self, val: f32) -> &mut Self { self.set_depth_bias_clamp(val); self } #[deprecated = "Use `set_depth_bias_slope_scale()` instead."] pub fn depth_bias_slope_scale(&mut self, val: f32) -> &mut Self { self.set_depth_bias_slope_scale(val); self } #[deprecated = "Use `set_depth_compare()` instead."] pub fn depth_compare(&mut self, val: GpuCompareFunction) -> &mut Self { self.set_depth_compare(val); self } #[deprecated = "Use `set_depth_write_enabled()` instead."] pub fn depth_write_enabled(&mut self, val: bool) -> &mut Self { self.set_depth_write_enabled(val); self } #[deprecated = "Use `set_format()` instead."] pub fn format(&mut self, val: GpuTextureFormat) -> &mut Self { self.set_format(val); self } #[deprecated = "Use `set_stencil_back()` instead."] pub fn stencil_back(&mut self, val: &GpuStencilFaceState) -> &mut Self { self.set_stencil_back(val); self } #[deprecated = "Use `set_stencil_front()` instead."] pub fn stencil_front(&mut self, val: &GpuStencilFaceState) -> &mut Self { self.set_stencil_front(val); self } #[deprecated = "Use `set_stencil_read_mask()` instead."] pub fn stencil_read_mask(&mut self, val: u32) -> &mut Self { self.set_stencil_read_mask(val); self } #[deprecated = "Use `set_stencil_write_mask()` instead."] pub fn stencil_write_mask(&mut self, val: u32) -> &mut Self { self.set_stencil_write_mask(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuDevice.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = EventTarget , extends = :: js_sys :: Object , js_name = GPUDevice , typescript_type = "GPUDevice")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuDevice` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuDevice; # [wasm_bindgen (structural , method , getter , js_class = "GPUDevice" , js_name = features)] #[doc = "Getter for the `features` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/features)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuSupportedFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn features(this: &GpuDevice) -> GpuSupportedFeatures; # [wasm_bindgen (structural , method , getter , js_class = "GPUDevice" , js_name = limits)] #[doc = "Getter for the `limits` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/limits)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn limits(this: &GpuDevice) -> GpuSupportedLimits; # [wasm_bindgen (structural , method , getter , js_class = "GPUDevice" , js_name = adapterInfo)] #[doc = "Getter for the `adapterInfo` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/adapterInfo)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAdapterInfo`, `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn adapter_info(this: &GpuDevice) -> GpuAdapterInfo; # [wasm_bindgen (structural , method , getter , js_class = "GPUDevice" , js_name = queue)] #[doc = "Getter for the `queue` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/queue)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn queue(this: &GpuDevice) -> GpuQueue; # [wasm_bindgen (structural , method , getter , js_class = "GPUDevice" , js_name = lost)] #[doc = "Getter for the `lost` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/lost)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn lost(this: &GpuDevice) -> ::js_sys::Promise; # [wasm_bindgen (structural , method , getter , js_class = "GPUDevice" , js_name = onuncapturederror)] #[doc = "Getter for the `onuncapturederror` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/onuncapturederror)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn onuncapturederror(this: &GpuDevice) -> Option<::js_sys::Function>; # [wasm_bindgen (structural , method , setter , js_class = "GPUDevice" , js_name = onuncapturederror)] #[doc = "Setter for the `onuncapturederror` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/onuncapturederror)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_onuncapturederror(this: &GpuDevice, value: Option<&::js_sys::Function>); # [wasm_bindgen (structural , method , getter , js_class = "GPUDevice" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuDevice) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUDevice" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuDevice, value: &str); # [wasm_bindgen (method , structural , js_class = "GPUDevice" , js_name = createBindGroup)] #[doc = "The `createBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuBindGroupDescriptor`, `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_bind_group(this: &GpuDevice, descriptor: &GpuBindGroupDescriptor) -> GpuBindGroup; # [wasm_bindgen (catch , method , structural , js_class = "GPUDevice" , js_name = createBindGroupLayout)] #[doc = "The `createBindGroupLayout()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createBindGroupLayout)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayout`, `GpuBindGroupLayoutDescriptor`, `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_bind_group_layout( this: &GpuDevice, descriptor: &GpuBindGroupLayoutDescriptor, ) -> Result; # [wasm_bindgen (catch , method , structural , js_class = "GPUDevice" , js_name = createBuffer)] #[doc = "The `createBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuBufferDescriptor`, `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_buffer( this: &GpuDevice, descriptor: &GpuBufferDescriptor, ) -> Result; # [wasm_bindgen (method , structural , js_class = "GPUDevice" , js_name = createCommandEncoder)] #[doc = "The `createCommandEncoder()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createCommandEncoder)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`, `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_command_encoder(this: &GpuDevice) -> GpuCommandEncoder; # [wasm_bindgen (method , structural , js_class = "GPUDevice" , js_name = createCommandEncoder)] #[doc = "The `createCommandEncoder()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createCommandEncoder)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCommandEncoder`, `GpuCommandEncoderDescriptor`, `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_command_encoder_with_descriptor( this: &GpuDevice, descriptor: &GpuCommandEncoderDescriptor, ) -> GpuCommandEncoder; # [wasm_bindgen (method , structural , js_class = "GPUDevice" , js_name = createComputePipeline)] #[doc = "The `createComputePipeline()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createComputePipeline)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePipeline`, `GpuComputePipelineDescriptor`, `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_compute_pipeline( this: &GpuDevice, descriptor: &GpuComputePipelineDescriptor, ) -> GpuComputePipeline; # [wasm_bindgen (method , structural , js_class = "GPUDevice" , js_name = createComputePipelineAsync)] #[doc = "The `createComputePipelineAsync()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createComputePipelineAsync)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuComputePipelineDescriptor`, `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_compute_pipeline_async( this: &GpuDevice, descriptor: &GpuComputePipelineDescriptor, ) -> ::js_sys::Promise; # [wasm_bindgen (method , structural , js_class = "GPUDevice" , js_name = createPipelineLayout)] #[doc = "The `createPipelineLayout()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createPipelineLayout)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuPipelineLayout`, `GpuPipelineLayoutDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_pipeline_layout( this: &GpuDevice, descriptor: &GpuPipelineLayoutDescriptor, ) -> GpuPipelineLayout; # [wasm_bindgen (catch , method , structural , js_class = "GPUDevice" , js_name = createQuerySet)] #[doc = "The `createQuerySet()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createQuerySet)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuQuerySet`, `GpuQuerySetDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_query_set( this: &GpuDevice, descriptor: &GpuQuerySetDescriptor, ) -> Result; # [wasm_bindgen (catch , method , structural , js_class = "GPUDevice" , js_name = createRenderBundleEncoder)] #[doc = "The `createRenderBundleEncoder()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createRenderBundleEncoder)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuRenderBundleEncoder`, `GpuRenderBundleEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_render_bundle_encoder( this: &GpuDevice, descriptor: &GpuRenderBundleEncoderDescriptor, ) -> Result; # [wasm_bindgen (catch , method , structural , js_class = "GPUDevice" , js_name = createRenderPipeline)] #[doc = "The `createRenderPipeline()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createRenderPipeline)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuRenderPipeline`, `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_render_pipeline( this: &GpuDevice, descriptor: &GpuRenderPipelineDescriptor, ) -> Result; # [wasm_bindgen (method , structural , js_class = "GPUDevice" , js_name = createRenderPipelineAsync)] #[doc = "The `createRenderPipelineAsync()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createRenderPipelineAsync)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_render_pipeline_async( this: &GpuDevice, descriptor: &GpuRenderPipelineDescriptor, ) -> ::js_sys::Promise; # [wasm_bindgen (method , structural , js_class = "GPUDevice" , js_name = createSampler)] #[doc = "The `createSampler()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createSampler)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuSampler`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_sampler(this: &GpuDevice) -> GpuSampler; # [wasm_bindgen (method , structural , js_class = "GPUDevice" , js_name = createSampler)] #[doc = "The `createSampler()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createSampler)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuSampler`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_sampler_with_descriptor( this: &GpuDevice, descriptor: &GpuSamplerDescriptor, ) -> GpuSampler; # [wasm_bindgen (method , structural , js_class = "GPUDevice" , js_name = createShaderModule)] #[doc = "The `createShaderModule()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createShaderModule)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuShaderModule`, `GpuShaderModuleDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_shader_module( this: &GpuDevice, descriptor: &GpuShaderModuleDescriptor, ) -> GpuShaderModule; # [wasm_bindgen (catch , method , structural , js_class = "GPUDevice" , js_name = createTexture)] #[doc = "The `createTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuTexture`, `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_texture( this: &GpuDevice, descriptor: &GpuTextureDescriptor, ) -> Result; # [wasm_bindgen (method , structural , js_class = "GPUDevice" , js_name = destroy)] #[doc = "The `destroy()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/destroy)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn destroy(this: &GpuDevice); # [wasm_bindgen (catch , method , structural , js_class = "GPUDevice" , js_name = importExternalTexture)] #[doc = "The `importExternalTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/importExternalTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuExternalTexture`, `GpuExternalTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn import_external_texture( this: &GpuDevice, descriptor: &GpuExternalTextureDescriptor, ) -> Result; # [wasm_bindgen (method , structural , js_class = "GPUDevice" , js_name = popErrorScope)] #[doc = "The `popErrorScope()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/popErrorScope)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn pop_error_scope(this: &GpuDevice) -> ::js_sys::Promise; # [wasm_bindgen (method , structural , js_class = "GPUDevice" , js_name = pushErrorScope)] #[doc = "The `pushErrorScope()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/pushErrorScope)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDevice`, `GpuErrorFilter`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn push_error_scope(this: &GpuDevice, filter: GpuErrorFilter); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuDeviceDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUDeviceDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuDeviceDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuDeviceDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuDeviceDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuDeviceDescriptor, val: &str); #[doc = "Get the `defaultQueue` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceDescriptor`, `GpuQueueDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "defaultQueue")] pub fn get_default_queue(this: &GpuDeviceDescriptor) -> Option; #[doc = "Change the `defaultQueue` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceDescriptor`, `GpuQueueDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "defaultQueue")] pub fn set_default_queue(this: &GpuDeviceDescriptor, val: &GpuQueueDescriptor); #[doc = "Get the `requiredFeatures` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "requiredFeatures")] pub fn get_required_features(this: &GpuDeviceDescriptor) -> Option<::js_sys::Array>; #[doc = "Change the `requiredFeatures` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "requiredFeatures")] pub fn set_required_features(this: &GpuDeviceDescriptor, val: &::wasm_bindgen::JsValue); #[doc = "Get the `requiredLimits` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "requiredLimits")] pub fn get_required_limits(this: &GpuDeviceDescriptor) -> Option<::js_sys::Object>; #[doc = "Change the `requiredLimits` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "requiredLimits")] pub fn set_required_limits(this: &GpuDeviceDescriptor, val: &::js_sys::Object); } impl GpuDeviceDescriptor { #[doc = "Construct a new `GpuDeviceDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_default_queue()` instead."] pub fn default_queue(&mut self, val: &GpuQueueDescriptor) -> &mut Self { self.set_default_queue(val); self } #[deprecated = "Use `set_required_features()` instead."] pub fn required_features(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_required_features(val); self } #[deprecated = "Use `set_required_limits()` instead."] pub fn required_limits(&mut self, val: &::js_sys::Object) -> &mut Self { self.set_required_limits(val); self } } impl Default for GpuDeviceDescriptor { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuDeviceLostInfo.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUDeviceLostInfo , typescript_type = "GPUDeviceLostInfo")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuDeviceLostInfo` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDeviceLostInfo)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceLostInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuDeviceLostInfo; # [wasm_bindgen (structural , method , getter , js_class = "GPUDeviceLostInfo" , js_name = reason)] #[doc = "Getter for the `reason` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDeviceLostInfo/reason)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceLostInfo`, `GpuDeviceLostReason`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn reason(this: &GpuDeviceLostInfo) -> GpuDeviceLostReason; # [wasm_bindgen (structural , method , getter , js_class = "GPUDeviceLostInfo" , js_name = message)] #[doc = "Getter for the `message` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUDeviceLostInfo/message)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceLostInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn message(this: &GpuDeviceLostInfo) -> ::alloc::string::String; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuDeviceLostReason.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuDeviceLostReason` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDeviceLostReason`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuDeviceLostReason { Unknown = "unknown", Destroyed = "destroyed", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuError.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUError , typescript_type = "GPUError")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuError` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUError)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuError`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuError; # [wasm_bindgen (structural , method , getter , js_class = "GPUError" , js_name = message)] #[doc = "Getter for the `message` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUError/message)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuError`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn message(this: &GpuError) -> ::alloc::string::String; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuErrorFilter.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuErrorFilter` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuErrorFilter`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuErrorFilter { Validation = "validation", OutOfMemory = "out-of-memory", Internal = "internal", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuExtent3dDict.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUExtent3DDict)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuExtent3dDict` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExtent3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuExtent3dDict; #[doc = "Get the `depthOrArrayLayers` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExtent3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthOrArrayLayers")] pub fn get_depth_or_array_layers(this: &GpuExtent3dDict) -> Option; #[doc = "Change the `depthOrArrayLayers` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExtent3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthOrArrayLayers")] pub fn set_depth_or_array_layers(this: &GpuExtent3dDict, val: u32); #[doc = "Get the `height` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExtent3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "height")] pub fn get_height(this: &GpuExtent3dDict) -> Option; #[doc = "Change the `height` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExtent3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "height")] pub fn set_height(this: &GpuExtent3dDict, val: u32); #[doc = "Get the `width` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExtent3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "width")] pub fn get_width(this: &GpuExtent3dDict) -> u32; #[doc = "Change the `width` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExtent3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "width")] pub fn set_width(this: &GpuExtent3dDict, val: u32); } impl GpuExtent3dDict { #[doc = "Construct a new `GpuExtent3dDict`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExtent3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(width: u32) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_width(width); ret } #[deprecated = "Use `set_depth_or_array_layers()` instead."] pub fn depth_or_array_layers(&mut self, val: u32) -> &mut Self { self.set_depth_or_array_layers(val); self } #[deprecated = "Use `set_height()` instead."] pub fn height(&mut self, val: u32) -> &mut Self { self.set_height(val); self } #[deprecated = "Use `set_width()` instead."] pub fn width(&mut self, val: u32) -> &mut Self { self.set_width(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuExternalTexture.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUExternalTexture , typescript_type = "GPUExternalTexture")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuExternalTexture` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUExternalTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExternalTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuExternalTexture; # [wasm_bindgen (structural , method , getter , js_class = "GPUExternalTexture" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUExternalTexture/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExternalTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuExternalTexture) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUExternalTexture" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUExternalTexture/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExternalTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuExternalTexture, value: &str); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuExternalTextureBindingLayout.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUExternalTextureBindingLayout)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuExternalTextureBindingLayout` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExternalTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuExternalTextureBindingLayout; } impl GpuExternalTextureBindingLayout { #[doc = "Construct a new `GpuExternalTextureBindingLayout`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExternalTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } } impl Default for GpuExternalTextureBindingLayout { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuExternalTextureDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUExternalTextureDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuExternalTextureDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExternalTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuExternalTextureDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExternalTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuExternalTextureDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExternalTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuExternalTextureDescriptor, val: &str); #[doc = "Get the `source` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExternalTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "source")] pub fn get_source(this: &GpuExternalTextureDescriptor) -> ::js_sys::Object; #[doc = "Change the `source` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExternalTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "source")] pub fn set_source(this: &GpuExternalTextureDescriptor, val: &::js_sys::Object); } impl GpuExternalTextureDescriptor { #[doc = "Construct a new `GpuExternalTextureDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExternalTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(source: &::js_sys::Object) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_source(source); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_source()` instead."] pub fn source(&mut self, val: &::js_sys::Object) -> &mut Self { self.set_source(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuFeatureName.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuFeatureName` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFeatureName`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuFeatureName { DepthClipControl = "depth-clip-control", Depth32floatStencil8 = "depth32float-stencil8", TextureCompressionBc = "texture-compression-bc", TextureCompressionBcSliced3d = "texture-compression-bc-sliced-3d", TextureCompressionEtc2 = "texture-compression-etc2", TextureCompressionAstc = "texture-compression-astc", TextureCompressionAstcSliced3d = "texture-compression-astc-sliced-3d", TimestampQuery = "timestamp-query", IndirectFirstInstance = "indirect-first-instance", ShaderF16 = "shader-f16", Rg11b10ufloatRenderable = "rg11b10ufloat-renderable", Bgra8unormStorage = "bgra8unorm-storage", Float32Filterable = "float32-filterable", Float32Blendable = "float32-blendable", ClipDistances = "clip-distances", DualSourceBlending = "dual-source-blending", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuFilterMode.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuFilterMode` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFilterMode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuFilterMode { Nearest = "nearest", Linear = "linear", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuFragmentState.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUFragmentState)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuFragmentState` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFragmentState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuFragmentState; #[doc = "Get the `constants` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFragmentState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "constants")] pub fn get_constants(this: &GpuFragmentState) -> Option<::js_sys::Object>; #[doc = "Change the `constants` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFragmentState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "constants")] pub fn set_constants(this: &GpuFragmentState, val: &::js_sys::Object); #[doc = "Get the `entryPoint` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFragmentState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "entryPoint")] pub fn get_entry_point(this: &GpuFragmentState) -> Option<::alloc::string::String>; #[doc = "Change the `entryPoint` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFragmentState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "entryPoint")] pub fn set_entry_point(this: &GpuFragmentState, val: &str); #[doc = "Get the `module` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFragmentState`, `GpuShaderModule`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "module")] pub fn get_module(this: &GpuFragmentState) -> GpuShaderModule; #[doc = "Change the `module` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFragmentState`, `GpuShaderModule`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "module")] pub fn set_module(this: &GpuFragmentState, val: &GpuShaderModule); #[doc = "Get the `targets` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFragmentState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "targets")] pub fn get_targets(this: &GpuFragmentState) -> ::js_sys::Array; #[doc = "Change the `targets` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFragmentState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "targets")] pub fn set_targets(this: &GpuFragmentState, val: &::wasm_bindgen::JsValue); } impl GpuFragmentState { #[doc = "Construct a new `GpuFragmentState`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFragmentState`, `GpuShaderModule`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(module: &GpuShaderModule, targets: &::wasm_bindgen::JsValue) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_module(module); ret.set_targets(targets); ret } #[deprecated = "Use `set_constants()` instead."] pub fn constants(&mut self, val: &::js_sys::Object) -> &mut Self { self.set_constants(val); self } #[deprecated = "Use `set_entry_point()` instead."] pub fn entry_point(&mut self, val: &str) -> &mut Self { self.set_entry_point(val); self } #[deprecated = "Use `set_module()` instead."] pub fn module(&mut self, val: &GpuShaderModule) -> &mut Self { self.set_module(val); self } #[deprecated = "Use `set_targets()` instead."] pub fn targets(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_targets(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuFrontFace.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuFrontFace` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFrontFace`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuFrontFace { Ccw = "ccw", Cw = "cw", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuIndexFormat.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuIndexFormat` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuIndexFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuIndexFormat { Uint16 = "uint16", Uint32 = "uint32", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuLoadOp.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuLoadOp` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuLoadOp`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuLoadOp { Load = "load", Clear = "clear", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuMipmapFilterMode.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuMipmapFilterMode` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuMipmapFilterMode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuMipmapFilterMode { Nearest = "nearest", Linear = "linear", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuMultisampleState.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUMultisampleState)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuMultisampleState` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuMultisampleState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuMultisampleState; #[doc = "Get the `alphaToCoverageEnabled` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuMultisampleState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "alphaToCoverageEnabled")] pub fn get_alpha_to_coverage_enabled(this: &GpuMultisampleState) -> Option; #[doc = "Change the `alphaToCoverageEnabled` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuMultisampleState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "alphaToCoverageEnabled")] pub fn set_alpha_to_coverage_enabled(this: &GpuMultisampleState, val: bool); #[doc = "Get the `count` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuMultisampleState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "count")] pub fn get_count(this: &GpuMultisampleState) -> Option; #[doc = "Change the `count` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuMultisampleState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "count")] pub fn set_count(this: &GpuMultisampleState, val: u32); #[doc = "Get the `mask` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuMultisampleState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "mask")] pub fn get_mask(this: &GpuMultisampleState) -> Option; #[doc = "Change the `mask` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuMultisampleState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "mask")] pub fn set_mask(this: &GpuMultisampleState, val: u32); } impl GpuMultisampleState { #[doc = "Construct a new `GpuMultisampleState`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuMultisampleState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_alpha_to_coverage_enabled()` instead."] pub fn alpha_to_coverage_enabled(&mut self, val: bool) -> &mut Self { self.set_alpha_to_coverage_enabled(val); self } #[deprecated = "Use `set_count()` instead."] pub fn count(&mut self, val: u32) -> &mut Self { self.set_count(val); self } #[deprecated = "Use `set_mask()` instead."] pub fn mask(&mut self, val: u32) -> &mut Self { self.set_mask(val); self } } impl Default for GpuMultisampleState { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuObjectDescriptorBase.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUObjectDescriptorBase)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuObjectDescriptorBase` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuObjectDescriptorBase`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuObjectDescriptorBase; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuObjectDescriptorBase`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuObjectDescriptorBase) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuObjectDescriptorBase`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuObjectDescriptorBase, val: &str); } impl GpuObjectDescriptorBase { #[doc = "Construct a new `GpuObjectDescriptorBase`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuObjectDescriptorBase`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } } impl Default for GpuObjectDescriptorBase { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuOrigin2dDict.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUOrigin2DDict)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuOrigin2dDict` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin2dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuOrigin2dDict; #[doc = "Get the `x` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin2dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "x")] pub fn get_x(this: &GpuOrigin2dDict) -> Option; #[doc = "Change the `x` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin2dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "x")] pub fn set_x(this: &GpuOrigin2dDict, val: u32); #[doc = "Get the `y` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin2dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "y")] pub fn get_y(this: &GpuOrigin2dDict) -> Option; #[doc = "Change the `y` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin2dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "y")] pub fn set_y(this: &GpuOrigin2dDict, val: u32); } impl GpuOrigin2dDict { #[doc = "Construct a new `GpuOrigin2dDict`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin2dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_x()` instead."] pub fn x(&mut self, val: u32) -> &mut Self { self.set_x(val); self } #[deprecated = "Use `set_y()` instead."] pub fn y(&mut self, val: u32) -> &mut Self { self.set_y(val); self } } impl Default for GpuOrigin2dDict { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuOrigin3dDict.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUOrigin3DDict)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuOrigin3dDict` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuOrigin3dDict; #[doc = "Get the `x` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "x")] pub fn get_x(this: &GpuOrigin3dDict) -> Option; #[doc = "Change the `x` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "x")] pub fn set_x(this: &GpuOrigin3dDict, val: u32); #[doc = "Get the `y` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "y")] pub fn get_y(this: &GpuOrigin3dDict) -> Option; #[doc = "Change the `y` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "y")] pub fn set_y(this: &GpuOrigin3dDict, val: u32); #[doc = "Get the `z` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "z")] pub fn get_z(this: &GpuOrigin3dDict) -> Option; #[doc = "Change the `z` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "z")] pub fn set_z(this: &GpuOrigin3dDict, val: u32); } impl GpuOrigin3dDict { #[doc = "Construct a new `GpuOrigin3dDict`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOrigin3dDict`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_x()` instead."] pub fn x(&mut self, val: u32) -> &mut Self { self.set_x(val); self } #[deprecated = "Use `set_y()` instead."] pub fn y(&mut self, val: u32) -> &mut Self { self.set_y(val); self } #[deprecated = "Use `set_z()` instead."] pub fn z(&mut self, val: u32) -> &mut Self { self.set_z(val); self } } impl Default for GpuOrigin3dDict { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuOutOfMemoryError.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = GpuError , extends = :: js_sys :: Object , js_name = GPUOutOfMemoryError , typescript_type = "GPUOutOfMemoryError")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuOutOfMemoryError` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUOutOfMemoryError)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOutOfMemoryError`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuOutOfMemoryError; #[wasm_bindgen(catch, constructor, js_class = "GPUOutOfMemoryError")] #[doc = "The `new GpuOutOfMemoryError(..)` constructor, creating a new instance of `GpuOutOfMemoryError`."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUOutOfMemoryError/GPUOutOfMemoryError)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuOutOfMemoryError`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(message: &str) -> Result; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuPipelineDescriptorBase.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUPipelineDescriptorBase)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuPipelineDescriptorBase` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineDescriptorBase`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuPipelineDescriptorBase; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineDescriptorBase`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuPipelineDescriptorBase) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineDescriptorBase`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuPipelineDescriptorBase, val: &str); #[doc = "Get the `layout` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineDescriptorBase`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "layout")] pub fn get_layout(this: &GpuPipelineDescriptorBase) -> ::wasm_bindgen::JsValue; #[doc = "Change the `layout` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineDescriptorBase`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "layout")] pub fn set_layout(this: &GpuPipelineDescriptorBase, val: &::wasm_bindgen::JsValue); } impl GpuPipelineDescriptorBase { #[doc = "Construct a new `GpuPipelineDescriptorBase`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineDescriptorBase`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(layout: &::wasm_bindgen::JsValue) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_layout(layout); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_layout()` instead."] pub fn layout(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_layout(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuPipelineLayout.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUPipelineLayout , typescript_type = "GPUPipelineLayout")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuPipelineLayout` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUPipelineLayout)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuPipelineLayout; # [wasm_bindgen (structural , method , getter , js_class = "GPUPipelineLayout" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUPipelineLayout/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuPipelineLayout) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUPipelineLayout" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUPipelineLayout/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuPipelineLayout, value: &str); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuPipelineLayoutDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUPipelineLayoutDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuPipelineLayoutDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineLayoutDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuPipelineLayoutDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineLayoutDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuPipelineLayoutDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineLayoutDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuPipelineLayoutDescriptor, val: &str); #[doc = "Get the `bindGroupLayouts` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineLayoutDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "bindGroupLayouts")] pub fn get_bind_group_layouts(this: &GpuPipelineLayoutDescriptor) -> ::js_sys::Array; #[doc = "Change the `bindGroupLayouts` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineLayoutDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "bindGroupLayouts")] pub fn set_bind_group_layouts( this: &GpuPipelineLayoutDescriptor, val: &::wasm_bindgen::JsValue, ); } impl GpuPipelineLayoutDescriptor { #[doc = "Construct a new `GpuPipelineLayoutDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPipelineLayoutDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(bind_group_layouts: &::wasm_bindgen::JsValue) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_bind_group_layouts(bind_group_layouts); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_bind_group_layouts()` instead."] pub fn bind_group_layouts(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_bind_group_layouts(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuPowerPreference.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuPowerPreference` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPowerPreference`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuPowerPreference { LowPower = "low-power", HighPerformance = "high-performance", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuPrimitiveState.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUPrimitiveState)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuPrimitiveState` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPrimitiveState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuPrimitiveState; #[doc = "Get the `cullMode` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCullMode`, `GpuPrimitiveState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "cullMode")] pub fn get_cull_mode(this: &GpuPrimitiveState) -> Option; #[doc = "Change the `cullMode` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCullMode`, `GpuPrimitiveState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "cullMode")] pub fn set_cull_mode(this: &GpuPrimitiveState, val: GpuCullMode); #[doc = "Get the `frontFace` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFrontFace`, `GpuPrimitiveState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "frontFace")] pub fn get_front_face(this: &GpuPrimitiveState) -> Option; #[doc = "Change the `frontFace` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFrontFace`, `GpuPrimitiveState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "frontFace")] pub fn set_front_face(this: &GpuPrimitiveState, val: GpuFrontFace); #[doc = "Get the `stripIndexFormat` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuIndexFormat`, `GpuPrimitiveState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "stripIndexFormat")] pub fn get_strip_index_format(this: &GpuPrimitiveState) -> Option; #[doc = "Change the `stripIndexFormat` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuIndexFormat`, `GpuPrimitiveState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "stripIndexFormat")] pub fn set_strip_index_format(this: &GpuPrimitiveState, val: GpuIndexFormat); #[doc = "Get the `topology` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPrimitiveState`, `GpuPrimitiveTopology`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "topology")] pub fn get_topology(this: &GpuPrimitiveState) -> Option; #[doc = "Change the `topology` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPrimitiveState`, `GpuPrimitiveTopology`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "topology")] pub fn set_topology(this: &GpuPrimitiveState, val: GpuPrimitiveTopology); #[doc = "Get the `unclippedDepth` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPrimitiveState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "unclippedDepth")] pub fn get_unclipped_depth(this: &GpuPrimitiveState) -> Option; #[doc = "Change the `unclippedDepth` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPrimitiveState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "unclippedDepth")] pub fn set_unclipped_depth(this: &GpuPrimitiveState, val: bool); } impl GpuPrimitiveState { #[doc = "Construct a new `GpuPrimitiveState`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPrimitiveState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_cull_mode()` instead."] pub fn cull_mode(&mut self, val: GpuCullMode) -> &mut Self { self.set_cull_mode(val); self } #[deprecated = "Use `set_front_face()` instead."] pub fn front_face(&mut self, val: GpuFrontFace) -> &mut Self { self.set_front_face(val); self } #[deprecated = "Use `set_strip_index_format()` instead."] pub fn strip_index_format(&mut self, val: GpuIndexFormat) -> &mut Self { self.set_strip_index_format(val); self } #[deprecated = "Use `set_topology()` instead."] pub fn topology(&mut self, val: GpuPrimitiveTopology) -> &mut Self { self.set_topology(val); self } #[deprecated = "Use `set_unclipped_depth()` instead."] pub fn unclipped_depth(&mut self, val: bool) -> &mut Self { self.set_unclipped_depth(val); self } } impl Default for GpuPrimitiveState { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuPrimitiveTopology.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuPrimitiveTopology` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPrimitiveTopology`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuPrimitiveTopology { PointList = "point-list", LineList = "line-list", LineStrip = "line-strip", TriangleList = "triangle-list", TriangleStrip = "triangle-strip", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuProgrammableStage.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUProgrammableStage)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuProgrammableStage` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuProgrammableStage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuProgrammableStage; #[doc = "Get the `constants` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuProgrammableStage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "constants")] pub fn get_constants(this: &GpuProgrammableStage) -> Option<::js_sys::Object>; #[doc = "Change the `constants` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuProgrammableStage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "constants")] pub fn set_constants(this: &GpuProgrammableStage, val: &::js_sys::Object); #[doc = "Get the `entryPoint` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuProgrammableStage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "entryPoint")] pub fn get_entry_point(this: &GpuProgrammableStage) -> Option<::alloc::string::String>; #[doc = "Change the `entryPoint` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuProgrammableStage`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "entryPoint")] pub fn set_entry_point(this: &GpuProgrammableStage, val: &str); #[doc = "Get the `module` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuProgrammableStage`, `GpuShaderModule`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "module")] pub fn get_module(this: &GpuProgrammableStage) -> GpuShaderModule; #[doc = "Change the `module` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuProgrammableStage`, `GpuShaderModule`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "module")] pub fn set_module(this: &GpuProgrammableStage, val: &GpuShaderModule); } impl GpuProgrammableStage { #[doc = "Construct a new `GpuProgrammableStage`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuProgrammableStage`, `GpuShaderModule`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(module: &GpuShaderModule) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_module(module); ret } #[deprecated = "Use `set_constants()` instead."] pub fn constants(&mut self, val: &::js_sys::Object) -> &mut Self { self.set_constants(val); self } #[deprecated = "Use `set_entry_point()` instead."] pub fn entry_point(&mut self, val: &str) -> &mut Self { self.set_entry_point(val); self } #[deprecated = "Use `set_module()` instead."] pub fn module(&mut self, val: &GpuShaderModule) -> &mut Self { self.set_module(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuQuerySet.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUQuerySet , typescript_type = "GPUQuerySet")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuQuerySet` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQuerySet)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySet`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuQuerySet; # [wasm_bindgen (structural , method , getter , js_class = "GPUQuerySet" , js_name = type)] #[doc = "Getter for the `type` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQuerySet/type)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySet`, `GpuQueryType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn type_(this: &GpuQuerySet) -> GpuQueryType; # [wasm_bindgen (structural , method , getter , js_class = "GPUQuerySet" , js_name = count)] #[doc = "Getter for the `count` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQuerySet/count)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySet`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn count(this: &GpuQuerySet) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUQuerySet" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQuerySet/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySet`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuQuerySet) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUQuerySet" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQuerySet/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySet`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuQuerySet, value: &str); # [wasm_bindgen (method , structural , js_class = "GPUQuerySet" , js_name = destroy)] #[doc = "The `destroy()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQuerySet/destroy)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySet`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn destroy(this: &GpuQuerySet); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuQuerySetDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUQuerySetDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuQuerySetDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySetDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuQuerySetDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySetDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuQuerySetDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySetDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuQuerySetDescriptor, val: &str); #[doc = "Get the `count` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySetDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "count")] pub fn get_count(this: &GpuQuerySetDescriptor) -> u32; #[doc = "Change the `count` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySetDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "count")] pub fn set_count(this: &GpuQuerySetDescriptor, val: u32); #[doc = "Get the `type` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySetDescriptor`, `GpuQueryType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "type")] pub fn get_type(this: &GpuQuerySetDescriptor) -> GpuQueryType; #[doc = "Change the `type` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySetDescriptor`, `GpuQueryType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "type")] pub fn set_type(this: &GpuQuerySetDescriptor, val: GpuQueryType); } impl GpuQuerySetDescriptor { #[doc = "Construct a new `GpuQuerySetDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySetDescriptor`, `GpuQueryType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(count: u32, type_: GpuQueryType) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_count(count); ret.set_type(type_); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_count()` instead."] pub fn count(&mut self, val: u32) -> &mut Self { self.set_count(val); self } #[deprecated = "Use `set_type()` instead."] pub fn type_(&mut self, val: GpuQueryType) -> &mut Self { self.set_type(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuQueryType.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuQueryType` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQueryType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuQueryType { Occlusion = "occlusion", Timestamp = "timestamp", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuQueue.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUQueue , typescript_type = "GPUQueue")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuQueue` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuQueue; # [wasm_bindgen (structural , method , getter , js_class = "GPUQueue" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuQueue) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUQueue" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuQueue, value: &str); # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = copyExternalImageToTexture)] #[doc = "The `copyExternalImageToTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/copyExternalImageToTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`, `GpuCopyExternalImageSourceInfo`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_external_image_to_texture_with_u32_sequence( this: &GpuQueue, source: &GpuCopyExternalImageSourceInfo, destination: &GpuCopyExternalImageDestInfo, copy_size: &::wasm_bindgen::JsValue, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = copyExternalImageToTexture)] #[doc = "The `copyExternalImageToTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/copyExternalImageToTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCopyExternalImageDestInfo`, `GpuCopyExternalImageSourceInfo`, `GpuExtent3dDict`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn copy_external_image_to_texture_with_gpu_extent_3d_dict( this: &GpuQueue, source: &GpuCopyExternalImageSourceInfo, destination: &GpuCopyExternalImageDestInfo, copy_size: &GpuExtent3dDict, ) -> Result<(), JsValue>; # [wasm_bindgen (method , structural , js_class = "GPUQueue" , js_name = onSubmittedWorkDone)] #[doc = "The `onSubmittedWorkDone()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/onSubmittedWorkDone)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn on_submitted_work_done(this: &GpuQueue) -> ::js_sys::Promise; # [wasm_bindgen (method , structural , js_class = "GPUQueue" , js_name = submit)] #[doc = "The `submit()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/submit)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn submit(this: &GpuQueue, command_buffers: &::wasm_bindgen::JsValue); # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_buffer_source( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Object, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_buffer_source( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Object, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_slice( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &[u8], ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_slice( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &[u8], ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_array( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Uint8Array, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_array( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Uint8Array, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_buffer_source_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Object, data_offset: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_buffer_source_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Object, data_offset: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_slice_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &[u8], data_offset: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_slice_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &[u8], data_offset: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_array_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Uint8Array, data_offset: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_array_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Uint8Array, data_offset: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_buffer_source_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Object, data_offset: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_buffer_source_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Object, data_offset: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_slice_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &[u8], data_offset: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_slice_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &[u8], data_offset: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_array_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Uint8Array, data_offset: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_array_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Uint8Array, data_offset: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_buffer_source_and_u32_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Object, data_offset: u32, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_buffer_source_and_u32_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Object, data_offset: u32, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_slice_and_u32_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &[u8], data_offset: u32, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_slice_and_u32_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &[u8], data_offset: u32, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_array_and_u32_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Uint8Array, data_offset: u32, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_array_and_u32_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Uint8Array, data_offset: u32, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_buffer_source_and_f64_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Object, data_offset: f64, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_buffer_source_and_f64_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Object, data_offset: f64, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_slice_and_f64_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &[u8], data_offset: f64, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_slice_and_f64_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &[u8], data_offset: f64, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_array_and_f64_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Uint8Array, data_offset: f64, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_array_and_f64_and_u32( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Uint8Array, data_offset: f64, size: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_buffer_source_and_u32_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Object, data_offset: u32, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_buffer_source_and_u32_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Object, data_offset: u32, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_slice_and_u32_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &[u8], data_offset: u32, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_slice_and_u32_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &[u8], data_offset: u32, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_array_and_u32_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Uint8Array, data_offset: u32, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_array_and_u32_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Uint8Array, data_offset: u32, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_buffer_source_and_f64_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Object, data_offset: f64, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_buffer_source_and_f64_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Object, data_offset: f64, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_slice_and_f64_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &[u8], data_offset: f64, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_slice_and_f64_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &[u8], data_offset: f64, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_u32_and_u8_array_and_f64_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: u32, data: &::js_sys::Uint8Array, data_offset: f64, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeBuffer)] #[doc = "The `writeBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuQueue`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_buffer_with_f64_and_u8_array_and_f64_and_f64( this: &GpuQueue, buffer: &GpuBuffer, buffer_offset: f64, data: &::js_sys::Uint8Array, data_offset: f64, size: f64, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeTexture)] #[doc = "The `writeTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQueue`, `GpuTexelCopyBufferLayout`, `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_texture_with_buffer_source_and_u32_sequence( this: &GpuQueue, destination: &GpuTexelCopyTextureInfo, data: &::js_sys::Object, data_layout: &GpuTexelCopyBufferLayout, size: &::wasm_bindgen::JsValue, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeTexture)] #[doc = "The `writeTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQueue`, `GpuTexelCopyBufferLayout`, `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_texture_with_u8_slice_and_u32_sequence( this: &GpuQueue, destination: &GpuTexelCopyTextureInfo, data: &[u8], data_layout: &GpuTexelCopyBufferLayout, size: &::wasm_bindgen::JsValue, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeTexture)] #[doc = "The `writeTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQueue`, `GpuTexelCopyBufferLayout`, `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_texture_with_u8_array_and_u32_sequence( this: &GpuQueue, destination: &GpuTexelCopyTextureInfo, data: &::js_sys::Uint8Array, data_layout: &GpuTexelCopyBufferLayout, size: &::wasm_bindgen::JsValue, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeTexture)] #[doc = "The `writeTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExtent3dDict`, `GpuQueue`, `GpuTexelCopyBufferLayout`, `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_texture_with_buffer_source_and_gpu_extent_3d_dict( this: &GpuQueue, destination: &GpuTexelCopyTextureInfo, data: &::js_sys::Object, data_layout: &GpuTexelCopyBufferLayout, size: &GpuExtent3dDict, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeTexture)] #[doc = "The `writeTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExtent3dDict`, `GpuQueue`, `GpuTexelCopyBufferLayout`, `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_texture_with_u8_slice_and_gpu_extent_3d_dict( this: &GpuQueue, destination: &GpuTexelCopyTextureInfo, data: &[u8], data_layout: &GpuTexelCopyBufferLayout, size: &GpuExtent3dDict, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPUQueue" , js_name = writeTexture)] #[doc = "The `writeTexture()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUQueue/writeTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuExtent3dDict`, `GpuQueue`, `GpuTexelCopyBufferLayout`, `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn write_texture_with_u8_array_and_gpu_extent_3d_dict( this: &GpuQueue, destination: &GpuTexelCopyTextureInfo, data: &::js_sys::Uint8Array, data_layout: &GpuTexelCopyBufferLayout, size: &GpuExtent3dDict, ) -> Result<(), JsValue>; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuQueueDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUQueueDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuQueueDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQueueDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuQueueDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQueueDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuQueueDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQueueDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuQueueDescriptor, val: &str); } impl GpuQueueDescriptor { #[doc = "Construct a new `GpuQueueDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQueueDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } } impl Default for GpuQueueDescriptor { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuRenderBundle.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPURenderBundle , typescript_type = "GPURenderBundle")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuRenderBundle` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundle)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundle`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuRenderBundle; # [wasm_bindgen (structural , method , getter , js_class = "GPURenderBundle" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundle/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundle`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuRenderBundle) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPURenderBundle" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundle/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundle`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuRenderBundle, value: &str); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuRenderBundleDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPURenderBundleDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuRenderBundleDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuRenderBundleDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuRenderBundleDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuRenderBundleDescriptor, val: &str); } impl GpuRenderBundleDescriptor { #[doc = "Construct a new `GpuRenderBundleDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } } impl Default for GpuRenderBundleDescriptor { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuRenderBundleEncoder.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPURenderBundleEncoder , typescript_type = "GPURenderBundleEncoder")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuRenderBundleEncoder` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuRenderBundleEncoder; # [wasm_bindgen (structural , method , getter , js_class = "GPURenderBundleEncoder" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuRenderBundleEncoder) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPURenderBundleEncoder" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuRenderBundleEncoder, value: &str); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = finish)] #[doc = "The `finish()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/finish)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundle`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn finish(this: &GpuRenderBundleEncoder) -> GpuRenderBundle; # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = finish)] #[doc = "The `finish()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/finish)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundle`, `GpuRenderBundleDescriptor`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn finish_with_descriptor( this: &GpuRenderBundleEncoder, descriptor: &GpuRenderBundleDescriptor, ) -> GpuRenderBundle; # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group( this: &GpuRenderBundleEncoder, index: u32, bind_group: Option<&GpuBindGroup>, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_sequence( this: &GpuRenderBundleEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets: &::wasm_bindgen::JsValue, ); # [wasm_bindgen (catch , method , structural , js_class = "GPURenderBundleEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_slice_and_u32_and_dynamic_offsets_data_length( this: &GpuRenderBundleEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets_data: &[u32], dynamic_offsets_data_start: u32, dynamic_offsets_data_length: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPURenderBundleEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_array_and_u32_and_dynamic_offsets_data_length( this: &GpuRenderBundleEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets_data: &::js_sys::Uint32Array, dynamic_offsets_data_start: u32, dynamic_offsets_data_length: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPURenderBundleEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_slice_and_f64_and_dynamic_offsets_data_length( this: &GpuRenderBundleEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets_data: &[u32], dynamic_offsets_data_start: f64, dynamic_offsets_data_length: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPURenderBundleEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_array_and_f64_and_dynamic_offsets_data_length( this: &GpuRenderBundleEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets_data: &::js_sys::Uint32Array, dynamic_offsets_data_start: f64, dynamic_offsets_data_length: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = insertDebugMarker)] #[doc = "The `insertDebugMarker()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/insertDebugMarker)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn insert_debug_marker(this: &GpuRenderBundleEncoder, marker_label: &str); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = popDebugGroup)] #[doc = "The `popDebugGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/popDebugGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn pop_debug_group(this: &GpuRenderBundleEncoder); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = pushDebugGroup)] #[doc = "The `pushDebugGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/pushDebugGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn push_debug_group(this: &GpuRenderBundleEncoder, group_label: &str); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = draw)] #[doc = "The `draw()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/draw)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw(this: &GpuRenderBundleEncoder, vertex_count: u32); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = draw)] #[doc = "The `draw()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/draw)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_with_instance_count( this: &GpuRenderBundleEncoder, vertex_count: u32, instance_count: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = draw)] #[doc = "The `draw()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/draw)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_with_instance_count_and_first_vertex( this: &GpuRenderBundleEncoder, vertex_count: u32, instance_count: u32, first_vertex: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = draw)] #[doc = "The `draw()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/draw)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_with_instance_count_and_first_vertex_and_first_instance( this: &GpuRenderBundleEncoder, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = drawIndexed)] #[doc = "The `drawIndexed()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/drawIndexed)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed(this: &GpuRenderBundleEncoder, index_count: u32); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = drawIndexed)] #[doc = "The `drawIndexed()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/drawIndexed)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed_with_instance_count( this: &GpuRenderBundleEncoder, index_count: u32, instance_count: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = drawIndexed)] #[doc = "The `drawIndexed()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/drawIndexed)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed_with_instance_count_and_first_index( this: &GpuRenderBundleEncoder, index_count: u32, instance_count: u32, first_index: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = drawIndexed)] #[doc = "The `drawIndexed()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/drawIndexed)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed_with_instance_count_and_first_index_and_base_vertex( this: &GpuRenderBundleEncoder, index_count: u32, instance_count: u32, first_index: u32, base_vertex: i32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = drawIndexed)] #[doc = "The `drawIndexed()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/drawIndexed)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed_with_instance_count_and_first_index_and_base_vertex_and_first_instance( this: &GpuRenderBundleEncoder, index_count: u32, instance_count: u32, first_index: u32, base_vertex: i32, first_instance: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = drawIndexedIndirect)] #[doc = "The `drawIndexedIndirect()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/drawIndexedIndirect)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed_indirect_with_u32( this: &GpuRenderBundleEncoder, indirect_buffer: &GpuBuffer, indirect_offset: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = drawIndexedIndirect)] #[doc = "The `drawIndexedIndirect()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/drawIndexedIndirect)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed_indirect_with_f64( this: &GpuRenderBundleEncoder, indirect_buffer: &GpuBuffer, indirect_offset: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = drawIndirect)] #[doc = "The `drawIndirect()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/drawIndirect)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indirect_with_u32( this: &GpuRenderBundleEncoder, indirect_buffer: &GpuBuffer, indirect_offset: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = drawIndirect)] #[doc = "The `drawIndirect()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/drawIndirect)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indirect_with_f64( this: &GpuRenderBundleEncoder, indirect_buffer: &GpuBuffer, indirect_offset: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer( this: &GpuRenderBundleEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer_with_u32( this: &GpuRenderBundleEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, offset: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer_with_f64( this: &GpuRenderBundleEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, offset: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer_with_u32_and_u32( this: &GpuRenderBundleEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, offset: u32, size: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer_with_f64_and_u32( this: &GpuRenderBundleEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, offset: f64, size: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer_with_u32_and_f64( this: &GpuRenderBundleEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, offset: u32, size: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer_with_f64_and_f64( this: &GpuRenderBundleEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, offset: f64, size: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setPipeline)] #[doc = "The `setPipeline()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setPipeline)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoder`, `GpuRenderPipeline`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_pipeline(this: &GpuRenderBundleEncoder, pipeline: &GpuRenderPipeline); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer(this: &GpuRenderBundleEncoder, slot: u32, buffer: Option<&GpuBuffer>); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer_with_u32( this: &GpuRenderBundleEncoder, slot: u32, buffer: Option<&GpuBuffer>, offset: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer_with_f64( this: &GpuRenderBundleEncoder, slot: u32, buffer: Option<&GpuBuffer>, offset: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer_with_u32_and_u32( this: &GpuRenderBundleEncoder, slot: u32, buffer: Option<&GpuBuffer>, offset: u32, size: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer_with_f64_and_u32( this: &GpuRenderBundleEncoder, slot: u32, buffer: Option<&GpuBuffer>, offset: f64, size: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer_with_u32_and_f64( this: &GpuRenderBundleEncoder, slot: u32, buffer: Option<&GpuBuffer>, offset: u32, size: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderBundleEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderBundleEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderBundleEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer_with_f64_and_f64( this: &GpuRenderBundleEncoder, slot: u32, buffer: Option<&GpuBuffer>, offset: f64, size: f64, ); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuRenderBundleEncoderDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPURenderBundleEncoderDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuRenderBundleEncoderDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuRenderBundleEncoderDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuRenderBundleEncoderDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuRenderBundleEncoderDescriptor, val: &str); #[doc = "Get the `colorFormats` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "colorFormats")] pub fn get_color_formats(this: &GpuRenderBundleEncoderDescriptor) -> ::js_sys::Array; #[doc = "Change the `colorFormats` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "colorFormats")] pub fn set_color_formats( this: &GpuRenderBundleEncoderDescriptor, val: &::wasm_bindgen::JsValue, ); #[doc = "Get the `depthStencilFormat` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthStencilFormat")] pub fn get_depth_stencil_format( this: &GpuRenderBundleEncoderDescriptor, ) -> Option; #[doc = "Change the `depthStencilFormat` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthStencilFormat")] pub fn set_depth_stencil_format(this: &GpuRenderBundleEncoderDescriptor, val: GpuTextureFormat); #[doc = "Get the `sampleCount` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "sampleCount")] pub fn get_sample_count(this: &GpuRenderBundleEncoderDescriptor) -> Option; #[doc = "Change the `sampleCount` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "sampleCount")] pub fn set_sample_count(this: &GpuRenderBundleEncoderDescriptor, val: u32); #[doc = "Get the `depthReadOnly` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthReadOnly")] pub fn get_depth_read_only(this: &GpuRenderBundleEncoderDescriptor) -> Option; #[doc = "Change the `depthReadOnly` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthReadOnly")] pub fn set_depth_read_only(this: &GpuRenderBundleEncoderDescriptor, val: bool); #[doc = "Get the `stencilReadOnly` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "stencilReadOnly")] pub fn get_stencil_read_only(this: &GpuRenderBundleEncoderDescriptor) -> Option; #[doc = "Change the `stencilReadOnly` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "stencilReadOnly")] pub fn set_stencil_read_only(this: &GpuRenderBundleEncoderDescriptor, val: bool); } impl GpuRenderBundleEncoderDescriptor { #[doc = "Construct a new `GpuRenderBundleEncoderDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderBundleEncoderDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(color_formats: &::wasm_bindgen::JsValue) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_color_formats(color_formats); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_color_formats()` instead."] pub fn color_formats(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_color_formats(val); self } #[deprecated = "Use `set_depth_stencil_format()` instead."] pub fn depth_stencil_format(&mut self, val: GpuTextureFormat) -> &mut Self { self.set_depth_stencil_format(val); self } #[deprecated = "Use `set_sample_count()` instead."] pub fn sample_count(&mut self, val: u32) -> &mut Self { self.set_sample_count(val); self } #[deprecated = "Use `set_depth_read_only()` instead."] pub fn depth_read_only(&mut self, val: bool) -> &mut Self { self.set_depth_read_only(val); self } #[deprecated = "Use `set_stencil_read_only()` instead."] pub fn stencil_read_only(&mut self, val: bool) -> &mut Self { self.set_stencil_read_only(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuRenderPassColorAttachment.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPURenderPassColorAttachment)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuRenderPassColorAttachment` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassColorAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuRenderPassColorAttachment; #[doc = "Get the `clearValue` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassColorAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "clearValue")] pub fn get_clear_value(this: &GpuRenderPassColorAttachment) -> ::wasm_bindgen::JsValue; #[doc = "Change the `clearValue` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassColorAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "clearValue")] pub fn set_clear_value(this: &GpuRenderPassColorAttachment, val: &::wasm_bindgen::JsValue); #[doc = "Get the `depthSlice` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassColorAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthSlice")] pub fn get_depth_slice(this: &GpuRenderPassColorAttachment) -> Option; #[doc = "Change the `depthSlice` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassColorAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthSlice")] pub fn set_depth_slice(this: &GpuRenderPassColorAttachment, val: u32); #[doc = "Get the `loadOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuLoadOp`, `GpuRenderPassColorAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "loadOp")] pub fn get_load_op(this: &GpuRenderPassColorAttachment) -> GpuLoadOp; #[doc = "Change the `loadOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuLoadOp`, `GpuRenderPassColorAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "loadOp")] pub fn set_load_op(this: &GpuRenderPassColorAttachment, val: GpuLoadOp); #[doc = "Get the `resolveTarget` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassColorAttachment`, `GpuTextureView`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "resolveTarget")] pub fn get_resolve_target(this: &GpuRenderPassColorAttachment) -> Option; #[doc = "Change the `resolveTarget` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassColorAttachment`, `GpuTextureView`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "resolveTarget")] pub fn set_resolve_target(this: &GpuRenderPassColorAttachment, val: &GpuTextureView); #[doc = "Get the `storeOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassColorAttachment`, `GpuStoreOp`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "storeOp")] pub fn get_store_op(this: &GpuRenderPassColorAttachment) -> GpuStoreOp; #[doc = "Change the `storeOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassColorAttachment`, `GpuStoreOp`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "storeOp")] pub fn set_store_op(this: &GpuRenderPassColorAttachment, val: GpuStoreOp); #[doc = "Get the `view` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassColorAttachment`, `GpuTextureView`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "view")] pub fn get_view(this: &GpuRenderPassColorAttachment) -> GpuTextureView; #[doc = "Change the `view` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassColorAttachment`, `GpuTextureView`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "view")] pub fn set_view(this: &GpuRenderPassColorAttachment, val: &GpuTextureView); } impl GpuRenderPassColorAttachment { #[doc = "Construct a new `GpuRenderPassColorAttachment`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuLoadOp`, `GpuRenderPassColorAttachment`, `GpuStoreOp`, `GpuTextureView`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(load_op: GpuLoadOp, store_op: GpuStoreOp, view: &GpuTextureView) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_load_op(load_op); ret.set_store_op(store_op); ret.set_view(view); ret } #[deprecated = "Use `set_clear_value()` instead."] pub fn clear_value(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_clear_value(val); self } #[deprecated = "Use `set_depth_slice()` instead."] pub fn depth_slice(&mut self, val: u32) -> &mut Self { self.set_depth_slice(val); self } #[deprecated = "Use `set_load_op()` instead."] pub fn load_op(&mut self, val: GpuLoadOp) -> &mut Self { self.set_load_op(val); self } #[deprecated = "Use `set_resolve_target()` instead."] pub fn resolve_target(&mut self, val: &GpuTextureView) -> &mut Self { self.set_resolve_target(val); self } #[deprecated = "Use `set_store_op()` instead."] pub fn store_op(&mut self, val: GpuStoreOp) -> &mut Self { self.set_store_op(val); self } #[deprecated = "Use `set_view()` instead."] pub fn view(&mut self, val: &GpuTextureView) -> &mut Self { self.set_view(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuRenderPassDepthStencilAttachment.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPURenderPassDepthStencilAttachment)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuRenderPassDepthStencilAttachment` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuRenderPassDepthStencilAttachment; #[doc = "Get the `depthClearValue` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthClearValue")] pub fn get_depth_clear_value(this: &GpuRenderPassDepthStencilAttachment) -> Option; #[doc = "Change the `depthClearValue` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthClearValue")] pub fn set_depth_clear_value(this: &GpuRenderPassDepthStencilAttachment, val: f32); #[doc = "Get the `depthLoadOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuLoadOp`, `GpuRenderPassDepthStencilAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthLoadOp")] pub fn get_depth_load_op(this: &GpuRenderPassDepthStencilAttachment) -> Option; #[doc = "Change the `depthLoadOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuLoadOp`, `GpuRenderPassDepthStencilAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthLoadOp")] pub fn set_depth_load_op(this: &GpuRenderPassDepthStencilAttachment, val: GpuLoadOp); #[doc = "Get the `depthReadOnly` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthReadOnly")] pub fn get_depth_read_only(this: &GpuRenderPassDepthStencilAttachment) -> Option; #[doc = "Change the `depthReadOnly` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthReadOnly")] pub fn set_depth_read_only(this: &GpuRenderPassDepthStencilAttachment, val: bool); #[doc = "Get the `depthStoreOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`, `GpuStoreOp`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthStoreOp")] pub fn get_depth_store_op(this: &GpuRenderPassDepthStencilAttachment) -> Option; #[doc = "Change the `depthStoreOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`, `GpuStoreOp`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthStoreOp")] pub fn set_depth_store_op(this: &GpuRenderPassDepthStencilAttachment, val: GpuStoreOp); #[doc = "Get the `stencilClearValue` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "stencilClearValue")] pub fn get_stencil_clear_value(this: &GpuRenderPassDepthStencilAttachment) -> Option; #[doc = "Change the `stencilClearValue` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "stencilClearValue")] pub fn set_stencil_clear_value(this: &GpuRenderPassDepthStencilAttachment, val: u32); #[doc = "Get the `stencilLoadOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuLoadOp`, `GpuRenderPassDepthStencilAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "stencilLoadOp")] pub fn get_stencil_load_op(this: &GpuRenderPassDepthStencilAttachment) -> Option; #[doc = "Change the `stencilLoadOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuLoadOp`, `GpuRenderPassDepthStencilAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "stencilLoadOp")] pub fn set_stencil_load_op(this: &GpuRenderPassDepthStencilAttachment, val: GpuLoadOp); #[doc = "Get the `stencilReadOnly` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "stencilReadOnly")] pub fn get_stencil_read_only(this: &GpuRenderPassDepthStencilAttachment) -> Option; #[doc = "Change the `stencilReadOnly` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "stencilReadOnly")] pub fn set_stencil_read_only(this: &GpuRenderPassDepthStencilAttachment, val: bool); #[doc = "Get the `stencilStoreOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`, `GpuStoreOp`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "stencilStoreOp")] pub fn get_stencil_store_op(this: &GpuRenderPassDepthStencilAttachment) -> Option; #[doc = "Change the `stencilStoreOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`, `GpuStoreOp`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "stencilStoreOp")] pub fn set_stencil_store_op(this: &GpuRenderPassDepthStencilAttachment, val: GpuStoreOp); #[doc = "Get the `view` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`, `GpuTextureView`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "view")] pub fn get_view(this: &GpuRenderPassDepthStencilAttachment) -> GpuTextureView; #[doc = "Change the `view` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`, `GpuTextureView`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "view")] pub fn set_view(this: &GpuRenderPassDepthStencilAttachment, val: &GpuTextureView); } impl GpuRenderPassDepthStencilAttachment { #[doc = "Construct a new `GpuRenderPassDepthStencilAttachment`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`, `GpuTextureView`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(view: &GpuTextureView) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_view(view); ret } #[deprecated = "Use `set_depth_clear_value()` instead."] pub fn depth_clear_value(&mut self, val: f32) -> &mut Self { self.set_depth_clear_value(val); self } #[deprecated = "Use `set_depth_load_op()` instead."] pub fn depth_load_op(&mut self, val: GpuLoadOp) -> &mut Self { self.set_depth_load_op(val); self } #[deprecated = "Use `set_depth_read_only()` instead."] pub fn depth_read_only(&mut self, val: bool) -> &mut Self { self.set_depth_read_only(val); self } #[deprecated = "Use `set_depth_store_op()` instead."] pub fn depth_store_op(&mut self, val: GpuStoreOp) -> &mut Self { self.set_depth_store_op(val); self } #[deprecated = "Use `set_stencil_clear_value()` instead."] pub fn stencil_clear_value(&mut self, val: u32) -> &mut Self { self.set_stencil_clear_value(val); self } #[deprecated = "Use `set_stencil_load_op()` instead."] pub fn stencil_load_op(&mut self, val: GpuLoadOp) -> &mut Self { self.set_stencil_load_op(val); self } #[deprecated = "Use `set_stencil_read_only()` instead."] pub fn stencil_read_only(&mut self, val: bool) -> &mut Self { self.set_stencil_read_only(val); self } #[deprecated = "Use `set_stencil_store_op()` instead."] pub fn stencil_store_op(&mut self, val: GpuStoreOp) -> &mut Self { self.set_stencil_store_op(val); self } #[deprecated = "Use `set_view()` instead."] pub fn view(&mut self, val: &GpuTextureView) -> &mut Self { self.set_view(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuRenderPassDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPURenderPassDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuRenderPassDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuRenderPassDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuRenderPassDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuRenderPassDescriptor, val: &str); #[doc = "Get the `colorAttachments` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "colorAttachments")] pub fn get_color_attachments(this: &GpuRenderPassDescriptor) -> ::js_sys::Array; #[doc = "Change the `colorAttachments` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "colorAttachments")] pub fn set_color_attachments(this: &GpuRenderPassDescriptor, val: &::wasm_bindgen::JsValue); #[doc = "Get the `depthStencilAttachment` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`, `GpuRenderPassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthStencilAttachment")] pub fn get_depth_stencil_attachment( this: &GpuRenderPassDescriptor, ) -> Option; #[doc = "Change the `depthStencilAttachment` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDepthStencilAttachment`, `GpuRenderPassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthStencilAttachment")] pub fn set_depth_stencil_attachment( this: &GpuRenderPassDescriptor, val: &GpuRenderPassDepthStencilAttachment, ); #[doc = "Get the `maxDrawCount` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "maxDrawCount")] pub fn get_max_draw_count(this: &GpuRenderPassDescriptor) -> Option; #[doc = "Change the `maxDrawCount` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "maxDrawCount")] pub fn set_max_draw_count(this: &GpuRenderPassDescriptor, val: f64); #[doc = "Get the `occlusionQuerySet` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySet`, `GpuRenderPassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "occlusionQuerySet")] pub fn get_occlusion_query_set(this: &GpuRenderPassDescriptor) -> Option; #[doc = "Change the `occlusionQuerySet` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySet`, `GpuRenderPassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "occlusionQuerySet")] pub fn set_occlusion_query_set(this: &GpuRenderPassDescriptor, val: &GpuQuerySet); #[doc = "Get the `timestampWrites` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDescriptor`, `GpuRenderPassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "timestampWrites")] pub fn get_timestamp_writes( this: &GpuRenderPassDescriptor, ) -> Option; #[doc = "Change the `timestampWrites` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDescriptor`, `GpuRenderPassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "timestampWrites")] pub fn set_timestamp_writes(this: &GpuRenderPassDescriptor, val: &GpuRenderPassTimestampWrites); } impl GpuRenderPassDescriptor { #[doc = "Construct a new `GpuRenderPassDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(color_attachments: &::wasm_bindgen::JsValue) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_color_attachments(color_attachments); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_color_attachments()` instead."] pub fn color_attachments(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_color_attachments(val); self } #[deprecated = "Use `set_depth_stencil_attachment()` instead."] pub fn depth_stencil_attachment( &mut self, val: &GpuRenderPassDepthStencilAttachment, ) -> &mut Self { self.set_depth_stencil_attachment(val); self } #[deprecated = "Use `set_max_draw_count()` instead."] pub fn max_draw_count(&mut self, val: f64) -> &mut Self { self.set_max_draw_count(val); self } #[deprecated = "Use `set_occlusion_query_set()` instead."] pub fn occlusion_query_set(&mut self, val: &GpuQuerySet) -> &mut Self { self.set_occlusion_query_set(val); self } #[deprecated = "Use `set_timestamp_writes()` instead."] pub fn timestamp_writes(&mut self, val: &GpuRenderPassTimestampWrites) -> &mut Self { self.set_timestamp_writes(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuRenderPassEncoder.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPURenderPassEncoder , typescript_type = "GPURenderPassEncoder")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuRenderPassEncoder` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuRenderPassEncoder; # [wasm_bindgen (structural , method , getter , js_class = "GPURenderPassEncoder" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuRenderPassEncoder) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPURenderPassEncoder" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuRenderPassEncoder, value: &str); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = beginOcclusionQuery)] #[doc = "The `beginOcclusionQuery()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/beginOcclusionQuery)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn begin_occlusion_query(this: &GpuRenderPassEncoder, query_index: u32); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = end)] #[doc = "The `end()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/end)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn end(this: &GpuRenderPassEncoder); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = endOcclusionQuery)] #[doc = "The `endOcclusionQuery()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/endOcclusionQuery)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn end_occlusion_query(this: &GpuRenderPassEncoder); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = executeBundles)] #[doc = "The `executeBundles()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/executeBundles)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn execute_bundles(this: &GpuRenderPassEncoder, bundles: &::wasm_bindgen::JsValue); # [wasm_bindgen (catch , method , structural , js_class = "GPURenderPassEncoder" , js_name = setBlendConstant)] #[doc = "The `setBlendConstant()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setBlendConstant)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_blend_constant_with_f64_sequence( this: &GpuRenderPassEncoder, color: &::wasm_bindgen::JsValue, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPURenderPassEncoder" , js_name = setBlendConstant)] #[doc = "The `setBlendConstant()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setBlendConstant)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuColorDict`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_blend_constant_with_gpu_color_dict( this: &GpuRenderPassEncoder, color: &GpuColorDict, ) -> Result<(), JsValue>; # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setScissorRect)] #[doc = "The `setScissorRect()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setScissorRect)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_scissor_rect(this: &GpuRenderPassEncoder, x: u32, y: u32, width: u32, height: u32); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setStencilReference)] #[doc = "The `setStencilReference()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setStencilReference)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_stencil_reference(this: &GpuRenderPassEncoder, reference: u32); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setViewport)] #[doc = "The `setViewport()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setViewport)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_viewport( this: &GpuRenderPassEncoder, x: f32, y: f32, width: f32, height: f32, min_depth: f32, max_depth: f32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group( this: &GpuRenderPassEncoder, index: u32, bind_group: Option<&GpuBindGroup>, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_sequence( this: &GpuRenderPassEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets: &::wasm_bindgen::JsValue, ); # [wasm_bindgen (catch , method , structural , js_class = "GPURenderPassEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_slice_and_u32_and_dynamic_offsets_data_length( this: &GpuRenderPassEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets_data: &[u32], dynamic_offsets_data_start: u32, dynamic_offsets_data_length: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPURenderPassEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_array_and_u32_and_dynamic_offsets_data_length( this: &GpuRenderPassEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets_data: &::js_sys::Uint32Array, dynamic_offsets_data_start: u32, dynamic_offsets_data_length: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPURenderPassEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_slice_and_f64_and_dynamic_offsets_data_length( this: &GpuRenderPassEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets_data: &[u32], dynamic_offsets_data_start: f64, dynamic_offsets_data_length: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (catch , method , structural , js_class = "GPURenderPassEncoder" , js_name = setBindGroup)] #[doc = "The `setBindGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroup`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_bind_group_with_u32_array_and_f64_and_dynamic_offsets_data_length( this: &GpuRenderPassEncoder, index: u32, bind_group: Option<&GpuBindGroup>, dynamic_offsets_data: &::js_sys::Uint32Array, dynamic_offsets_data_start: f64, dynamic_offsets_data_length: u32, ) -> Result<(), JsValue>; # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = insertDebugMarker)] #[doc = "The `insertDebugMarker()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/insertDebugMarker)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn insert_debug_marker(this: &GpuRenderPassEncoder, marker_label: &str); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = popDebugGroup)] #[doc = "The `popDebugGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/popDebugGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn pop_debug_group(this: &GpuRenderPassEncoder); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = pushDebugGroup)] #[doc = "The `pushDebugGroup()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/pushDebugGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn push_debug_group(this: &GpuRenderPassEncoder, group_label: &str); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = draw)] #[doc = "The `draw()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/draw)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw(this: &GpuRenderPassEncoder, vertex_count: u32); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = draw)] #[doc = "The `draw()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/draw)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_with_instance_count( this: &GpuRenderPassEncoder, vertex_count: u32, instance_count: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = draw)] #[doc = "The `draw()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/draw)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_with_instance_count_and_first_vertex( this: &GpuRenderPassEncoder, vertex_count: u32, instance_count: u32, first_vertex: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = draw)] #[doc = "The `draw()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/draw)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_with_instance_count_and_first_vertex_and_first_instance( this: &GpuRenderPassEncoder, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = drawIndexed)] #[doc = "The `drawIndexed()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/drawIndexed)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed(this: &GpuRenderPassEncoder, index_count: u32); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = drawIndexed)] #[doc = "The `drawIndexed()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/drawIndexed)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed_with_instance_count( this: &GpuRenderPassEncoder, index_count: u32, instance_count: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = drawIndexed)] #[doc = "The `drawIndexed()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/drawIndexed)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed_with_instance_count_and_first_index( this: &GpuRenderPassEncoder, index_count: u32, instance_count: u32, first_index: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = drawIndexed)] #[doc = "The `drawIndexed()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/drawIndexed)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed_with_instance_count_and_first_index_and_base_vertex( this: &GpuRenderPassEncoder, index_count: u32, instance_count: u32, first_index: u32, base_vertex: i32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = drawIndexed)] #[doc = "The `drawIndexed()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/drawIndexed)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed_with_instance_count_and_first_index_and_base_vertex_and_first_instance( this: &GpuRenderPassEncoder, index_count: u32, instance_count: u32, first_index: u32, base_vertex: i32, first_instance: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = drawIndexedIndirect)] #[doc = "The `drawIndexedIndirect()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/drawIndexedIndirect)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed_indirect_with_u32( this: &GpuRenderPassEncoder, indirect_buffer: &GpuBuffer, indirect_offset: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = drawIndexedIndirect)] #[doc = "The `drawIndexedIndirect()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/drawIndexedIndirect)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indexed_indirect_with_f64( this: &GpuRenderPassEncoder, indirect_buffer: &GpuBuffer, indirect_offset: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = drawIndirect)] #[doc = "The `drawIndirect()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/drawIndirect)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indirect_with_u32( this: &GpuRenderPassEncoder, indirect_buffer: &GpuBuffer, indirect_offset: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = drawIndirect)] #[doc = "The `drawIndirect()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/drawIndirect)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn draw_indirect_with_f64( this: &GpuRenderPassEncoder, indirect_buffer: &GpuBuffer, indirect_offset: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer( this: &GpuRenderPassEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer_with_u32( this: &GpuRenderPassEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, offset: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer_with_f64( this: &GpuRenderPassEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, offset: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer_with_u32_and_u32( this: &GpuRenderPassEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, offset: u32, size: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer_with_f64_and_u32( this: &GpuRenderPassEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, offset: f64, size: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer_with_u32_and_f64( this: &GpuRenderPassEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, offset: u32, size: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setIndexBuffer)] #[doc = "The `setIndexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setIndexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuIndexFormat`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_index_buffer_with_f64_and_f64( this: &GpuRenderPassEncoder, buffer: &GpuBuffer, index_format: GpuIndexFormat, offset: f64, size: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setPipeline)] #[doc = "The `setPipeline()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setPipeline)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassEncoder`, `GpuRenderPipeline`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_pipeline(this: &GpuRenderPassEncoder, pipeline: &GpuRenderPipeline); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer(this: &GpuRenderPassEncoder, slot: u32, buffer: Option<&GpuBuffer>); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer_with_u32( this: &GpuRenderPassEncoder, slot: u32, buffer: Option<&GpuBuffer>, offset: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer_with_f64( this: &GpuRenderPassEncoder, slot: u32, buffer: Option<&GpuBuffer>, offset: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer_with_u32_and_u32( this: &GpuRenderPassEncoder, slot: u32, buffer: Option<&GpuBuffer>, offset: u32, size: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer_with_f64_and_u32( this: &GpuRenderPassEncoder, slot: u32, buffer: Option<&GpuBuffer>, offset: f64, size: u32, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer_with_u32_and_f64( this: &GpuRenderPassEncoder, slot: u32, buffer: Option<&GpuBuffer>, offset: u32, size: f64, ); # [wasm_bindgen (method , structural , js_class = "GPURenderPassEncoder" , js_name = setVertexBuffer)] #[doc = "The `setVertexBuffer()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPassEncoder/setVertexBuffer)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuRenderPassEncoder`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_vertex_buffer_with_f64_and_f64( this: &GpuRenderPassEncoder, slot: u32, buffer: Option<&GpuBuffer>, offset: f64, size: f64, ); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuRenderPassTimestampWrites.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPURenderPassTimestampWrites)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuRenderPassTimestampWrites` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuRenderPassTimestampWrites; #[doc = "Get the `beginningOfPassWriteIndex` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "beginningOfPassWriteIndex")] pub fn get_beginning_of_pass_write_index(this: &GpuRenderPassTimestampWrites) -> Option; #[doc = "Change the `beginningOfPassWriteIndex` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "beginningOfPassWriteIndex")] pub fn set_beginning_of_pass_write_index(this: &GpuRenderPassTimestampWrites, val: u32); #[doc = "Get the `endOfPassWriteIndex` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "endOfPassWriteIndex")] pub fn get_end_of_pass_write_index(this: &GpuRenderPassTimestampWrites) -> Option; #[doc = "Change the `endOfPassWriteIndex` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "endOfPassWriteIndex")] pub fn set_end_of_pass_write_index(this: &GpuRenderPassTimestampWrites, val: u32); #[doc = "Get the `querySet` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySet`, `GpuRenderPassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "querySet")] pub fn get_query_set(this: &GpuRenderPassTimestampWrites) -> GpuQuerySet; #[doc = "Change the `querySet` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySet`, `GpuRenderPassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "querySet")] pub fn set_query_set(this: &GpuRenderPassTimestampWrites, val: &GpuQuerySet); } impl GpuRenderPassTimestampWrites { #[doc = "Construct a new `GpuRenderPassTimestampWrites`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuQuerySet`, `GpuRenderPassTimestampWrites`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(query_set: &GpuQuerySet) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_query_set(query_set); ret } #[deprecated = "Use `set_beginning_of_pass_write_index()` instead."] pub fn beginning_of_pass_write_index(&mut self, val: u32) -> &mut Self { self.set_beginning_of_pass_write_index(val); self } #[deprecated = "Use `set_end_of_pass_write_index()` instead."] pub fn end_of_pass_write_index(&mut self, val: u32) -> &mut Self { self.set_end_of_pass_write_index(val); self } #[deprecated = "Use `set_query_set()` instead."] pub fn query_set(&mut self, val: &GpuQuerySet) -> &mut Self { self.set_query_set(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuRenderPipeline.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPURenderPipeline , typescript_type = "GPURenderPipeline")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuRenderPipeline` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPipeline)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPipeline`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuRenderPipeline; # [wasm_bindgen (structural , method , getter , js_class = "GPURenderPipeline" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPipeline/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPipeline`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuRenderPipeline) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPURenderPipeline" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPipeline/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPipeline`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuRenderPipeline, value: &str); # [wasm_bindgen (method , structural , js_class = "GPURenderPipeline" , js_name = getBindGroupLayout)] #[doc = "The `getBindGroupLayout()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPURenderPipeline/getBindGroupLayout)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBindGroupLayout`, `GpuRenderPipeline`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn get_bind_group_layout(this: &GpuRenderPipeline, index: u32) -> GpuBindGroupLayout; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuRenderPipelineDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPURenderPipelineDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuRenderPipelineDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuRenderPipelineDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuRenderPipelineDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuRenderPipelineDescriptor, val: &str); #[doc = "Get the `layout` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "layout")] pub fn get_layout(this: &GpuRenderPipelineDescriptor) -> ::wasm_bindgen::JsValue; #[doc = "Change the `layout` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "layout")] pub fn set_layout(this: &GpuRenderPipelineDescriptor, val: &::wasm_bindgen::JsValue); #[doc = "Get the `depthStencil` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`, `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthStencil")] pub fn get_depth_stencil(this: &GpuRenderPipelineDescriptor) -> Option; #[doc = "Change the `depthStencil` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuDepthStencilState`, `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthStencil")] pub fn set_depth_stencil(this: &GpuRenderPipelineDescriptor, val: &GpuDepthStencilState); #[doc = "Get the `fragment` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFragmentState`, `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "fragment")] pub fn get_fragment(this: &GpuRenderPipelineDescriptor) -> Option; #[doc = "Change the `fragment` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFragmentState`, `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "fragment")] pub fn set_fragment(this: &GpuRenderPipelineDescriptor, val: &GpuFragmentState); #[doc = "Get the `multisample` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuMultisampleState`, `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "multisample")] pub fn get_multisample(this: &GpuRenderPipelineDescriptor) -> Option; #[doc = "Change the `multisample` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuMultisampleState`, `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "multisample")] pub fn set_multisample(this: &GpuRenderPipelineDescriptor, val: &GpuMultisampleState); #[doc = "Get the `primitive` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPrimitiveState`, `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "primitive")] pub fn get_primitive(this: &GpuRenderPipelineDescriptor) -> Option; #[doc = "Change the `primitive` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPrimitiveState`, `GpuRenderPipelineDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "primitive")] pub fn set_primitive(this: &GpuRenderPipelineDescriptor, val: &GpuPrimitiveState); #[doc = "Get the `vertex` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPipelineDescriptor`, `GpuVertexState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "vertex")] pub fn get_vertex(this: &GpuRenderPipelineDescriptor) -> GpuVertexState; #[doc = "Change the `vertex` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPipelineDescriptor`, `GpuVertexState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "vertex")] pub fn set_vertex(this: &GpuRenderPipelineDescriptor, val: &GpuVertexState); } impl GpuRenderPipelineDescriptor { #[doc = "Construct a new `GpuRenderPipelineDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRenderPipelineDescriptor`, `GpuVertexState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(layout: &::wasm_bindgen::JsValue, vertex: &GpuVertexState) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_layout(layout); ret.set_vertex(vertex); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_layout()` instead."] pub fn layout(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_layout(val); self } #[deprecated = "Use `set_depth_stencil()` instead."] pub fn depth_stencil(&mut self, val: &GpuDepthStencilState) -> &mut Self { self.set_depth_stencil(val); self } #[deprecated = "Use `set_fragment()` instead."] pub fn fragment(&mut self, val: &GpuFragmentState) -> &mut Self { self.set_fragment(val); self } #[deprecated = "Use `set_multisample()` instead."] pub fn multisample(&mut self, val: &GpuMultisampleState) -> &mut Self { self.set_multisample(val); self } #[deprecated = "Use `set_primitive()` instead."] pub fn primitive(&mut self, val: &GpuPrimitiveState) -> &mut Self { self.set_primitive(val); self } #[deprecated = "Use `set_vertex()` instead."] pub fn vertex(&mut self, val: &GpuVertexState) -> &mut Self { self.set_vertex(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuRequestAdapterOptions.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPURequestAdapterOptions)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuRequestAdapterOptions` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRequestAdapterOptions`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuRequestAdapterOptions; #[doc = "Get the `featureLevel` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRequestAdapterOptions`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "featureLevel")] pub fn get_feature_level(this: &GpuRequestAdapterOptions) -> Option<::alloc::string::String>; #[doc = "Change the `featureLevel` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRequestAdapterOptions`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "featureLevel")] pub fn set_feature_level(this: &GpuRequestAdapterOptions, val: &str); #[doc = "Get the `forceFallbackAdapter` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRequestAdapterOptions`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "forceFallbackAdapter")] pub fn get_force_fallback_adapter(this: &GpuRequestAdapterOptions) -> Option; #[doc = "Change the `forceFallbackAdapter` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRequestAdapterOptions`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "forceFallbackAdapter")] pub fn set_force_fallback_adapter(this: &GpuRequestAdapterOptions, val: bool); #[doc = "Get the `powerPreference` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPowerPreference`, `GpuRequestAdapterOptions`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "powerPreference")] pub fn get_power_preference(this: &GpuRequestAdapterOptions) -> Option; #[doc = "Change the `powerPreference` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuPowerPreference`, `GpuRequestAdapterOptions`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "powerPreference")] pub fn set_power_preference(this: &GpuRequestAdapterOptions, val: GpuPowerPreference); #[doc = "Get the `xrCompatible` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRequestAdapterOptions`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "xrCompatible")] pub fn get_xr_compatible(this: &GpuRequestAdapterOptions) -> Option; #[doc = "Change the `xrCompatible` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRequestAdapterOptions`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "xrCompatible")] pub fn set_xr_compatible(this: &GpuRequestAdapterOptions, val: bool); } impl GpuRequestAdapterOptions { #[doc = "Construct a new `GpuRequestAdapterOptions`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuRequestAdapterOptions`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_feature_level()` instead."] pub fn feature_level(&mut self, val: &str) -> &mut Self { self.set_feature_level(val); self } #[deprecated = "Use `set_force_fallback_adapter()` instead."] pub fn force_fallback_adapter(&mut self, val: bool) -> &mut Self { self.set_force_fallback_adapter(val); self } #[deprecated = "Use `set_power_preference()` instead."] pub fn power_preference(&mut self, val: GpuPowerPreference) -> &mut Self { self.set_power_preference(val); self } #[deprecated = "Use `set_xr_compatible()` instead."] pub fn xr_compatible(&mut self, val: bool) -> &mut Self { self.set_xr_compatible(val); self } } impl Default for GpuRequestAdapterOptions { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuSampler.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUSampler , typescript_type = "GPUSampler")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuSampler` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSampler)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSampler`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuSampler; # [wasm_bindgen (structural , method , getter , js_class = "GPUSampler" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSampler/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSampler`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuSampler) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUSampler" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSampler/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSampler`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuSampler, value: &str); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuSamplerBindingLayout.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUSamplerBindingLayout)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuSamplerBindingLayout` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuSamplerBindingLayout; #[doc = "Get the `type` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerBindingLayout`, `GpuSamplerBindingType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "type")] pub fn get_type(this: &GpuSamplerBindingLayout) -> Option; #[doc = "Change the `type` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerBindingLayout`, `GpuSamplerBindingType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "type")] pub fn set_type(this: &GpuSamplerBindingLayout, val: GpuSamplerBindingType); } impl GpuSamplerBindingLayout { #[doc = "Construct a new `GpuSamplerBindingLayout`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_type()` instead."] pub fn type_(&mut self, val: GpuSamplerBindingType) -> &mut Self { self.set_type(val); self } } impl Default for GpuSamplerBindingLayout { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuSamplerBindingType.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuSamplerBindingType` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerBindingType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuSamplerBindingType { Filtering = "filtering", NonFiltering = "non-filtering", Comparison = "comparison", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuSamplerDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUSamplerDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuSamplerDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuSamplerDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuSamplerDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuSamplerDescriptor, val: &str); #[doc = "Get the `addressModeU` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAddressMode`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "addressModeU")] pub fn get_address_mode_u(this: &GpuSamplerDescriptor) -> Option; #[doc = "Change the `addressModeU` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAddressMode`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "addressModeU")] pub fn set_address_mode_u(this: &GpuSamplerDescriptor, val: GpuAddressMode); #[doc = "Get the `addressModeV` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAddressMode`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "addressModeV")] pub fn get_address_mode_v(this: &GpuSamplerDescriptor) -> Option; #[doc = "Change the `addressModeV` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAddressMode`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "addressModeV")] pub fn set_address_mode_v(this: &GpuSamplerDescriptor, val: GpuAddressMode); #[doc = "Get the `addressModeW` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAddressMode`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "addressModeW")] pub fn get_address_mode_w(this: &GpuSamplerDescriptor) -> Option; #[doc = "Change the `addressModeW` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuAddressMode`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "addressModeW")] pub fn set_address_mode_w(this: &GpuSamplerDescriptor, val: GpuAddressMode); #[doc = "Get the `compare` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompareFunction`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "compare")] pub fn get_compare(this: &GpuSamplerDescriptor) -> Option; #[doc = "Change the `compare` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompareFunction`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "compare")] pub fn set_compare(this: &GpuSamplerDescriptor, val: GpuCompareFunction); #[doc = "Get the `lodMaxClamp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "lodMaxClamp")] pub fn get_lod_max_clamp(this: &GpuSamplerDescriptor) -> Option; #[doc = "Change the `lodMaxClamp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "lodMaxClamp")] pub fn set_lod_max_clamp(this: &GpuSamplerDescriptor, val: f32); #[doc = "Get the `lodMinClamp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "lodMinClamp")] pub fn get_lod_min_clamp(this: &GpuSamplerDescriptor) -> Option; #[doc = "Change the `lodMinClamp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "lodMinClamp")] pub fn set_lod_min_clamp(this: &GpuSamplerDescriptor, val: f32); #[doc = "Get the `magFilter` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFilterMode`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "magFilter")] pub fn get_mag_filter(this: &GpuSamplerDescriptor) -> Option; #[doc = "Change the `magFilter` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFilterMode`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "magFilter")] pub fn set_mag_filter(this: &GpuSamplerDescriptor, val: GpuFilterMode); #[doc = "Get the `maxAnisotropy` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "maxAnisotropy")] pub fn get_max_anisotropy(this: &GpuSamplerDescriptor) -> Option; #[doc = "Change the `maxAnisotropy` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "maxAnisotropy")] pub fn set_max_anisotropy(this: &GpuSamplerDescriptor, val: u16); #[doc = "Get the `minFilter` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFilterMode`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "minFilter")] pub fn get_min_filter(this: &GpuSamplerDescriptor) -> Option; #[doc = "Change the `minFilter` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuFilterMode`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "minFilter")] pub fn set_min_filter(this: &GpuSamplerDescriptor, val: GpuFilterMode); #[doc = "Get the `mipmapFilter` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuMipmapFilterMode`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "mipmapFilter")] pub fn get_mipmap_filter(this: &GpuSamplerDescriptor) -> Option; #[doc = "Change the `mipmapFilter` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuMipmapFilterMode`, `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "mipmapFilter")] pub fn set_mipmap_filter(this: &GpuSamplerDescriptor, val: GpuMipmapFilterMode); } impl GpuSamplerDescriptor { #[doc = "Construct a new `GpuSamplerDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSamplerDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_address_mode_u()` instead."] pub fn address_mode_u(&mut self, val: GpuAddressMode) -> &mut Self { self.set_address_mode_u(val); self } #[deprecated = "Use `set_address_mode_v()` instead."] pub fn address_mode_v(&mut self, val: GpuAddressMode) -> &mut Self { self.set_address_mode_v(val); self } #[deprecated = "Use `set_address_mode_w()` instead."] pub fn address_mode_w(&mut self, val: GpuAddressMode) -> &mut Self { self.set_address_mode_w(val); self } #[deprecated = "Use `set_compare()` instead."] pub fn compare(&mut self, val: GpuCompareFunction) -> &mut Self { self.set_compare(val); self } #[deprecated = "Use `set_lod_max_clamp()` instead."] pub fn lod_max_clamp(&mut self, val: f32) -> &mut Self { self.set_lod_max_clamp(val); self } #[deprecated = "Use `set_lod_min_clamp()` instead."] pub fn lod_min_clamp(&mut self, val: f32) -> &mut Self { self.set_lod_min_clamp(val); self } #[deprecated = "Use `set_mag_filter()` instead."] pub fn mag_filter(&mut self, val: GpuFilterMode) -> &mut Self { self.set_mag_filter(val); self } #[deprecated = "Use `set_max_anisotropy()` instead."] pub fn max_anisotropy(&mut self, val: u16) -> &mut Self { self.set_max_anisotropy(val); self } #[deprecated = "Use `set_min_filter()` instead."] pub fn min_filter(&mut self, val: GpuFilterMode) -> &mut Self { self.set_min_filter(val); self } #[deprecated = "Use `set_mipmap_filter()` instead."] pub fn mipmap_filter(&mut self, val: GpuMipmapFilterMode) -> &mut Self { self.set_mipmap_filter(val); self } } impl Default for GpuSamplerDescriptor { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuShaderModule.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUShaderModule , typescript_type = "GPUShaderModule")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuShaderModule` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUShaderModule)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModule`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuShaderModule; # [wasm_bindgen (structural , method , getter , js_class = "GPUShaderModule" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUShaderModule/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModule`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuShaderModule) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUShaderModule" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUShaderModule/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModule`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuShaderModule, value: &str); # [wasm_bindgen (method , structural , js_class = "GPUShaderModule" , js_name = getCompilationInfo)] #[doc = "The `getCompilationInfo()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUShaderModule/getCompilationInfo)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModule`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn get_compilation_info(this: &GpuShaderModule) -> ::js_sys::Promise; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuShaderModuleDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUShaderModuleDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuShaderModuleDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModuleDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuShaderModuleDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModuleDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuShaderModuleDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModuleDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuShaderModuleDescriptor, val: &str); #[doc = "Get the `code` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModuleDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "code")] pub fn get_code(this: &GpuShaderModuleDescriptor) -> ::alloc::string::String; #[doc = "Change the `code` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModuleDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "code")] pub fn set_code(this: &GpuShaderModuleDescriptor, val: &str); #[doc = "Get the `compilationHints` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModuleDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "compilationHints")] pub fn get_compilation_hints(this: &GpuShaderModuleDescriptor) -> Option<::js_sys::Array>; #[doc = "Change the `compilationHints` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModuleDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "compilationHints")] pub fn set_compilation_hints(this: &GpuShaderModuleDescriptor, val: &::wasm_bindgen::JsValue); } impl GpuShaderModuleDescriptor { #[doc = "Construct a new `GpuShaderModuleDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModuleDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(code: &str) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_code(code); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_code()` instead."] pub fn code(&mut self, val: &str) -> &mut Self { self.set_code(val); self } #[deprecated = "Use `set_compilation_hints()` instead."] pub fn compilation_hints(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_compilation_hints(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuStencilFaceState.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUStencilFaceState)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuStencilFaceState` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStencilFaceState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuStencilFaceState; #[doc = "Get the `compare` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompareFunction`, `GpuStencilFaceState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "compare")] pub fn get_compare(this: &GpuStencilFaceState) -> Option; #[doc = "Change the `compare` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuCompareFunction`, `GpuStencilFaceState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "compare")] pub fn set_compare(this: &GpuStencilFaceState, val: GpuCompareFunction); #[doc = "Get the `depthFailOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStencilFaceState`, `GpuStencilOperation`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "depthFailOp")] pub fn get_depth_fail_op(this: &GpuStencilFaceState) -> Option; #[doc = "Change the `depthFailOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStencilFaceState`, `GpuStencilOperation`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "depthFailOp")] pub fn set_depth_fail_op(this: &GpuStencilFaceState, val: GpuStencilOperation); #[doc = "Get the `failOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStencilFaceState`, `GpuStencilOperation`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "failOp")] pub fn get_fail_op(this: &GpuStencilFaceState) -> Option; #[doc = "Change the `failOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStencilFaceState`, `GpuStencilOperation`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "failOp")] pub fn set_fail_op(this: &GpuStencilFaceState, val: GpuStencilOperation); #[doc = "Get the `passOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStencilFaceState`, `GpuStencilOperation`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "passOp")] pub fn get_pass_op(this: &GpuStencilFaceState) -> Option; #[doc = "Change the `passOp` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStencilFaceState`, `GpuStencilOperation`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "passOp")] pub fn set_pass_op(this: &GpuStencilFaceState, val: GpuStencilOperation); } impl GpuStencilFaceState { #[doc = "Construct a new `GpuStencilFaceState`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStencilFaceState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_compare()` instead."] pub fn compare(&mut self, val: GpuCompareFunction) -> &mut Self { self.set_compare(val); self } #[deprecated = "Use `set_depth_fail_op()` instead."] pub fn depth_fail_op(&mut self, val: GpuStencilOperation) -> &mut Self { self.set_depth_fail_op(val); self } #[deprecated = "Use `set_fail_op()` instead."] pub fn fail_op(&mut self, val: GpuStencilOperation) -> &mut Self { self.set_fail_op(val); self } #[deprecated = "Use `set_pass_op()` instead."] pub fn pass_op(&mut self, val: GpuStencilOperation) -> &mut Self { self.set_pass_op(val); self } } impl Default for GpuStencilFaceState { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuStencilOperation.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuStencilOperation` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStencilOperation`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuStencilOperation { Keep = "keep", Zero = "zero", Replace = "replace", Invert = "invert", IncrementClamp = "increment-clamp", DecrementClamp = "decrement-clamp", IncrementWrap = "increment-wrap", DecrementWrap = "decrement-wrap", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuStorageTextureAccess.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuStorageTextureAccess` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStorageTextureAccess`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuStorageTextureAccess { WriteOnly = "write-only", ReadOnly = "read-only", ReadWrite = "read-write", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuStorageTextureBindingLayout.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUStorageTextureBindingLayout)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuStorageTextureBindingLayout` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStorageTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuStorageTextureBindingLayout; #[doc = "Get the `access` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStorageTextureAccess`, `GpuStorageTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "access")] pub fn get_access(this: &GpuStorageTextureBindingLayout) -> Option; #[doc = "Change the `access` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStorageTextureAccess`, `GpuStorageTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "access")] pub fn set_access(this: &GpuStorageTextureBindingLayout, val: GpuStorageTextureAccess); #[doc = "Get the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStorageTextureBindingLayout`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "format")] pub fn get_format(this: &GpuStorageTextureBindingLayout) -> GpuTextureFormat; #[doc = "Change the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStorageTextureBindingLayout`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "format")] pub fn set_format(this: &GpuStorageTextureBindingLayout, val: GpuTextureFormat); #[doc = "Get the `viewDimension` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStorageTextureBindingLayout`, `GpuTextureViewDimension`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "viewDimension")] pub fn get_view_dimension( this: &GpuStorageTextureBindingLayout, ) -> Option; #[doc = "Change the `viewDimension` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStorageTextureBindingLayout`, `GpuTextureViewDimension`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "viewDimension")] pub fn set_view_dimension(this: &GpuStorageTextureBindingLayout, val: GpuTextureViewDimension); } impl GpuStorageTextureBindingLayout { #[doc = "Construct a new `GpuStorageTextureBindingLayout`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStorageTextureBindingLayout`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(format: GpuTextureFormat) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_format(format); ret } #[deprecated = "Use `set_access()` instead."] pub fn access(&mut self, val: GpuStorageTextureAccess) -> &mut Self { self.set_access(val); self } #[deprecated = "Use `set_format()` instead."] pub fn format(&mut self, val: GpuTextureFormat) -> &mut Self { self.set_format(val); self } #[deprecated = "Use `set_view_dimension()` instead."] pub fn view_dimension(&mut self, val: GpuTextureViewDimension) -> &mut Self { self.set_view_dimension(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuStoreOp.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuStoreOp` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuStoreOp`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuStoreOp { Store = "store", Discard = "discard", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuSupportedFeatures.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUSupportedFeatures , typescript_type = "GPUSupportedFeatures")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuSupportedFeatures` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedFeatures)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuSupportedFeatures; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedFeatures" , js_name = size)] #[doc = "Getter for the `size` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedFeatures/size)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn size(this: &GpuSupportedFeatures) -> u32; # [wasm_bindgen (method , structural , js_class = "GPUSupportedFeatures" , js_name = entries)] #[doc = "The `entries()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedFeatures/entries)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn entries(this: &GpuSupportedFeatures) -> ::js_sys::Iterator; # [wasm_bindgen (catch , method , structural , js_class = "GPUSupportedFeatures" , js_name = forEach)] #[doc = "The `forEach()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedFeatures/forEach)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn for_each( this: &GpuSupportedFeatures, callback: &::js_sys::Function, ) -> Result<(), JsValue>; # [wasm_bindgen (method , structural , js_class = "GPUSupportedFeatures" , js_name = has)] #[doc = "The `has()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedFeatures/has)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn has(this: &GpuSupportedFeatures, value: &str) -> bool; # [wasm_bindgen (method , structural , js_class = "GPUSupportedFeatures" , js_name = keys)] #[doc = "The `keys()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedFeatures/keys)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn keys(this: &GpuSupportedFeatures) -> ::js_sys::Iterator; # [wasm_bindgen (method , structural , js_class = "GPUSupportedFeatures" , js_name = values)] #[doc = "The `values()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedFeatures/values)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn values(this: &GpuSupportedFeatures) -> ::js_sys::Iterator; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuSupportedLimits.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUSupportedLimits , typescript_type = "GPUSupportedLimits")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuSupportedLimits` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuSupportedLimits; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxTextureDimension1D)] #[doc = "Getter for the `maxTextureDimension1D` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxTextureDimension1D)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_texture_dimension_1d(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxTextureDimension2D)] #[doc = "Getter for the `maxTextureDimension2D` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxTextureDimension2D)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_texture_dimension_2d(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxTextureDimension3D)] #[doc = "Getter for the `maxTextureDimension3D` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxTextureDimension3D)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_texture_dimension_3d(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxTextureArrayLayers)] #[doc = "Getter for the `maxTextureArrayLayers` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxTextureArrayLayers)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_texture_array_layers(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxBindGroups)] #[doc = "Getter for the `maxBindGroups` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxBindGroups)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_bind_groups(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxBindGroupsPlusVertexBuffers)] #[doc = "Getter for the `maxBindGroupsPlusVertexBuffers` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxBindGroupsPlusVertexBuffers)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_bind_groups_plus_vertex_buffers(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxBindingsPerBindGroup)] #[doc = "Getter for the `maxBindingsPerBindGroup` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxBindingsPerBindGroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_bindings_per_bind_group(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxDynamicUniformBuffersPerPipelineLayout)] #[doc = "Getter for the `maxDynamicUniformBuffersPerPipelineLayout` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxDynamicUniformBuffersPerPipelineLayout)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_dynamic_uniform_buffers_per_pipeline_layout(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxDynamicStorageBuffersPerPipelineLayout)] #[doc = "Getter for the `maxDynamicStorageBuffersPerPipelineLayout` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxDynamicStorageBuffersPerPipelineLayout)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_dynamic_storage_buffers_per_pipeline_layout(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxSampledTexturesPerShaderStage)] #[doc = "Getter for the `maxSampledTexturesPerShaderStage` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxSampledTexturesPerShaderStage)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_sampled_textures_per_shader_stage(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxSamplersPerShaderStage)] #[doc = "Getter for the `maxSamplersPerShaderStage` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxSamplersPerShaderStage)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_samplers_per_shader_stage(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxStorageBuffersPerShaderStage)] #[doc = "Getter for the `maxStorageBuffersPerShaderStage` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxStorageBuffersPerShaderStage)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_storage_buffers_per_shader_stage(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxStorageTexturesPerShaderStage)] #[doc = "Getter for the `maxStorageTexturesPerShaderStage` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxStorageTexturesPerShaderStage)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_storage_textures_per_shader_stage(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxUniformBuffersPerShaderStage)] #[doc = "Getter for the `maxUniformBuffersPerShaderStage` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxUniformBuffersPerShaderStage)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_uniform_buffers_per_shader_stage(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxUniformBufferBindingSize)] #[doc = "Getter for the `maxUniformBufferBindingSize` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxUniformBufferBindingSize)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_uniform_buffer_binding_size(this: &GpuSupportedLimits) -> f64; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxStorageBufferBindingSize)] #[doc = "Getter for the `maxStorageBufferBindingSize` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxStorageBufferBindingSize)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_storage_buffer_binding_size(this: &GpuSupportedLimits) -> f64; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = minUniformBufferOffsetAlignment)] #[doc = "Getter for the `minUniformBufferOffsetAlignment` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/minUniformBufferOffsetAlignment)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn min_uniform_buffer_offset_alignment(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = minStorageBufferOffsetAlignment)] #[doc = "Getter for the `minStorageBufferOffsetAlignment` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/minStorageBufferOffsetAlignment)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn min_storage_buffer_offset_alignment(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxVertexBuffers)] #[doc = "Getter for the `maxVertexBuffers` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxVertexBuffers)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_vertex_buffers(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxBufferSize)] #[doc = "Getter for the `maxBufferSize` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxBufferSize)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_buffer_size(this: &GpuSupportedLimits) -> f64; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxVertexAttributes)] #[doc = "Getter for the `maxVertexAttributes` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxVertexAttributes)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_vertex_attributes(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxVertexBufferArrayStride)] #[doc = "Getter for the `maxVertexBufferArrayStride` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxVertexBufferArrayStride)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_vertex_buffer_array_stride(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxInterStageShaderVariables)] #[doc = "Getter for the `maxInterStageShaderVariables` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxInterStageShaderVariables)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_inter_stage_shader_variables(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxColorAttachments)] #[doc = "Getter for the `maxColorAttachments` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxColorAttachments)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_color_attachments(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxColorAttachmentBytesPerSample)] #[doc = "Getter for the `maxColorAttachmentBytesPerSample` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxColorAttachmentBytesPerSample)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_color_attachment_bytes_per_sample(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxComputeWorkgroupStorageSize)] #[doc = "Getter for the `maxComputeWorkgroupStorageSize` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxComputeWorkgroupStorageSize)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_compute_workgroup_storage_size(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxComputeInvocationsPerWorkgroup)] #[doc = "Getter for the `maxComputeInvocationsPerWorkgroup` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxComputeInvocationsPerWorkgroup)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_compute_invocations_per_workgroup(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxComputeWorkgroupSizeX)] #[doc = "Getter for the `maxComputeWorkgroupSizeX` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxComputeWorkgroupSizeX)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_compute_workgroup_size_x(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxComputeWorkgroupSizeY)] #[doc = "Getter for the `maxComputeWorkgroupSizeY` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxComputeWorkgroupSizeY)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_compute_workgroup_size_y(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxComputeWorkgroupSizeZ)] #[doc = "Getter for the `maxComputeWorkgroupSizeZ` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxComputeWorkgroupSizeZ)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_compute_workgroup_size_z(this: &GpuSupportedLimits) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUSupportedLimits" , js_name = maxComputeWorkgroupsPerDimension)] #[doc = "Getter for the `maxComputeWorkgroupsPerDimension` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUSupportedLimits/maxComputeWorkgroupsPerDimension)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuSupportedLimits`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn max_compute_workgroups_per_dimension(this: &GpuSupportedLimits) -> u32; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuTexelCopyBufferInfo.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUTexelCopyBufferInfo)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuTexelCopyBufferInfo` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuTexelCopyBufferInfo; #[doc = "Get the `bytesPerRow` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "bytesPerRow")] pub fn get_bytes_per_row(this: &GpuTexelCopyBufferInfo) -> Option; #[doc = "Change the `bytesPerRow` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "bytesPerRow")] pub fn set_bytes_per_row(this: &GpuTexelCopyBufferInfo, val: u32); #[doc = "Get the `offset` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "offset")] pub fn get_offset(this: &GpuTexelCopyBufferInfo) -> Option; #[doc = "Change the `offset` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "offset")] pub fn set_offset(this: &GpuTexelCopyBufferInfo, val: f64); #[doc = "Get the `rowsPerImage` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "rowsPerImage")] pub fn get_rows_per_image(this: &GpuTexelCopyBufferInfo) -> Option; #[doc = "Change the `rowsPerImage` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "rowsPerImage")] pub fn set_rows_per_image(this: &GpuTexelCopyBufferInfo, val: u32); #[doc = "Get the `buffer` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuTexelCopyBufferInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "buffer")] pub fn get_buffer(this: &GpuTexelCopyBufferInfo) -> GpuBuffer; #[doc = "Change the `buffer` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuTexelCopyBufferInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "buffer")] pub fn set_buffer(this: &GpuTexelCopyBufferInfo, val: &GpuBuffer); } impl GpuTexelCopyBufferInfo { #[doc = "Construct a new `GpuTexelCopyBufferInfo`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuBuffer`, `GpuTexelCopyBufferInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(buffer: &GpuBuffer) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_buffer(buffer); ret } #[deprecated = "Use `set_bytes_per_row()` instead."] pub fn bytes_per_row(&mut self, val: u32) -> &mut Self { self.set_bytes_per_row(val); self } #[deprecated = "Use `set_offset()` instead."] pub fn offset(&mut self, val: f64) -> &mut Self { self.set_offset(val); self } #[deprecated = "Use `set_rows_per_image()` instead."] pub fn rows_per_image(&mut self, val: u32) -> &mut Self { self.set_rows_per_image(val); self } #[deprecated = "Use `set_buffer()` instead."] pub fn buffer(&mut self, val: &GpuBuffer) -> &mut Self { self.set_buffer(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuTexelCopyBufferLayout.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUTexelCopyBufferLayout)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuTexelCopyBufferLayout` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuTexelCopyBufferLayout; #[doc = "Get the `bytesPerRow` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "bytesPerRow")] pub fn get_bytes_per_row(this: &GpuTexelCopyBufferLayout) -> Option; #[doc = "Change the `bytesPerRow` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "bytesPerRow")] pub fn set_bytes_per_row(this: &GpuTexelCopyBufferLayout, val: u32); #[doc = "Get the `offset` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "offset")] pub fn get_offset(this: &GpuTexelCopyBufferLayout) -> Option; #[doc = "Change the `offset` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "offset")] pub fn set_offset(this: &GpuTexelCopyBufferLayout, val: f64); #[doc = "Get the `rowsPerImage` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "rowsPerImage")] pub fn get_rows_per_image(this: &GpuTexelCopyBufferLayout) -> Option; #[doc = "Change the `rowsPerImage` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "rowsPerImage")] pub fn set_rows_per_image(this: &GpuTexelCopyBufferLayout, val: u32); } impl GpuTexelCopyBufferLayout { #[doc = "Construct a new `GpuTexelCopyBufferLayout`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_bytes_per_row()` instead."] pub fn bytes_per_row(&mut self, val: u32) -> &mut Self { self.set_bytes_per_row(val); self } #[deprecated = "Use `set_offset()` instead."] pub fn offset(&mut self, val: f64) -> &mut Self { self.set_offset(val); self } #[deprecated = "Use `set_rows_per_image()` instead."] pub fn rows_per_image(&mut self, val: u32) -> &mut Self { self.set_rows_per_image(val); self } } impl Default for GpuTexelCopyBufferLayout { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuTexelCopyTextureInfo.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUTexelCopyTextureInfo)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuTexelCopyTextureInfo` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuTexelCopyTextureInfo; #[doc = "Get the `aspect` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyTextureInfo`, `GpuTextureAspect`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "aspect")] pub fn get_aspect(this: &GpuTexelCopyTextureInfo) -> Option; #[doc = "Change the `aspect` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyTextureInfo`, `GpuTextureAspect`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "aspect")] pub fn set_aspect(this: &GpuTexelCopyTextureInfo, val: GpuTextureAspect); #[doc = "Get the `mipLevel` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "mipLevel")] pub fn get_mip_level(this: &GpuTexelCopyTextureInfo) -> Option; #[doc = "Change the `mipLevel` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "mipLevel")] pub fn set_mip_level(this: &GpuTexelCopyTextureInfo, val: u32); #[doc = "Get the `origin` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "origin")] pub fn get_origin(this: &GpuTexelCopyTextureInfo) -> ::wasm_bindgen::JsValue; #[doc = "Change the `origin` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyTextureInfo`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "origin")] pub fn set_origin(this: &GpuTexelCopyTextureInfo, val: &::wasm_bindgen::JsValue); #[doc = "Get the `texture` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyTextureInfo`, `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "texture")] pub fn get_texture(this: &GpuTexelCopyTextureInfo) -> GpuTexture; #[doc = "Change the `texture` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyTextureInfo`, `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "texture")] pub fn set_texture(this: &GpuTexelCopyTextureInfo, val: &GpuTexture); } impl GpuTexelCopyTextureInfo { #[doc = "Construct a new `GpuTexelCopyTextureInfo`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexelCopyTextureInfo`, `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(texture: &GpuTexture) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_texture(texture); ret } #[deprecated = "Use `set_aspect()` instead."] pub fn aspect(&mut self, val: GpuTextureAspect) -> &mut Self { self.set_aspect(val); self } #[deprecated = "Use `set_mip_level()` instead."] pub fn mip_level(&mut self, val: u32) -> &mut Self { self.set_mip_level(val); self } #[deprecated = "Use `set_origin()` instead."] pub fn origin(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_origin(val); self } #[deprecated = "Use `set_texture()` instead."] pub fn texture(&mut self, val: &GpuTexture) -> &mut Self { self.set_texture(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuTexture.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUTexture , typescript_type = "GPUTexture")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuTexture` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuTexture; # [wasm_bindgen (structural , method , getter , js_class = "GPUTexture" , js_name = width)] #[doc = "Getter for the `width` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture/width)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn width(this: &GpuTexture) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUTexture" , js_name = height)] #[doc = "Getter for the `height` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture/height)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn height(this: &GpuTexture) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUTexture" , js_name = depthOrArrayLayers)] #[doc = "Getter for the `depthOrArrayLayers` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture/depthOrArrayLayers)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn depth_or_array_layers(this: &GpuTexture) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUTexture" , js_name = mipLevelCount)] #[doc = "Getter for the `mipLevelCount` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture/mipLevelCount)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn mip_level_count(this: &GpuTexture) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUTexture" , js_name = sampleCount)] #[doc = "Getter for the `sampleCount` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture/sampleCount)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn sample_count(this: &GpuTexture) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUTexture" , js_name = dimension)] #[doc = "Getter for the `dimension` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture/dimension)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`, `GpuTextureDimension`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn dimension(this: &GpuTexture) -> GpuTextureDimension; # [wasm_bindgen (structural , method , getter , js_class = "GPUTexture" , js_name = format)] #[doc = "Getter for the `format` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture/format)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn format(this: &GpuTexture) -> GpuTextureFormat; # [wasm_bindgen (structural , method , getter , js_class = "GPUTexture" , js_name = usage)] #[doc = "Getter for the `usage` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture/usage)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn usage(this: &GpuTexture) -> u32; # [wasm_bindgen (structural , method , getter , js_class = "GPUTexture" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuTexture) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUTexture" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuTexture, value: &str); # [wasm_bindgen (catch , method , structural , js_class = "GPUTexture" , js_name = createView)] #[doc = "The `createView()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture/createView)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`, `GpuTextureView`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_view(this: &GpuTexture) -> Result; # [wasm_bindgen (catch , method , structural , js_class = "GPUTexture" , js_name = createView)] #[doc = "The `createView()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture/createView)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`, `GpuTextureView`, `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn create_view_with_descriptor( this: &GpuTexture, descriptor: &GpuTextureViewDescriptor, ) -> Result; # [wasm_bindgen (method , structural , js_class = "GPUTexture" , js_name = destroy)] #[doc = "The `destroy()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTexture/destroy)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTexture`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn destroy(this: &GpuTexture); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuTextureAspect.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuTextureAspect` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureAspect`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuTextureAspect { All = "all", StencilOnly = "stencil-only", DepthOnly = "depth-only", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuTextureBindingLayout.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUTextureBindingLayout)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuTextureBindingLayout` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuTextureBindingLayout; #[doc = "Get the `multisampled` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "multisampled")] pub fn get_multisampled(this: &GpuTextureBindingLayout) -> Option; #[doc = "Change the `multisampled` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "multisampled")] pub fn set_multisampled(this: &GpuTextureBindingLayout, val: bool); #[doc = "Get the `sampleType` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureBindingLayout`, `GpuTextureSampleType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "sampleType")] pub fn get_sample_type(this: &GpuTextureBindingLayout) -> Option; #[doc = "Change the `sampleType` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureBindingLayout`, `GpuTextureSampleType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "sampleType")] pub fn set_sample_type(this: &GpuTextureBindingLayout, val: GpuTextureSampleType); #[doc = "Get the `viewDimension` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureBindingLayout`, `GpuTextureViewDimension`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "viewDimension")] pub fn get_view_dimension(this: &GpuTextureBindingLayout) -> Option; #[doc = "Change the `viewDimension` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureBindingLayout`, `GpuTextureViewDimension`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "viewDimension")] pub fn set_view_dimension(this: &GpuTextureBindingLayout, val: GpuTextureViewDimension); } impl GpuTextureBindingLayout { #[doc = "Construct a new `GpuTextureBindingLayout`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureBindingLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_multisampled()` instead."] pub fn multisampled(&mut self, val: bool) -> &mut Self { self.set_multisampled(val); self } #[deprecated = "Use `set_sample_type()` instead."] pub fn sample_type(&mut self, val: GpuTextureSampleType) -> &mut Self { self.set_sample_type(val); self } #[deprecated = "Use `set_view_dimension()` instead."] pub fn view_dimension(&mut self, val: GpuTextureViewDimension) -> &mut Self { self.set_view_dimension(val); self } } impl Default for GpuTextureBindingLayout { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuTextureDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUTextureDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuTextureDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuTextureDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuTextureDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuTextureDescriptor, val: &str); #[doc = "Get the `dimension` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`, `GpuTextureDimension`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "dimension")] pub fn get_dimension(this: &GpuTextureDescriptor) -> Option; #[doc = "Change the `dimension` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`, `GpuTextureDimension`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "dimension")] pub fn set_dimension(this: &GpuTextureDescriptor, val: GpuTextureDimension); #[doc = "Get the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "format")] pub fn get_format(this: &GpuTextureDescriptor) -> GpuTextureFormat; #[doc = "Change the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "format")] pub fn set_format(this: &GpuTextureDescriptor, val: GpuTextureFormat); #[doc = "Get the `mipLevelCount` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "mipLevelCount")] pub fn get_mip_level_count(this: &GpuTextureDescriptor) -> Option; #[doc = "Change the `mipLevelCount` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "mipLevelCount")] pub fn set_mip_level_count(this: &GpuTextureDescriptor, val: u32); #[doc = "Get the `sampleCount` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "sampleCount")] pub fn get_sample_count(this: &GpuTextureDescriptor) -> Option; #[doc = "Change the `sampleCount` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "sampleCount")] pub fn set_sample_count(this: &GpuTextureDescriptor, val: u32); #[doc = "Get the `size` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "size")] pub fn get_size(this: &GpuTextureDescriptor) -> ::wasm_bindgen::JsValue; #[doc = "Change the `size` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "size")] pub fn set_size(this: &GpuTextureDescriptor, val: &::wasm_bindgen::JsValue); #[doc = "Get the `usage` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "usage")] pub fn get_usage(this: &GpuTextureDescriptor) -> u32; #[doc = "Change the `usage` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "usage")] pub fn set_usage(this: &GpuTextureDescriptor, val: u32); #[doc = "Get the `viewFormats` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "viewFormats")] pub fn get_view_formats(this: &GpuTextureDescriptor) -> Option<::js_sys::Array>; #[doc = "Change the `viewFormats` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "viewFormats")] pub fn set_view_formats(this: &GpuTextureDescriptor, val: &::wasm_bindgen::JsValue); } impl GpuTextureDescriptor { #[doc = "Construct a new `GpuTextureDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDescriptor`, `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(format: GpuTextureFormat, size: &::wasm_bindgen::JsValue, usage: u32) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_format(format); ret.set_size(size); ret.set_usage(usage); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_dimension()` instead."] pub fn dimension(&mut self, val: GpuTextureDimension) -> &mut Self { self.set_dimension(val); self } #[deprecated = "Use `set_format()` instead."] pub fn format(&mut self, val: GpuTextureFormat) -> &mut Self { self.set_format(val); self } #[deprecated = "Use `set_mip_level_count()` instead."] pub fn mip_level_count(&mut self, val: u32) -> &mut Self { self.set_mip_level_count(val); self } #[deprecated = "Use `set_sample_count()` instead."] pub fn sample_count(&mut self, val: u32) -> &mut Self { self.set_sample_count(val); self } #[deprecated = "Use `set_size()` instead."] pub fn size(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_size(val); self } #[deprecated = "Use `set_usage()` instead."] pub fn usage(&mut self, val: u32) -> &mut Self { self.set_usage(val); self } #[deprecated = "Use `set_view_formats()` instead."] pub fn view_formats(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_view_formats(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuTextureDimension.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuTextureDimension` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureDimension`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuTextureDimension { N1d = "1d", N2d = "2d", N3d = "3d", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuTextureFormat.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuTextureFormat` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuTextureFormat { R8unorm = "r8unorm", R8snorm = "r8snorm", R8uint = "r8uint", R8sint = "r8sint", R16uint = "r16uint", R16sint = "r16sint", R16float = "r16float", Rg8unorm = "rg8unorm", Rg8snorm = "rg8snorm", Rg8uint = "rg8uint", Rg8sint = "rg8sint", R32uint = "r32uint", R32sint = "r32sint", R32float = "r32float", Rg16uint = "rg16uint", Rg16sint = "rg16sint", Rg16float = "rg16float", Rgba8unorm = "rgba8unorm", Rgba8unormSrgb = "rgba8unorm-srgb", Rgba8snorm = "rgba8snorm", Rgba8uint = "rgba8uint", Rgba8sint = "rgba8sint", Bgra8unorm = "bgra8unorm", Bgra8unormSrgb = "bgra8unorm-srgb", Rgb9e5ufloat = "rgb9e5ufloat", Rgb10a2uint = "rgb10a2uint", Rgb10a2unorm = "rgb10a2unorm", Rg11b10ufloat = "rg11b10ufloat", Rg32uint = "rg32uint", Rg32sint = "rg32sint", Rg32float = "rg32float", Rgba16uint = "rgba16uint", Rgba16sint = "rgba16sint", Rgba16float = "rgba16float", Rgba32uint = "rgba32uint", Rgba32sint = "rgba32sint", Rgba32float = "rgba32float", Stencil8 = "stencil8", Depth16unorm = "depth16unorm", Depth24plus = "depth24plus", Depth24plusStencil8 = "depth24plus-stencil8", Depth32float = "depth32float", Depth32floatStencil8 = "depth32float-stencil8", Bc1RgbaUnorm = "bc1-rgba-unorm", Bc1RgbaUnormSrgb = "bc1-rgba-unorm-srgb", Bc2RgbaUnorm = "bc2-rgba-unorm", Bc2RgbaUnormSrgb = "bc2-rgba-unorm-srgb", Bc3RgbaUnorm = "bc3-rgba-unorm", Bc3RgbaUnormSrgb = "bc3-rgba-unorm-srgb", Bc4RUnorm = "bc4-r-unorm", Bc4RSnorm = "bc4-r-snorm", Bc5RgUnorm = "bc5-rg-unorm", Bc5RgSnorm = "bc5-rg-snorm", Bc6hRgbUfloat = "bc6h-rgb-ufloat", Bc6hRgbFloat = "bc6h-rgb-float", Bc7RgbaUnorm = "bc7-rgba-unorm", Bc7RgbaUnormSrgb = "bc7-rgba-unorm-srgb", Etc2Rgb8unorm = "etc2-rgb8unorm", Etc2Rgb8unormSrgb = "etc2-rgb8unorm-srgb", Etc2Rgb8a1unorm = "etc2-rgb8a1unorm", Etc2Rgb8a1unormSrgb = "etc2-rgb8a1unorm-srgb", Etc2Rgba8unorm = "etc2-rgba8unorm", Etc2Rgba8unormSrgb = "etc2-rgba8unorm-srgb", EacR11unorm = "eac-r11unorm", EacR11snorm = "eac-r11snorm", EacRg11unorm = "eac-rg11unorm", EacRg11snorm = "eac-rg11snorm", Astc4x4Unorm = "astc-4x4-unorm", Astc4x4UnormSrgb = "astc-4x4-unorm-srgb", Astc5x4Unorm = "astc-5x4-unorm", Astc5x4UnormSrgb = "astc-5x4-unorm-srgb", Astc5x5Unorm = "astc-5x5-unorm", Astc5x5UnormSrgb = "astc-5x5-unorm-srgb", Astc6x5Unorm = "astc-6x5-unorm", Astc6x5UnormSrgb = "astc-6x5-unorm-srgb", Astc6x6Unorm = "astc-6x6-unorm", Astc6x6UnormSrgb = "astc-6x6-unorm-srgb", Astc8x5Unorm = "astc-8x5-unorm", Astc8x5UnormSrgb = "astc-8x5-unorm-srgb", Astc8x6Unorm = "astc-8x6-unorm", Astc8x6UnormSrgb = "astc-8x6-unorm-srgb", Astc8x8Unorm = "astc-8x8-unorm", Astc8x8UnormSrgb = "astc-8x8-unorm-srgb", Astc10x5Unorm = "astc-10x5-unorm", Astc10x5UnormSrgb = "astc-10x5-unorm-srgb", Astc10x6Unorm = "astc-10x6-unorm", Astc10x6UnormSrgb = "astc-10x6-unorm-srgb", Astc10x8Unorm = "astc-10x8-unorm", Astc10x8UnormSrgb = "astc-10x8-unorm-srgb", Astc10x10Unorm = "astc-10x10-unorm", Astc10x10UnormSrgb = "astc-10x10-unorm-srgb", Astc12x10Unorm = "astc-12x10-unorm", Astc12x10UnormSrgb = "astc-12x10-unorm-srgb", Astc12x12Unorm = "astc-12x12-unorm", Astc12x12UnormSrgb = "astc-12x12-unorm-srgb", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuTextureSampleType.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuTextureSampleType` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureSampleType`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuTextureSampleType { Float = "float", UnfilterableFloat = "unfilterable-float", Depth = "depth", Sint = "sint", Uint = "uint", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuTextureView.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUTextureView , typescript_type = "GPUTextureView")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuTextureView` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTextureView)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureView`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuTextureView; # [wasm_bindgen (structural , method , getter , js_class = "GPUTextureView" , js_name = label)] #[doc = "Getter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTextureView/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureView`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn label(this: &GpuTextureView) -> ::alloc::string::String; # [wasm_bindgen (structural , method , setter , js_class = "GPUTextureView" , js_name = label)] #[doc = "Setter for the `label` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUTextureView/label)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureView`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn set_label(this: &GpuTextureView, value: &str); } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuTextureViewDescriptor.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUTextureViewDescriptor)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuTextureViewDescriptor` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuTextureViewDescriptor; #[doc = "Get the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "label")] pub fn get_label(this: &GpuTextureViewDescriptor) -> Option<::alloc::string::String>; #[doc = "Change the `label` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "label")] pub fn set_label(this: &GpuTextureViewDescriptor, val: &str); #[doc = "Get the `arrayLayerCount` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "arrayLayerCount")] pub fn get_array_layer_count(this: &GpuTextureViewDescriptor) -> Option; #[doc = "Change the `arrayLayerCount` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "arrayLayerCount")] pub fn set_array_layer_count(this: &GpuTextureViewDescriptor, val: u32); #[doc = "Get the `aspect` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureAspect`, `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "aspect")] pub fn get_aspect(this: &GpuTextureViewDescriptor) -> Option; #[doc = "Change the `aspect` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureAspect`, `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "aspect")] pub fn set_aspect(this: &GpuTextureViewDescriptor, val: GpuTextureAspect); #[doc = "Get the `baseArrayLayer` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "baseArrayLayer")] pub fn get_base_array_layer(this: &GpuTextureViewDescriptor) -> Option; #[doc = "Change the `baseArrayLayer` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "baseArrayLayer")] pub fn set_base_array_layer(this: &GpuTextureViewDescriptor, val: u32); #[doc = "Get the `baseMipLevel` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "baseMipLevel")] pub fn get_base_mip_level(this: &GpuTextureViewDescriptor) -> Option; #[doc = "Change the `baseMipLevel` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "baseMipLevel")] pub fn set_base_mip_level(this: &GpuTextureViewDescriptor, val: u32); #[doc = "Get the `dimension` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`, `GpuTextureViewDimension`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "dimension")] pub fn get_dimension(this: &GpuTextureViewDescriptor) -> Option; #[doc = "Change the `dimension` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`, `GpuTextureViewDimension`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "dimension")] pub fn set_dimension(this: &GpuTextureViewDescriptor, val: GpuTextureViewDimension); #[doc = "Get the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureFormat`, `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "format")] pub fn get_format(this: &GpuTextureViewDescriptor) -> Option; #[doc = "Change the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureFormat`, `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "format")] pub fn set_format(this: &GpuTextureViewDescriptor, val: GpuTextureFormat); #[doc = "Get the `mipLevelCount` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "mipLevelCount")] pub fn get_mip_level_count(this: &GpuTextureViewDescriptor) -> Option; #[doc = "Change the `mipLevelCount` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "mipLevelCount")] pub fn set_mip_level_count(this: &GpuTextureViewDescriptor, val: u32); #[doc = "Get the `usage` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "usage")] pub fn get_usage(this: &GpuTextureViewDescriptor) -> Option; #[doc = "Change the `usage` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "usage")] pub fn set_usage(this: &GpuTextureViewDescriptor, val: u32); } impl GpuTextureViewDescriptor { #[doc = "Construct a new `GpuTextureViewDescriptor`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDescriptor`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new() -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret } #[deprecated = "Use `set_label()` instead."] pub fn label(&mut self, val: &str) -> &mut Self { self.set_label(val); self } #[deprecated = "Use `set_array_layer_count()` instead."] pub fn array_layer_count(&mut self, val: u32) -> &mut Self { self.set_array_layer_count(val); self } #[deprecated = "Use `set_aspect()` instead."] pub fn aspect(&mut self, val: GpuTextureAspect) -> &mut Self { self.set_aspect(val); self } #[deprecated = "Use `set_base_array_layer()` instead."] pub fn base_array_layer(&mut self, val: u32) -> &mut Self { self.set_base_array_layer(val); self } #[deprecated = "Use `set_base_mip_level()` instead."] pub fn base_mip_level(&mut self, val: u32) -> &mut Self { self.set_base_mip_level(val); self } #[deprecated = "Use `set_dimension()` instead."] pub fn dimension(&mut self, val: GpuTextureViewDimension) -> &mut Self { self.set_dimension(val); self } #[deprecated = "Use `set_format()` instead."] pub fn format(&mut self, val: GpuTextureFormat) -> &mut Self { self.set_format(val); self } #[deprecated = "Use `set_mip_level_count()` instead."] pub fn mip_level_count(&mut self, val: u32) -> &mut Self { self.set_mip_level_count(val); self } #[deprecated = "Use `set_usage()` instead."] pub fn usage(&mut self, val: u32) -> &mut Self { self.set_usage(val); self } } impl Default for GpuTextureViewDescriptor { fn default() -> Self { Self::new() } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuTextureViewDimension.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuTextureViewDimension` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuTextureViewDimension`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuTextureViewDimension { N1d = "1d", N2d = "2d", N2dArray = "2d-array", Cube = "cube", CubeArray = "cube-array", N3d = "3d", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuUncapturedErrorEvent.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = Event , extends = :: js_sys :: Object , js_name = GPUUncapturedErrorEvent , typescript_type = "GPUUncapturedErrorEvent")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuUncapturedErrorEvent` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUUncapturedErrorEvent)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuUncapturedErrorEvent`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuUncapturedErrorEvent; # [wasm_bindgen (structural , method , getter , js_class = "GPUUncapturedErrorEvent" , js_name = error)] #[doc = "Getter for the `error` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUUncapturedErrorEvent/error)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuError`, `GpuUncapturedErrorEvent`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn error(this: &GpuUncapturedErrorEvent) -> GpuError; #[wasm_bindgen(catch, constructor, js_class = "GPUUncapturedErrorEvent")] #[doc = "The `new GpuUncapturedErrorEvent(..)` constructor, creating a new instance of `GpuUncapturedErrorEvent`."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUUncapturedErrorEvent/GPUUncapturedErrorEvent)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuUncapturedErrorEvent`, `GpuUncapturedErrorEventInit`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new( type_: &str, gpu_uncaptured_error_event_init_dict: &GpuUncapturedErrorEventInit, ) -> Result; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuUncapturedErrorEventInit.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUUncapturedErrorEventInit)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuUncapturedErrorEventInit` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuUncapturedErrorEventInit`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuUncapturedErrorEventInit; #[doc = "Get the `bubbles` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuUncapturedErrorEventInit`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "bubbles")] pub fn get_bubbles(this: &GpuUncapturedErrorEventInit) -> Option; #[doc = "Change the `bubbles` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuUncapturedErrorEventInit`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "bubbles")] pub fn set_bubbles(this: &GpuUncapturedErrorEventInit, val: bool); #[doc = "Get the `cancelable` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuUncapturedErrorEventInit`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "cancelable")] pub fn get_cancelable(this: &GpuUncapturedErrorEventInit) -> Option; #[doc = "Change the `cancelable` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuUncapturedErrorEventInit`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "cancelable")] pub fn set_cancelable(this: &GpuUncapturedErrorEventInit, val: bool); #[doc = "Get the `composed` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuUncapturedErrorEventInit`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "composed")] pub fn get_composed(this: &GpuUncapturedErrorEventInit) -> Option; #[doc = "Change the `composed` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuUncapturedErrorEventInit`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "composed")] pub fn set_composed(this: &GpuUncapturedErrorEventInit, val: bool); #[doc = "Get the `error` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuError`, `GpuUncapturedErrorEventInit`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "error")] pub fn get_error(this: &GpuUncapturedErrorEventInit) -> GpuError; #[doc = "Change the `error` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuError`, `GpuUncapturedErrorEventInit`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "error")] pub fn set_error(this: &GpuUncapturedErrorEventInit, val: &GpuError); } impl GpuUncapturedErrorEventInit { #[doc = "Construct a new `GpuUncapturedErrorEventInit`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuError`, `GpuUncapturedErrorEventInit`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(error: &GpuError) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_error(error); ret } #[deprecated = "Use `set_bubbles()` instead."] pub fn bubbles(&mut self, val: bool) -> &mut Self { self.set_bubbles(val); self } #[deprecated = "Use `set_cancelable()` instead."] pub fn cancelable(&mut self, val: bool) -> &mut Self { self.set_cancelable(val); self } #[deprecated = "Use `set_composed()` instead."] pub fn composed(&mut self, val: bool) -> &mut Self { self.set_composed(val); self } #[deprecated = "Use `set_error()` instead."] pub fn error(&mut self, val: &GpuError) -> &mut Self { self.set_error(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuValidationError.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = GpuError , extends = :: js_sys :: Object , js_name = GPUValidationError , typescript_type = "GPUValidationError")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuValidationError` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUValidationError)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuValidationError`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuValidationError; #[wasm_bindgen(catch, constructor, js_class = "GPUValidationError")] #[doc = "The `new GpuValidationError(..)` constructor, creating a new instance of `GpuValidationError`."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/GPUValidationError/GPUValidationError)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuValidationError`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(message: &str) -> Result; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuVertexAttribute.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUVertexAttribute)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuVertexAttribute` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexAttribute`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuVertexAttribute; #[doc = "Get the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexAttribute`, `GpuVertexFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "format")] pub fn get_format(this: &GpuVertexAttribute) -> GpuVertexFormat; #[doc = "Change the `format` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexAttribute`, `GpuVertexFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "format")] pub fn set_format(this: &GpuVertexAttribute, val: GpuVertexFormat); #[doc = "Get the `offset` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexAttribute`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "offset")] pub fn get_offset(this: &GpuVertexAttribute) -> f64; #[doc = "Change the `offset` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexAttribute`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "offset")] pub fn set_offset(this: &GpuVertexAttribute, val: f64); #[doc = "Get the `shaderLocation` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexAttribute`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "shaderLocation")] pub fn get_shader_location(this: &GpuVertexAttribute) -> u32; #[doc = "Change the `shaderLocation` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexAttribute`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "shaderLocation")] pub fn set_shader_location(this: &GpuVertexAttribute, val: u32); } impl GpuVertexAttribute { #[doc = "Construct a new `GpuVertexAttribute`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexAttribute`, `GpuVertexFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(format: GpuVertexFormat, offset: f64, shader_location: u32) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_format(format); ret.set_offset(offset); ret.set_shader_location(shader_location); ret } #[deprecated = "Use `set_format()` instead."] pub fn format(&mut self, val: GpuVertexFormat) -> &mut Self { self.set_format(val); self } #[deprecated = "Use `set_offset()` instead."] pub fn offset(&mut self, val: f64) -> &mut Self { self.set_offset(val); self } #[deprecated = "Use `set_shader_location()` instead."] pub fn shader_location(&mut self, val: u32) -> &mut Self { self.set_shader_location(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuVertexBufferLayout.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUVertexBufferLayout)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuVertexBufferLayout` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuVertexBufferLayout; #[doc = "Get the `arrayStride` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "arrayStride")] pub fn get_array_stride(this: &GpuVertexBufferLayout) -> f64; #[doc = "Change the `arrayStride` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "arrayStride")] pub fn set_array_stride(this: &GpuVertexBufferLayout, val: f64); #[doc = "Get the `attributes` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "attributes")] pub fn get_attributes(this: &GpuVertexBufferLayout) -> ::js_sys::Array; #[doc = "Change the `attributes` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "attributes")] pub fn set_attributes(this: &GpuVertexBufferLayout, val: &::wasm_bindgen::JsValue); #[doc = "Get the `stepMode` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexBufferLayout`, `GpuVertexStepMode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "stepMode")] pub fn get_step_mode(this: &GpuVertexBufferLayout) -> Option; #[doc = "Change the `stepMode` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexBufferLayout`, `GpuVertexStepMode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "stepMode")] pub fn set_step_mode(this: &GpuVertexBufferLayout, val: GpuVertexStepMode); } impl GpuVertexBufferLayout { #[doc = "Construct a new `GpuVertexBufferLayout`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexBufferLayout`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(array_stride: f64, attributes: &::wasm_bindgen::JsValue) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_array_stride(array_stride); ret.set_attributes(attributes); ret } #[deprecated = "Use `set_array_stride()` instead."] pub fn array_stride(&mut self, val: f64) -> &mut Self { self.set_array_stride(val); self } #[deprecated = "Use `set_attributes()` instead."] pub fn attributes(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_attributes(val); self } #[deprecated = "Use `set_step_mode()` instead."] pub fn step_mode(&mut self, val: GpuVertexStepMode) -> &mut Self { self.set_step_mode(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuVertexFormat.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuVertexFormat` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexFormat`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuVertexFormat { Uint8 = "uint8", Uint8x2 = "uint8x2", Uint8x4 = "uint8x4", Sint8 = "sint8", Sint8x2 = "sint8x2", Sint8x4 = "sint8x4", Unorm8 = "unorm8", Unorm8x2 = "unorm8x2", Unorm8x4 = "unorm8x4", Snorm8 = "snorm8", Snorm8x2 = "snorm8x2", Snorm8x4 = "snorm8x4", Uint16 = "uint16", Uint16x2 = "uint16x2", Uint16x4 = "uint16x4", Sint16 = "sint16", Sint16x2 = "sint16x2", Sint16x4 = "sint16x4", Unorm16 = "unorm16", Unorm16x2 = "unorm16x2", Unorm16x4 = "unorm16x4", Snorm16 = "snorm16", Snorm16x2 = "snorm16x2", Snorm16x4 = "snorm16x4", Float16 = "float16", Float16x2 = "float16x2", Float16x4 = "float16x4", Float32 = "float32", Float32x2 = "float32x2", Float32x3 = "float32x3", Float32x4 = "float32x4", Uint32 = "uint32", Uint32x2 = "uint32x2", Uint32x3 = "uint32x3", Uint32x4 = "uint32x4", Sint32 = "sint32", Sint32x2 = "sint32x2", Sint32x3 = "sint32x3", Sint32x4 = "sint32x4", Unorm1010102 = "unorm10-10-10-2", Unorm8x4Bgra = "unorm8x4-bgra", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuVertexState.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = GPUVertexState)] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `GpuVertexState` dictionary."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type GpuVertexState; #[doc = "Get the `constants` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "constants")] pub fn get_constants(this: &GpuVertexState) -> Option<::js_sys::Object>; #[doc = "Change the `constants` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "constants")] pub fn set_constants(this: &GpuVertexState, val: &::js_sys::Object); #[doc = "Get the `entryPoint` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "entryPoint")] pub fn get_entry_point(this: &GpuVertexState) -> Option<::alloc::string::String>; #[doc = "Change the `entryPoint` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "entryPoint")] pub fn set_entry_point(this: &GpuVertexState, val: &str); #[doc = "Get the `module` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModule`, `GpuVertexState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "module")] pub fn get_module(this: &GpuVertexState) -> GpuShaderModule; #[doc = "Change the `module` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModule`, `GpuVertexState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "module")] pub fn set_module(this: &GpuVertexState, val: &GpuShaderModule); #[doc = "Get the `buffers` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, getter = "buffers")] pub fn get_buffers(this: &GpuVertexState) -> Option<::js_sys::Array>; #[doc = "Change the `buffers` field of this object."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[wasm_bindgen(method, setter = "buffers")] pub fn set_buffers(this: &GpuVertexState, val: &::wasm_bindgen::JsValue); } impl GpuVertexState { #[doc = "Construct a new `GpuVertexState`."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuShaderModule`, `GpuVertexState`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn new(module: &GpuShaderModule) -> Self { #[allow(unused_mut)] let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(::js_sys::Object::new()); ret.set_module(module); ret } #[deprecated = "Use `set_constants()` instead."] pub fn constants(&mut self, val: &::js_sys::Object) -> &mut Self { self.set_constants(val); self } #[deprecated = "Use `set_entry_point()` instead."] pub fn entry_point(&mut self, val: &str) -> &mut Self { self.set_entry_point(val); self } #[deprecated = "Use `set_module()` instead."] pub fn module(&mut self, val: &GpuShaderModule) -> &mut Self { self.set_module(val); self } #[deprecated = "Use `set_buffers()` instead."] pub fn buffers(&mut self, val: &::wasm_bindgen::JsValue) -> &mut Self { self.set_buffers(val); self } } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_GpuVertexStepMode.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use wasm_bindgen::prelude::*; #[wasm_bindgen] #[doc = "The `GpuVertexStepMode` enum."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `GpuVertexStepMode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GpuVertexStepMode { Vertex = "vertex", Instance = "instance", } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_WgslLanguageFeatures.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports)] #![allow(clippy::all)] use super::*; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { # [wasm_bindgen (extends = :: js_sys :: Object , js_name = WGSLLanguageFeatures , typescript_type = "WGSLLanguageFeatures")] #[derive(Debug, Clone, PartialEq, Eq)] #[doc = "The `WgslLanguageFeatures` class."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WGSLLanguageFeatures)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `WgslLanguageFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub type WgslLanguageFeatures; # [wasm_bindgen (structural , method , getter , js_class = "WGSLLanguageFeatures" , js_name = size)] #[doc = "Getter for the `size` field of this object."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WGSLLanguageFeatures/size)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `WgslLanguageFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn size(this: &WgslLanguageFeatures) -> u32; # [wasm_bindgen (method , structural , js_class = "WGSLLanguageFeatures" , js_name = entries)] #[doc = "The `entries()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WGSLLanguageFeatures/entries)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `WgslLanguageFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn entries(this: &WgslLanguageFeatures) -> ::js_sys::Iterator; # [wasm_bindgen (catch , method , structural , js_class = "WGSLLanguageFeatures" , js_name = forEach)] #[doc = "The `forEach()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WGSLLanguageFeatures/forEach)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `WgslLanguageFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn for_each( this: &WgslLanguageFeatures, callback: &::js_sys::Function, ) -> Result<(), JsValue>; # [wasm_bindgen (method , structural , js_class = "WGSLLanguageFeatures" , js_name = has)] #[doc = "The `has()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WGSLLanguageFeatures/has)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `WgslLanguageFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn has(this: &WgslLanguageFeatures, value: &str) -> bool; # [wasm_bindgen (method , structural , js_class = "WGSLLanguageFeatures" , js_name = keys)] #[doc = "The `keys()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WGSLLanguageFeatures/keys)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `WgslLanguageFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn keys(this: &WgslLanguageFeatures) -> ::js_sys::Iterator; # [wasm_bindgen (method , structural , js_class = "WGSLLanguageFeatures" , js_name = values)] #[doc = "The `values()` method."] #[doc = ""] #[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WGSLLanguageFeatures/values)"] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `WgslLanguageFeatures`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub fn values(this: &WgslLanguageFeatures) -> ::js_sys::Iterator; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/gen_gpu_map_mode.rs ================================================ // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub mod gpu_map_mode { #![allow(unused_imports)] #![allow(clippy::all)] use super::super::*; use wasm_bindgen::prelude::*; #[doc = "The `GPUMapMode.READ` const."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `gpu_map_mode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub const READ: u32 = 1u64 as u32; #[doc = "The `GPUMapMode.WRITE` const."] #[doc = ""] #[doc = "*This API requires the following crate features to be activated: `gpu_map_mode`*"] #[doc = ""] #[doc = "*This API is unstable and requires `--cfg=web_sys_unstable_apis` to be activated, as"] #[doc = "[described in the `wasm-bindgen` guide](https://wasm-bindgen.github.io/wasm-bindgen/web-sys/unstable-apis.html)*"] pub const WRITE: u32 = 2u64 as u32; } ================================================ FILE: wgpu/src/backend/webgpu/webgpu_sys/mod.rs ================================================ //! Bindings to the WebGPU API. //! //! Internally vendored from the `web-sys` crate until the WebGPU binding are stabilized. // DO NOT EDIT THIS FILE! // // This module part of a subset of web-sys that is used by wgpu's webgpu backend. // // These bindings are vendored into wgpu for the sole purpose of letting // us pin the WebGPU backend to a specific version of the bindings, not // to enable local changes. There are no provisions to preserve changes // you make here the next time we re-vendor the bindings. // // The `web-sys` crate does not treat breaking changes to the WebGPU API // as semver breaking changes, as WebGPU is "unstable". This means Cargo // will not let us mix versions of `web-sys`, pinning WebGPU bindings to // a specific version, while letting other bindings like WebGL get // updated. Vendoring WebGPU was the workaround we chose. // // Vendoring also allows us to avoid building `web-sys` with // `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings. // // If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository. // // This file was generated by the `cargo xtask vendor-web-sys --version f7e0467c4b4d88303eb79fba485320cfb4dfdd58` command. #![allow(unused_imports, non_snake_case)] use web_sys::{Event, EventTarget}; mod gen_Gpu; pub use gen_Gpu::*; mod gen_GpuAdapter; pub use gen_GpuAdapter::*; mod gen_GpuAdapterInfo; pub use gen_GpuAdapterInfo::*; mod gen_GpuAddressMode; pub use gen_GpuAddressMode::*; mod gen_GpuAutoLayoutMode; pub use gen_GpuAutoLayoutMode::*; mod gen_GpuBindGroup; pub use gen_GpuBindGroup::*; mod gen_GpuBindGroupDescriptor; pub use gen_GpuBindGroupDescriptor::*; mod gen_GpuBindGroupEntry; pub use gen_GpuBindGroupEntry::*; mod gen_GpuBindGroupLayout; pub use gen_GpuBindGroupLayout::*; mod gen_GpuBindGroupLayoutDescriptor; pub use gen_GpuBindGroupLayoutDescriptor::*; mod gen_GpuBindGroupLayoutEntry; pub use gen_GpuBindGroupLayoutEntry::*; mod gen_GpuBlendComponent; pub use gen_GpuBlendComponent::*; mod gen_GpuBlendFactor; pub use gen_GpuBlendFactor::*; mod gen_GpuBlendOperation; pub use gen_GpuBlendOperation::*; mod gen_GpuBlendState; pub use gen_GpuBlendState::*; mod gen_GpuBuffer; pub use gen_GpuBuffer::*; mod gen_GpuBufferBinding; pub use gen_GpuBufferBinding::*; mod gen_GpuBufferBindingLayout; pub use gen_GpuBufferBindingLayout::*; mod gen_GpuBufferBindingType; pub use gen_GpuBufferBindingType::*; mod gen_GpuBufferDescriptor; pub use gen_GpuBufferDescriptor::*; mod gen_GpuBufferMapState; pub use gen_GpuBufferMapState::*; mod gen_GpuCanvasAlphaMode; pub use gen_GpuCanvasAlphaMode::*; mod gen_GpuCanvasContext; pub use gen_GpuCanvasContext::*; mod gen_GpuCanvasConfiguration; pub use gen_GpuCanvasConfiguration::*; mod gen_GpuCanvasToneMapping; pub use gen_GpuCanvasToneMapping::*; mod gen_GpuCanvasToneMappingMode; pub use gen_GpuCanvasToneMappingMode::*; mod gen_GpuColorDict; pub use gen_GpuColorDict::*; mod gen_GpuColorTargetState; pub use gen_GpuColorTargetState::*; mod gen_GpuCommandBuffer; pub use gen_GpuCommandBuffer::*; mod gen_GpuCommandBufferDescriptor; pub use gen_GpuCommandBufferDescriptor::*; mod gen_GpuCommandEncoder; pub use gen_GpuCommandEncoder::*; mod gen_GpuCommandEncoderDescriptor; pub use gen_GpuCommandEncoderDescriptor::*; mod gen_GpuCompareFunction; pub use gen_GpuCompareFunction::*; mod gen_GpuCompilationInfo; pub use gen_GpuCompilationInfo::*; mod gen_GpuCompilationMessage; pub use gen_GpuCompilationMessage::*; mod gen_GpuCompilationMessageType; pub use gen_GpuCompilationMessageType::*; mod gen_GpuComputePassDescriptor; pub use gen_GpuComputePassDescriptor::*; mod gen_GpuComputePassEncoder; pub use gen_GpuComputePassEncoder::*; mod gen_GpuComputePassTimestampWrites; pub use gen_GpuComputePassTimestampWrites::*; mod gen_GpuComputePipeline; pub use gen_GpuComputePipeline::*; mod gen_GpuComputePipelineDescriptor; pub use gen_GpuComputePipelineDescriptor::*; mod gen_GpuCullMode; pub use gen_GpuCullMode::*; mod gen_GpuDepthStencilState; pub use gen_GpuDepthStencilState::*; mod gen_GpuDevice; pub use gen_GpuDevice::*; mod gen_GpuDeviceDescriptor; pub use gen_GpuDeviceDescriptor::*; mod gen_GpuDeviceLostInfo; pub use gen_GpuDeviceLostInfo::*; mod gen_GpuDeviceLostReason; pub use gen_GpuDeviceLostReason::*; mod gen_GpuError; pub use gen_GpuError::*; mod gen_GpuErrorFilter; pub use gen_GpuErrorFilter::*; mod gen_GpuExternalTexture; pub use gen_GpuExternalTexture::*; mod gen_GpuExternalTextureBindingLayout; pub use gen_GpuExternalTextureBindingLayout::*; mod gen_GpuExternalTextureDescriptor; pub use gen_GpuExternalTextureDescriptor::*; mod gen_GpuExtent3dDict; pub use gen_GpuExtent3dDict::*; mod gen_GpuFeatureName; pub use gen_GpuFeatureName::*; mod gen_GpuFilterMode; pub use gen_GpuFilterMode::*; mod gen_GpuFragmentState; pub use gen_GpuFragmentState::*; mod gen_GpuFrontFace; pub use gen_GpuFrontFace::*; mod gen_GpuTexelCopyBufferInfo; pub use gen_GpuTexelCopyBufferInfo::*; mod gen_GpuCopyExternalImageSourceInfo; pub use gen_GpuCopyExternalImageSourceInfo::*; mod gen_GpuTexelCopyTextureInfo; pub use gen_GpuTexelCopyTextureInfo::*; mod gen_GpuCopyExternalImageDestInfo; pub use gen_GpuCopyExternalImageDestInfo::*; mod gen_GpuTexelCopyBufferLayout; pub use gen_GpuTexelCopyBufferLayout::*; mod gen_GpuIndexFormat; pub use gen_GpuIndexFormat::*; mod gen_GpuLoadOp; pub use gen_GpuLoadOp::*; mod gen_gpu_map_mode; pub use gen_gpu_map_mode::*; mod gen_GpuMipmapFilterMode; pub use gen_GpuMipmapFilterMode::*; mod gen_GpuMultisampleState; pub use gen_GpuMultisampleState::*; mod gen_GpuObjectDescriptorBase; pub use gen_GpuObjectDescriptorBase::*; mod gen_GpuOrigin2dDict; pub use gen_GpuOrigin2dDict::*; mod gen_GpuOrigin3dDict; pub use gen_GpuOrigin3dDict::*; mod gen_GpuOutOfMemoryError; pub use gen_GpuOutOfMemoryError::*; mod gen_GpuPipelineDescriptorBase; pub use gen_GpuPipelineDescriptorBase::*; mod gen_GpuPipelineLayout; pub use gen_GpuPipelineLayout::*; mod gen_GpuPipelineLayoutDescriptor; pub use gen_GpuPipelineLayoutDescriptor::*; mod gen_GpuPowerPreference; pub use gen_GpuPowerPreference::*; mod gen_GpuPrimitiveState; pub use gen_GpuPrimitiveState::*; mod gen_GpuPrimitiveTopology; pub use gen_GpuPrimitiveTopology::*; mod gen_GpuProgrammableStage; pub use gen_GpuProgrammableStage::*; mod gen_GpuQuerySet; pub use gen_GpuQuerySet::*; mod gen_GpuQuerySetDescriptor; pub use gen_GpuQuerySetDescriptor::*; mod gen_GpuQueryType; pub use gen_GpuQueryType::*; mod gen_GpuQueue; pub use gen_GpuQueue::*; mod gen_GpuQueueDescriptor; pub use gen_GpuQueueDescriptor::*; mod gen_GpuRenderBundle; pub use gen_GpuRenderBundle::*; mod gen_GpuRenderBundleDescriptor; pub use gen_GpuRenderBundleDescriptor::*; mod gen_GpuRenderBundleEncoder; pub use gen_GpuRenderBundleEncoder::*; mod gen_GpuRenderBundleEncoderDescriptor; pub use gen_GpuRenderBundleEncoderDescriptor::*; mod gen_GpuRenderPassColorAttachment; pub use gen_GpuRenderPassColorAttachment::*; mod gen_GpuRenderPassDepthStencilAttachment; pub use gen_GpuRenderPassDepthStencilAttachment::*; mod gen_GpuRenderPassDescriptor; pub use gen_GpuRenderPassDescriptor::*; mod gen_GpuRenderPassEncoder; pub use gen_GpuRenderPassEncoder::*; mod gen_GpuRenderPassTimestampWrites; pub use gen_GpuRenderPassTimestampWrites::*; mod gen_GpuRenderPipeline; pub use gen_GpuRenderPipeline::*; mod gen_GpuRenderPipelineDescriptor; pub use gen_GpuRenderPipelineDescriptor::*; mod gen_GpuRequestAdapterOptions; pub use gen_GpuRequestAdapterOptions::*; mod gen_GpuSampler; pub use gen_GpuSampler::*; mod gen_GpuSamplerBindingLayout; pub use gen_GpuSamplerBindingLayout::*; mod gen_GpuSamplerBindingType; pub use gen_GpuSamplerBindingType::*; mod gen_GpuSamplerDescriptor; pub use gen_GpuSamplerDescriptor::*; mod gen_GpuShaderModule; pub use gen_GpuShaderModule::*; mod gen_GpuShaderModuleDescriptor; pub use gen_GpuShaderModuleDescriptor::*; mod gen_GpuStencilFaceState; pub use gen_GpuStencilFaceState::*; mod gen_GpuStencilOperation; pub use gen_GpuStencilOperation::*; mod gen_GpuStorageTextureAccess; pub use gen_GpuStorageTextureAccess::*; mod gen_GpuStorageTextureBindingLayout; pub use gen_GpuStorageTextureBindingLayout::*; mod gen_GpuStoreOp; pub use gen_GpuStoreOp::*; mod gen_GpuSupportedFeatures; pub use gen_GpuSupportedFeatures::*; mod gen_GpuSupportedLimits; pub use gen_GpuSupportedLimits::*; mod gen_GpuTexture; pub use gen_GpuTexture::*; mod gen_GpuTextureAspect; pub use gen_GpuTextureAspect::*; mod gen_GpuTextureBindingLayout; pub use gen_GpuTextureBindingLayout::*; mod gen_GpuTextureDescriptor; pub use gen_GpuTextureDescriptor::*; mod gen_GpuTextureDimension; pub use gen_GpuTextureDimension::*; mod gen_GpuTextureFormat; pub use gen_GpuTextureFormat::*; mod gen_GpuTextureSampleType; pub use gen_GpuTextureSampleType::*; mod gen_GpuTextureView; pub use gen_GpuTextureView::*; mod gen_GpuTextureViewDescriptor; pub use gen_GpuTextureViewDescriptor::*; mod gen_GpuTextureViewDimension; pub use gen_GpuTextureViewDimension::*; mod gen_GpuUncapturedErrorEvent; pub use gen_GpuUncapturedErrorEvent::*; mod gen_GpuUncapturedErrorEventInit; pub use gen_GpuUncapturedErrorEventInit::*; mod gen_GpuValidationError; pub use gen_GpuValidationError::*; mod gen_GpuVertexAttribute; pub use gen_GpuVertexAttribute::*; mod gen_GpuVertexBufferLayout; pub use gen_GpuVertexBufferLayout::*; mod gen_GpuVertexFormat; pub use gen_GpuVertexFormat::*; mod gen_GpuVertexState; pub use gen_GpuVertexState::*; mod gen_GpuVertexStepMode; pub use gen_GpuVertexStepMode::*; mod gen_WgslLanguageFeatures; pub use gen_WgslLanguageFeatures::*; ================================================ FILE: wgpu/src/backend/webgpu.rs ================================================ #![allow(clippy::type_complexity)] mod defined_non_null_js_value; mod ext_bindings; #[allow(clippy::allow_attributes)] mod webgpu_sys; use alloc::{ boxed::Box, format, rc::Rc, string::{String, ToString as _}, sync::Arc, vec, vec::Vec, }; use core::{ cell::{Cell, OnceCell, RefCell}, fmt, future::Future, ops::Range, pin::Pin, task::{self, Poll}, }; use wgt::Backends; use js_sys::Promise; use wasm_bindgen::{prelude::*, JsCast}; use crate::{ dispatch::{self, BlasCompactCallback}, Blas, SurfaceTargetUnsafe, Tlas, WriteOnly, }; use defined_non_null_js_value::DefinedNonNullJsValue; // We need to mark various types as Send and Sync to satisfy the Rust type system. // // SAFETY: All webgpu handle types in wasm32 are internally a `JsValue`, and `JsValue` is neither // Send nor Sync. Currently, wasm32 has no threading support by default, so implementing `Send` or // `Sync` for a type is harmless. However, nightly Rust supports compiling wasm with experimental // threading support via `--target-features`. If `wgpu` is being compiled with those features, we do // not implement `Send` and `Sync` on the webgpu handle types. macro_rules! impl_send_sync { ($name:ty) => { #[cfg(send_sync)] unsafe impl Send for $name {} #[cfg(send_sync)] unsafe impl Sync for $name {} }; } #[derive(Clone)] pub struct ContextWebGpu { /// `None` if browser does not advertise support for WebGPU. gpu: Option>, /// Unique identifier for this context. ident: crate::cmp::Identifier, /// Backends requested in the [`crate::InstanceDescriptor`]. /// Remembered for error reporting even though this itself is strictly /// [`Backends::BROWSER_WEBGPU`]. requested_backends: Backends, } impl fmt::Debug for ContextWebGpu { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ContextWebGpu") .field("type", &"Web") .finish() } } impl crate::Error { fn from_js(js_error: js_sys::Object) -> Self { let source = Box::::from(""); if let Some(js_error) = js_error.dyn_ref::() { crate::Error::Validation { source, description: js_error.message(), } } else if js_error.has_type::() { crate::Error::OutOfMemory { source } } else { panic!("Unexpected error"); } } } #[derive(Debug, Clone)] pub struct WebShaderModule { module: webgpu_sys::GpuShaderModule, compilation_info: WebShaderCompilationInfo, /// Unique identifier for this shader module. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] enum WebShaderCompilationInfo { /// WGSL shaders get their compilation info from a native WebGPU function. /// We need the source to be able to do UTF16 to UTF8 location remapping. Wgsl { source: String }, /// Transformed shaders get their compilation info from the transformer. /// Further compilation errors are reported without a span. Transformed { compilation_info: crate::CompilationInfo, }, } fn map_utf16_to_utf8_offset(utf16_offset: u32, text: &str) -> u32 { let mut utf16_i = 0; for (utf8_index, c) in text.char_indices() { if utf16_i >= utf16_offset { return utf8_index as u32; } utf16_i += c.len_utf16() as u32; } if utf16_i >= utf16_offset { text.len() as u32 } else { log::error!("UTF16 offset {utf16_offset} is out of bounds for string {text}"); u32::MAX } } impl crate::CompilationMessage { fn from_js( js_message: webgpu_sys::GpuCompilationMessage, compilation_info: &WebShaderCompilationInfo, ) -> Self { let message_type = match js_message.type_() { webgpu_sys::GpuCompilationMessageType::Error => crate::CompilationMessageType::Error, webgpu_sys::GpuCompilationMessageType::Warning => { crate::CompilationMessageType::Warning } webgpu_sys::GpuCompilationMessageType::Info => crate::CompilationMessageType::Info, _ => crate::CompilationMessageType::Error, }; let utf16_offset = js_message.offset() as u32; let utf16_length = js_message.length() as u32; let span = match compilation_info { WebShaderCompilationInfo::Wgsl { .. } if utf16_offset == 0 && utf16_length == 0 => None, WebShaderCompilationInfo::Wgsl { source } => { let offset = map_utf16_to_utf8_offset(utf16_offset, source); let length = map_utf16_to_utf8_offset(utf16_length, &source[offset as usize..]); let line_number = js_message.line_num() as u32; // That's legal, because we're counting lines the same way let prefix = &source[..offset as usize]; let line_start = prefix.rfind('\n').map(|pos| pos + 1).unwrap_or(0) as u32; let line_position = offset - line_start + 1; // Counting UTF-8 byte indices Some(crate::SourceLocation { offset, length, line_number, line_position, }) } WebShaderCompilationInfo::Transformed { .. } => None, }; crate::CompilationMessage { message: js_message.message(), message_type, location: span, } } } // We need to assert that any future we return is Send to match the native API. // // This is safe on wasm32 *for now*, but similarly to the unsafe Send impls for the handle type // wrappers, the full story for threading on wasm32 is still unfolding. pub(crate) struct MakeSendFuture { future: F, map: M, } impl T, T> Future for MakeSendFuture { type Output = T; fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { // This is safe because we have no Drop implementation to violate the Pin requirements and // do not provide any means of moving the inner future. unsafe { let this = self.get_unchecked_mut(); match Pin::new_unchecked(&mut this.future).poll(cx) { task::Poll::Ready(value) => task::Poll::Ready((this.map)(value)), task::Poll::Pending => task::Poll::Pending, } } } } impl MakeSendFuture { fn new(future: F, map: M) -> Self { Self { future, map } } } #[cfg(send_sync)] unsafe impl Send for MakeSendFuture {} fn map_texture_format(texture_format: wgt::TextureFormat) -> webgpu_sys::GpuTextureFormat { use webgpu_sys::GpuTextureFormat as tf; use wgt::TextureFormat; match texture_format { // 8-bit formats TextureFormat::R8Unorm => tf::R8unorm, TextureFormat::R8Snorm => tf::R8snorm, TextureFormat::R8Uint => tf::R8uint, TextureFormat::R8Sint => tf::R8sint, // 16-bit formats TextureFormat::R16Uint => tf::R16uint, TextureFormat::R16Sint => tf::R16sint, TextureFormat::R16Float => tf::R16float, TextureFormat::Rg8Unorm => tf::Rg8unorm, TextureFormat::Rg8Snorm => tf::Rg8snorm, TextureFormat::Rg8Uint => tf::Rg8uint, TextureFormat::Rg8Sint => tf::Rg8sint, // 32-bit formats TextureFormat::R32Uint => tf::R32uint, TextureFormat::R32Sint => tf::R32sint, TextureFormat::R32Float => tf::R32float, TextureFormat::Rg16Uint => tf::Rg16uint, TextureFormat::Rg16Sint => tf::Rg16sint, TextureFormat::Rg16Float => tf::Rg16float, TextureFormat::Rgba8Unorm => tf::Rgba8unorm, TextureFormat::Rgba8UnormSrgb => tf::Rgba8unormSrgb, TextureFormat::Rgba8Snorm => tf::Rgba8snorm, TextureFormat::Rgba8Uint => tf::Rgba8uint, TextureFormat::Rgba8Sint => tf::Rgba8sint, TextureFormat::Bgra8Unorm => tf::Bgra8unorm, TextureFormat::Bgra8UnormSrgb => tf::Bgra8unormSrgb, // Packed 32-bit formats TextureFormat::Rgb9e5Ufloat => tf::Rgb9e5ufloat, TextureFormat::Rgb10a2Uint => tf::Rgb10a2uint, TextureFormat::Rgb10a2Unorm => tf::Rgb10a2unorm, TextureFormat::Rg11b10Ufloat => tf::Rg11b10ufloat, // 64-bit formats TextureFormat::Rg32Uint => tf::Rg32uint, TextureFormat::Rg32Sint => tf::Rg32sint, TextureFormat::Rg32Float => tf::Rg32float, TextureFormat::Rgba16Uint => tf::Rgba16uint, TextureFormat::Rgba16Sint => tf::Rgba16sint, TextureFormat::Rgba16Float => tf::Rgba16float, // 128-bit formats TextureFormat::Rgba32Uint => tf::Rgba32uint, TextureFormat::Rgba32Sint => tf::Rgba32sint, TextureFormat::Rgba32Float => tf::Rgba32float, // Depth/stencil formats TextureFormat::Stencil8 => tf::Stencil8, TextureFormat::Depth16Unorm => tf::Depth16unorm, TextureFormat::Depth24Plus => tf::Depth24plus, TextureFormat::Depth24PlusStencil8 => tf::Depth24plusStencil8, TextureFormat::Depth32Float => tf::Depth32float, // "depth32float-stencil8" feature TextureFormat::Depth32FloatStencil8 => tf::Depth32floatStencil8, TextureFormat::Bc1RgbaUnorm => tf::Bc1RgbaUnorm, TextureFormat::Bc1RgbaUnormSrgb => tf::Bc1RgbaUnormSrgb, TextureFormat::Bc2RgbaUnorm => tf::Bc2RgbaUnorm, TextureFormat::Bc2RgbaUnormSrgb => tf::Bc2RgbaUnormSrgb, TextureFormat::Bc3RgbaUnorm => tf::Bc3RgbaUnorm, TextureFormat::Bc3RgbaUnormSrgb => tf::Bc3RgbaUnormSrgb, TextureFormat::Bc4RUnorm => tf::Bc4RUnorm, TextureFormat::Bc4RSnorm => tf::Bc4RSnorm, TextureFormat::Bc5RgUnorm => tf::Bc5RgUnorm, TextureFormat::Bc5RgSnorm => tf::Bc5RgSnorm, TextureFormat::Bc6hRgbUfloat => tf::Bc6hRgbUfloat, TextureFormat::Bc6hRgbFloat => tf::Bc6hRgbFloat, TextureFormat::Bc7RgbaUnorm => tf::Bc7RgbaUnorm, TextureFormat::Bc7RgbaUnormSrgb => tf::Bc7RgbaUnormSrgb, TextureFormat::Etc2Rgb8Unorm => tf::Etc2Rgb8unorm, TextureFormat::Etc2Rgb8UnormSrgb => tf::Etc2Rgb8unormSrgb, TextureFormat::Etc2Rgb8A1Unorm => tf::Etc2Rgb8a1unorm, TextureFormat::Etc2Rgb8A1UnormSrgb => tf::Etc2Rgb8a1unormSrgb, TextureFormat::Etc2Rgba8Unorm => tf::Etc2Rgba8unorm, TextureFormat::Etc2Rgba8UnormSrgb => tf::Etc2Rgba8unormSrgb, TextureFormat::EacR11Unorm => tf::EacR11unorm, TextureFormat::EacR11Snorm => tf::EacR11snorm, TextureFormat::EacRg11Unorm => tf::EacRg11unorm, TextureFormat::EacRg11Snorm => tf::EacRg11snorm, TextureFormat::Astc { block, channel } => match channel { wgt::AstcChannel::Unorm => match block { wgt::AstcBlock::B4x4 => tf::Astc4x4Unorm, wgt::AstcBlock::B5x4 => tf::Astc5x4Unorm, wgt::AstcBlock::B5x5 => tf::Astc5x5Unorm, wgt::AstcBlock::B6x5 => tf::Astc6x5Unorm, wgt::AstcBlock::B6x6 => tf::Astc6x6Unorm, wgt::AstcBlock::B8x5 => tf::Astc8x5Unorm, wgt::AstcBlock::B8x6 => tf::Astc8x6Unorm, wgt::AstcBlock::B8x8 => tf::Astc8x8Unorm, wgt::AstcBlock::B10x5 => tf::Astc10x5Unorm, wgt::AstcBlock::B10x6 => tf::Astc10x6Unorm, wgt::AstcBlock::B10x8 => tf::Astc10x8Unorm, wgt::AstcBlock::B10x10 => tf::Astc10x10Unorm, wgt::AstcBlock::B12x10 => tf::Astc12x10Unorm, wgt::AstcBlock::B12x12 => tf::Astc12x12Unorm, }, wgt::AstcChannel::UnormSrgb => match block { wgt::AstcBlock::B4x4 => tf::Astc4x4UnormSrgb, wgt::AstcBlock::B5x4 => tf::Astc5x4UnormSrgb, wgt::AstcBlock::B5x5 => tf::Astc5x5UnormSrgb, wgt::AstcBlock::B6x5 => tf::Astc6x5UnormSrgb, wgt::AstcBlock::B6x6 => tf::Astc6x6UnormSrgb, wgt::AstcBlock::B8x5 => tf::Astc8x5UnormSrgb, wgt::AstcBlock::B8x6 => tf::Astc8x6UnormSrgb, wgt::AstcBlock::B8x8 => tf::Astc8x8UnormSrgb, wgt::AstcBlock::B10x5 => tf::Astc10x5UnormSrgb, wgt::AstcBlock::B10x6 => tf::Astc10x6UnormSrgb, wgt::AstcBlock::B10x8 => tf::Astc10x8UnormSrgb, wgt::AstcBlock::B10x10 => tf::Astc10x10UnormSrgb, wgt::AstcBlock::B12x10 => tf::Astc12x10UnormSrgb, wgt::AstcBlock::B12x12 => tf::Astc12x12UnormSrgb, }, wgt::AstcChannel::Hdr => { unimplemented!("Format {texture_format:?} has no WebGPU equivalent") } }, _ => unimplemented!("Format {texture_format:?} has no WebGPU equivalent"), } } fn map_texture_component_type( sample_type: wgt::TextureSampleType, ) -> webgpu_sys::GpuTextureSampleType { use webgpu_sys::GpuTextureSampleType as ts; use wgt::TextureSampleType; match sample_type { TextureSampleType::Float { filterable: true } => ts::Float, TextureSampleType::Float { filterable: false } => ts::UnfilterableFloat, TextureSampleType::Sint => ts::Sint, TextureSampleType::Uint => ts::Uint, TextureSampleType::Depth => ts::Depth, } } fn map_cull_mode(cull_mode: Option) -> webgpu_sys::GpuCullMode { use webgpu_sys::GpuCullMode as cm; use wgt::Face; match cull_mode { None => cm::None, Some(Face::Front) => cm::Front, Some(Face::Back) => cm::Back, } } fn map_front_face(front_face: wgt::FrontFace) -> webgpu_sys::GpuFrontFace { use webgpu_sys::GpuFrontFace as ff; use wgt::FrontFace; match front_face { FrontFace::Ccw => ff::Ccw, FrontFace::Cw => ff::Cw, } } fn map_primitive_state(primitive: &wgt::PrimitiveState) -> webgpu_sys::GpuPrimitiveState { use webgpu_sys::GpuPrimitiveTopology as pt; use wgt::PrimitiveTopology; let mapped = webgpu_sys::GpuPrimitiveState::new(); mapped.set_cull_mode(map_cull_mode(primitive.cull_mode)); mapped.set_front_face(map_front_face(primitive.front_face)); if let Some(format) = primitive.strip_index_format { mapped.set_strip_index_format(map_index_format(format)); } mapped.set_topology(match primitive.topology { PrimitiveTopology::PointList => pt::PointList, PrimitiveTopology::LineList => pt::LineList, PrimitiveTopology::LineStrip => pt::LineStrip, PrimitiveTopology::TriangleList => pt::TriangleList, PrimitiveTopology::TriangleStrip => pt::TriangleStrip, }); mapped.set_unclipped_depth(primitive.unclipped_depth); match primitive.polygon_mode { wgt::PolygonMode::Fill => {} wgt::PolygonMode::Line => panic!( "{:?} is not enabled for this backend", wgt::Features::POLYGON_MODE_LINE ), wgt::PolygonMode::Point => panic!( "{:?} is not enabled for this backend", wgt::Features::POLYGON_MODE_POINT ), } mapped } fn map_compare_function(compare_fn: wgt::CompareFunction) -> webgpu_sys::GpuCompareFunction { use webgpu_sys::GpuCompareFunction as cf; use wgt::CompareFunction; match compare_fn { CompareFunction::Never => cf::Never, CompareFunction::Less => cf::Less, CompareFunction::Equal => cf::Equal, CompareFunction::LessEqual => cf::LessEqual, CompareFunction::Greater => cf::Greater, CompareFunction::NotEqual => cf::NotEqual, CompareFunction::GreaterEqual => cf::GreaterEqual, CompareFunction::Always => cf::Always, } } fn map_stencil_operation(op: wgt::StencilOperation) -> webgpu_sys::GpuStencilOperation { use webgpu_sys::GpuStencilOperation as so; use wgt::StencilOperation; match op { StencilOperation::Keep => so::Keep, StencilOperation::Zero => so::Zero, StencilOperation::Replace => so::Replace, StencilOperation::Invert => so::Invert, StencilOperation::IncrementClamp => so::IncrementClamp, StencilOperation::DecrementClamp => so::DecrementClamp, StencilOperation::IncrementWrap => so::IncrementWrap, StencilOperation::DecrementWrap => so::DecrementWrap, } } fn map_stencil_state_face(desc: &wgt::StencilFaceState) -> webgpu_sys::GpuStencilFaceState { let mapped = webgpu_sys::GpuStencilFaceState::new(); mapped.set_compare(map_compare_function(desc.compare)); mapped.set_depth_fail_op(map_stencil_operation(desc.depth_fail_op)); mapped.set_fail_op(map_stencil_operation(desc.fail_op)); mapped.set_pass_op(map_stencil_operation(desc.pass_op)); mapped } fn map_depth_stencil_state(desc: &wgt::DepthStencilState) -> webgpu_sys::GpuDepthStencilState { let mapped = webgpu_sys::GpuDepthStencilState::new(map_texture_format(desc.format)); if let Some(compare) = desc.depth_compare { mapped.set_depth_compare(map_compare_function(compare)); } if let Some(write_enabled) = desc.depth_write_enabled { mapped.set_depth_write_enabled(write_enabled); } mapped.set_depth_bias(desc.bias.constant); mapped.set_depth_bias_clamp(desc.bias.clamp); mapped.set_depth_bias_slope_scale(desc.bias.slope_scale); mapped.set_stencil_back(&map_stencil_state_face(&desc.stencil.back)); mapped.set_stencil_front(&map_stencil_state_face(&desc.stencil.front)); mapped.set_stencil_read_mask(desc.stencil.read_mask); mapped.set_stencil_write_mask(desc.stencil.write_mask); mapped } fn map_blend_component(desc: &wgt::BlendComponent) -> webgpu_sys::GpuBlendComponent { let mapped = webgpu_sys::GpuBlendComponent::new(); mapped.set_dst_factor(map_blend_factor(desc.dst_factor)); mapped.set_operation(map_blend_operation(desc.operation)); mapped.set_src_factor(map_blend_factor(desc.src_factor)); mapped } fn map_blend_factor(factor: wgt::BlendFactor) -> webgpu_sys::GpuBlendFactor { use webgpu_sys::GpuBlendFactor as bf; use wgt::BlendFactor; match factor { BlendFactor::Zero => bf::Zero, BlendFactor::One => bf::One, BlendFactor::Src => bf::Src, BlendFactor::OneMinusSrc => bf::OneMinusSrc, BlendFactor::SrcAlpha => bf::SrcAlpha, BlendFactor::OneMinusSrcAlpha => bf::OneMinusSrcAlpha, BlendFactor::Dst => bf::Dst, BlendFactor::OneMinusDst => bf::OneMinusDst, BlendFactor::DstAlpha => bf::DstAlpha, BlendFactor::OneMinusDstAlpha => bf::OneMinusDstAlpha, BlendFactor::SrcAlphaSaturated => bf::SrcAlphaSaturated, BlendFactor::Constant => bf::Constant, BlendFactor::OneMinusConstant => bf::OneMinusConstant, BlendFactor::Src1 => bf::Src1, BlendFactor::OneMinusSrc1 => bf::OneMinusSrc1, BlendFactor::Src1Alpha => bf::Src1Alpha, BlendFactor::OneMinusSrc1Alpha => bf::OneMinusSrc1Alpha, } } fn map_blend_operation(op: wgt::BlendOperation) -> webgpu_sys::GpuBlendOperation { use webgpu_sys::GpuBlendOperation as bo; use wgt::BlendOperation; match op { BlendOperation::Add => bo::Add, BlendOperation::Subtract => bo::Subtract, BlendOperation::ReverseSubtract => bo::ReverseSubtract, BlendOperation::Min => bo::Min, BlendOperation::Max => bo::Max, } } fn map_index_format(format: wgt::IndexFormat) -> webgpu_sys::GpuIndexFormat { use webgpu_sys::GpuIndexFormat as f; use wgt::IndexFormat; match format { IndexFormat::Uint16 => f::Uint16, IndexFormat::Uint32 => f::Uint32, } } fn map_vertex_format(format: wgt::VertexFormat) -> webgpu_sys::GpuVertexFormat { use webgpu_sys::GpuVertexFormat as vf; use wgt::VertexFormat; match format { VertexFormat::Uint8 => vf::Uint8, VertexFormat::Uint8x2 => vf::Uint8x2, VertexFormat::Uint8x4 => vf::Uint8x4, VertexFormat::Sint8 => vf::Sint8, VertexFormat::Sint8x2 => vf::Sint8x2, VertexFormat::Sint8x4 => vf::Sint8x4, VertexFormat::Unorm8 => vf::Unorm8, VertexFormat::Unorm8x2 => vf::Unorm8x2, VertexFormat::Unorm8x4 => vf::Unorm8x4, VertexFormat::Snorm8 => vf::Snorm8, VertexFormat::Snorm8x2 => vf::Snorm8x2, VertexFormat::Snorm8x4 => vf::Snorm8x4, VertexFormat::Uint16 => vf::Uint16, VertexFormat::Uint16x2 => vf::Uint16x2, VertexFormat::Uint16x4 => vf::Uint16x4, VertexFormat::Sint16 => vf::Sint16, VertexFormat::Sint16x2 => vf::Sint16x2, VertexFormat::Sint16x4 => vf::Sint16x4, VertexFormat::Unorm16 => vf::Unorm16, VertexFormat::Unorm16x2 => vf::Unorm16x2, VertexFormat::Unorm16x4 => vf::Unorm16x4, VertexFormat::Snorm16 => vf::Snorm16, VertexFormat::Snorm16x2 => vf::Snorm16x2, VertexFormat::Snorm16x4 => vf::Snorm16x4, VertexFormat::Float16 => vf::Float16, VertexFormat::Float16x2 => vf::Float16x2, VertexFormat::Float16x4 => vf::Float16x4, VertexFormat::Float32 => vf::Float32, VertexFormat::Float32x2 => vf::Float32x2, VertexFormat::Float32x3 => vf::Float32x3, VertexFormat::Float32x4 => vf::Float32x4, VertexFormat::Uint32 => vf::Uint32, VertexFormat::Uint32x2 => vf::Uint32x2, VertexFormat::Uint32x3 => vf::Uint32x3, VertexFormat::Uint32x4 => vf::Uint32x4, VertexFormat::Sint32 => vf::Sint32, VertexFormat::Sint32x2 => vf::Sint32x2, VertexFormat::Sint32x3 => vf::Sint32x3, VertexFormat::Sint32x4 => vf::Sint32x4, VertexFormat::Unorm10_10_10_2 => vf::Unorm1010102, VertexFormat::Unorm8x4Bgra => vf::Unorm8x4Bgra, VertexFormat::Float64 | VertexFormat::Float64x2 | VertexFormat::Float64x3 | VertexFormat::Float64x4 => { panic!("VERTEX_ATTRIBUTE_64BIT feature must be enabled to use Double formats") } } } fn map_vertex_step_mode(mode: wgt::VertexStepMode) -> webgpu_sys::GpuVertexStepMode { use webgpu_sys::GpuVertexStepMode as sm; use wgt::VertexStepMode; match mode { VertexStepMode::Vertex => sm::Vertex, VertexStepMode::Instance => sm::Instance, } } fn map_extent_3d(extent: wgt::Extent3d) -> webgpu_sys::GpuExtent3dDict { let mapped = webgpu_sys::GpuExtent3dDict::new(extent.width); mapped.set_height(extent.height); mapped.set_depth_or_array_layers(extent.depth_or_array_layers); mapped } fn map_origin_2d(extent: wgt::Origin2d) -> webgpu_sys::GpuOrigin2dDict { let mapped = webgpu_sys::GpuOrigin2dDict::new(); mapped.set_x(extent.x); mapped.set_y(extent.y); mapped } fn map_origin_3d(origin: wgt::Origin3d) -> webgpu_sys::GpuOrigin3dDict { let mapped = webgpu_sys::GpuOrigin3dDict::new(); mapped.set_x(origin.x); mapped.set_y(origin.y); mapped.set_z(origin.z); mapped } fn map_texture_dimension( texture_dimension: wgt::TextureDimension, ) -> webgpu_sys::GpuTextureDimension { match texture_dimension { wgt::TextureDimension::D1 => webgpu_sys::GpuTextureDimension::N1d, wgt::TextureDimension::D2 => webgpu_sys::GpuTextureDimension::N2d, wgt::TextureDimension::D3 => webgpu_sys::GpuTextureDimension::N3d, } } fn map_texture_view_dimension( texture_view_dimension: wgt::TextureViewDimension, ) -> webgpu_sys::GpuTextureViewDimension { use webgpu_sys::GpuTextureViewDimension as tvd; match texture_view_dimension { wgt::TextureViewDimension::D1 => tvd::N1d, wgt::TextureViewDimension::D2 => tvd::N2d, wgt::TextureViewDimension::D2Array => tvd::N2dArray, wgt::TextureViewDimension::Cube => tvd::Cube, wgt::TextureViewDimension::CubeArray => tvd::CubeArray, wgt::TextureViewDimension::D3 => tvd::N3d, } } fn map_buffer_copy_view( view: crate::TexelCopyBufferInfo<'_>, ) -> webgpu_sys::GpuTexelCopyBufferInfo { let buffer = view.buffer.inner.as_webgpu(); let mapped = webgpu_sys::GpuTexelCopyBufferInfo::new(&buffer.inner); if let Some(bytes_per_row) = view.layout.bytes_per_row { mapped.set_bytes_per_row(bytes_per_row); } if let Some(rows_per_image) = view.layout.rows_per_image { mapped.set_rows_per_image(rows_per_image); } mapped.set_offset(view.layout.offset as f64); mapped } fn map_texture_copy_view( view: crate::TexelCopyTextureInfo<'_>, ) -> webgpu_sys::GpuTexelCopyTextureInfo { let texture = view.texture.inner.as_webgpu(); let mapped = webgpu_sys::GpuTexelCopyTextureInfo::new(&texture.inner); mapped.set_mip_level(view.mip_level); mapped.set_origin(&map_origin_3d(view.origin)); mapped.set_aspect(map_texture_aspect(view.aspect)); mapped } fn map_tagged_texture_copy_view( view: crate::CopyExternalImageDestInfo<&crate::api::Texture>, ) -> webgpu_sys::GpuCopyExternalImageDestInfo { let texture = view.texture.inner.as_webgpu(); let mapped = webgpu_sys::GpuCopyExternalImageDestInfo::new(&texture.inner); mapped.set_mip_level(view.mip_level); mapped.set_origin(&map_origin_3d(view.origin)); mapped.set_aspect(map_texture_aspect(view.aspect)); // mapped.set_color_space(map_color_space(view.color_space)); mapped.set_premultiplied_alpha(view.premultiplied_alpha); mapped } fn map_external_texture_copy_view( view: &crate::CopyExternalImageSourceInfo, ) -> webgpu_sys::GpuCopyExternalImageSourceInfo { let mapped = webgpu_sys::GpuCopyExternalImageSourceInfo::new(&view.source); mapped.set_origin(&map_origin_2d(view.origin)); mapped.set_flip_y(view.flip_y); mapped } fn map_texture_aspect(aspect: wgt::TextureAspect) -> webgpu_sys::GpuTextureAspect { match aspect { wgt::TextureAspect::All => webgpu_sys::GpuTextureAspect::All, wgt::TextureAspect::StencilOnly => webgpu_sys::GpuTextureAspect::StencilOnly, wgt::TextureAspect::DepthOnly => webgpu_sys::GpuTextureAspect::DepthOnly, wgt::TextureAspect::Plane0 | wgt::TextureAspect::Plane1 | wgt::TextureAspect::Plane2 => { panic!("multi-plane textures are not supported") } } } fn map_filter_mode(mode: wgt::FilterMode) -> webgpu_sys::GpuFilterMode { match mode { wgt::FilterMode::Nearest => webgpu_sys::GpuFilterMode::Nearest, wgt::FilterMode::Linear => webgpu_sys::GpuFilterMode::Linear, } } fn map_mipmap_filter_mode(mode: wgt::MipmapFilterMode) -> webgpu_sys::GpuMipmapFilterMode { match mode { wgt::MipmapFilterMode::Nearest => webgpu_sys::GpuMipmapFilterMode::Nearest, wgt::MipmapFilterMode::Linear => webgpu_sys::GpuMipmapFilterMode::Linear, } } fn map_address_mode(mode: wgt::AddressMode) -> webgpu_sys::GpuAddressMode { match mode { wgt::AddressMode::ClampToEdge => webgpu_sys::GpuAddressMode::ClampToEdge, wgt::AddressMode::Repeat => webgpu_sys::GpuAddressMode::Repeat, wgt::AddressMode::MirrorRepeat => webgpu_sys::GpuAddressMode::MirrorRepeat, wgt::AddressMode::ClampToBorder => panic!("Clamp to border is not supported"), } } fn map_color(color: wgt::Color) -> webgpu_sys::GpuColorDict { webgpu_sys::GpuColorDict::new(color.a, color.b, color.g, color.r) } fn map_store_op(store: crate::StoreOp) -> webgpu_sys::GpuStoreOp { match store { crate::StoreOp::Store => webgpu_sys::GpuStoreOp::Store, crate::StoreOp::Discard => webgpu_sys::GpuStoreOp::Discard, } } fn map_map_mode(mode: crate::MapMode) -> u32 { match mode { crate::MapMode::Read => webgpu_sys::gpu_map_mode::READ, crate::MapMode::Write => webgpu_sys::gpu_map_mode::WRITE, } } const FEATURES_MAPPING: [(wgt::Features, webgpu_sys::GpuFeatureName); 16] = [ ( wgt::Features::DEPTH_CLIP_CONTROL, webgpu_sys::GpuFeatureName::DepthClipControl, ), ( wgt::Features::DEPTH32FLOAT_STENCIL8, webgpu_sys::GpuFeatureName::Depth32floatStencil8, ), ( wgt::Features::TEXTURE_COMPRESSION_BC, webgpu_sys::GpuFeatureName::TextureCompressionBc, ), ( wgt::Features::TEXTURE_COMPRESSION_BC_SLICED_3D, webgpu_sys::GpuFeatureName::TextureCompressionBcSliced3d, ), ( wgt::Features::TEXTURE_COMPRESSION_ETC2, webgpu_sys::GpuFeatureName::TextureCompressionEtc2, ), ( wgt::Features::TEXTURE_COMPRESSION_ASTC, webgpu_sys::GpuFeatureName::TextureCompressionAstc, ), ( wgt::Features::TEXTURE_COMPRESSION_ASTC_SLICED_3D, webgpu_sys::GpuFeatureName::TextureCompressionAstcSliced3d, ), ( wgt::Features::TIMESTAMP_QUERY, webgpu_sys::GpuFeatureName::TimestampQuery, ), ( wgt::Features::INDIRECT_FIRST_INSTANCE, webgpu_sys::GpuFeatureName::IndirectFirstInstance, ), ( wgt::Features::SHADER_F16, webgpu_sys::GpuFeatureName::ShaderF16, ), ( wgt::Features::RG11B10UFLOAT_RENDERABLE, webgpu_sys::GpuFeatureName::Rg11b10ufloatRenderable, ), ( wgt::Features::BGRA8UNORM_STORAGE, webgpu_sys::GpuFeatureName::Bgra8unormStorage, ), ( wgt::Features::FLOAT32_FILTERABLE, webgpu_sys::GpuFeatureName::Float32Filterable, ), ( wgt::Features::FLOAT32_BLENDABLE, webgpu_sys::GpuFeatureName::Float32Blendable, ), ( wgt::Features::DUAL_SOURCE_BLENDING, webgpu_sys::GpuFeatureName::DualSourceBlending, ), ( wgt::Features::CLIP_DISTANCES, webgpu_sys::GpuFeatureName::ClipDistances, ), ]; fn map_wgt_features(supported_features: webgpu_sys::GpuSupportedFeatures) -> wgt::Features { let mut features = wgt::Features::empty(); for (wgpu_feat, web_feat) in FEATURES_MAPPING { match wasm_bindgen::JsValue::from(web_feat).as_string() { Some(value) if supported_features.has(&value) => features |= wgpu_feat, _ => {} } } features } fn map_wgt_limits(limits: webgpu_sys::GpuSupportedLimits) -> wgt::Limits { wgt::Limits { max_texture_dimension_1d: limits.max_texture_dimension_1d(), max_texture_dimension_2d: limits.max_texture_dimension_2d(), max_texture_dimension_3d: limits.max_texture_dimension_3d(), max_texture_array_layers: limits.max_texture_array_layers(), max_bind_groups: limits.max_bind_groups(), max_bindings_per_bind_group: limits.max_bindings_per_bind_group(), max_dynamic_uniform_buffers_per_pipeline_layout: limits .max_dynamic_uniform_buffers_per_pipeline_layout(), max_dynamic_storage_buffers_per_pipeline_layout: limits .max_dynamic_storage_buffers_per_pipeline_layout(), max_sampled_textures_per_shader_stage: limits.max_sampled_textures_per_shader_stage(), max_samplers_per_shader_stage: limits.max_samplers_per_shader_stage(), max_storage_buffers_per_shader_stage: limits.max_storage_buffers_per_shader_stage(), max_storage_textures_per_shader_stage: limits.max_storage_textures_per_shader_stage(), max_uniform_buffers_per_shader_stage: limits.max_uniform_buffers_per_shader_stage(), max_binding_array_elements_per_shader_stage: 0, max_binding_array_sampler_elements_per_shader_stage: 0, max_binding_array_acceleration_structure_elements_per_shader_stage: 0, max_uniform_buffer_binding_size: limits.max_uniform_buffer_binding_size() as u64, max_storage_buffer_binding_size: limits.max_storage_buffer_binding_size() as u64, max_vertex_buffers: limits.max_vertex_buffers(), max_buffer_size: limits.max_buffer_size() as u64, max_vertex_attributes: limits.max_vertex_attributes(), max_vertex_buffer_array_stride: limits.max_vertex_buffer_array_stride(), max_inter_stage_shader_variables: limits.max_inter_stage_shader_variables(), min_uniform_buffer_offset_alignment: limits.min_uniform_buffer_offset_alignment(), min_storage_buffer_offset_alignment: limits.min_storage_buffer_offset_alignment(), max_color_attachments: limits.max_color_attachments(), max_color_attachment_bytes_per_sample: limits.max_color_attachment_bytes_per_sample(), max_compute_workgroup_storage_size: limits.max_compute_workgroup_storage_size(), max_compute_invocations_per_workgroup: limits.max_compute_invocations_per_workgroup(), max_compute_workgroup_size_x: limits.max_compute_workgroup_size_x(), max_compute_workgroup_size_y: limits.max_compute_workgroup_size_y(), max_compute_workgroup_size_z: limits.max_compute_workgroup_size_z(), max_compute_workgroups_per_dimension: limits.max_compute_workgroups_per_dimension(), max_immediate_size: wgt::Limits::default().max_immediate_size, max_non_sampler_bindings: wgt::Limits::default().max_non_sampler_bindings, max_task_mesh_workgroup_total_count: wgt::Limits::default() .max_task_mesh_workgroup_total_count, max_task_mesh_workgroups_per_dimension: wgt::Limits::default() .max_task_mesh_workgroups_per_dimension, max_task_invocations_per_workgroup: wgt::Limits::default() .max_task_invocations_per_workgroup, max_task_invocations_per_dimension: wgt::Limits::default() .max_task_invocations_per_dimension, max_mesh_invocations_per_workgroup: wgt::Limits::default() .max_mesh_invocations_per_workgroup, max_mesh_invocations_per_dimension: wgt::Limits::default() .max_mesh_invocations_per_dimension, max_task_payload_size: wgt::Limits::default().max_task_payload_size, max_mesh_output_vertices: wgt::Limits::default().max_mesh_output_vertices, max_mesh_output_primitives: wgt::Limits::default().max_mesh_output_primitives, max_mesh_output_layers: wgt::Limits::default().max_mesh_output_layers, max_mesh_multiview_view_count: wgt::Limits::default().max_mesh_multiview_view_count, max_blas_primitive_count: wgt::Limits::default().max_blas_primitive_count, max_blas_geometry_count: wgt::Limits::default().max_blas_geometry_count, max_tlas_instance_count: wgt::Limits::default().max_tlas_instance_count, max_acceleration_structures_per_shader_stage: wgt::Limits::default() .max_acceleration_structures_per_shader_stage, max_multiview_view_count: wgt::Limits::default().max_multiview_view_count, } } fn map_adapter_info(adapter_info: &webgpu_sys::GpuAdapterInfo) -> wgt::AdapterInfo { // TODO(https://github.com/gfx-rs/wgpu/issues/8819): populate more fields if/when possible wgt::AdapterInfo { name: adapter_info.description().to_string(), vendor: 0, device: 0, device_type: wgt::DeviceType::Other, device_pci_bus_id: String::new(), driver: String::new(), driver_info: String::new(), backend: wgt::Backend::BrowserWebGpu, subgroup_min_size: wgt::MINIMUM_SUBGROUP_MIN_SIZE, subgroup_max_size: wgt::MAXIMUM_SUBGROUP_MAX_SIZE, transient_saves_memory: false, } } fn map_js_sys_limits(limits: &wgt::Limits) -> js_sys::Object { let object = js_sys::Object::new(); macro_rules! set_properties { (($from:expr) => ($on:expr) : $(($js_ident:ident, $rs_ident:ident)),* $(,)?) => { $( ::js_sys::Reflect::set( &$on, &::wasm_bindgen::JsValue::from(stringify!($js_ident)), // Numbers may be u64, however using `from` on a u64 yields // errors on the wasm side, since it uses an unsupported api. // Wasm sends us things that need to fit into u64s by sending // us f64s instead. So we just send them f64s back. &::wasm_bindgen::JsValue::from($from.$rs_ident as f64) ) .expect("Setting Object properties should never fail."); )* } } // https://gpuweb.github.io/gpuweb/#gpusupportedlimits set_properties![ (limits) => (object): (maxTextureDimension1D, max_texture_dimension_1d), (maxTextureDimension2D, max_texture_dimension_2d), (maxTextureDimension3D, max_texture_dimension_3d), (maxTextureArrayLayers, max_texture_array_layers), (maxBindGroups, max_bind_groups), // TODO: (maxBindGroupsPlusVertexBuffers, max_bind_groups_plus_vertex_buffers), (maxBindingsPerBindGroup, max_bindings_per_bind_group), (maxDynamicUniformBuffersPerPipelineLayout, max_dynamic_uniform_buffers_per_pipeline_layout), (maxDynamicStorageBuffersPerPipelineLayout, max_dynamic_storage_buffers_per_pipeline_layout), (maxSampledTexturesPerShaderStage, max_sampled_textures_per_shader_stage), (maxSamplersPerShaderStage, max_samplers_per_shader_stage), (maxStorageBuffersPerShaderStage, max_storage_buffers_per_shader_stage), (maxStorageTexturesPerShaderStage, max_storage_textures_per_shader_stage), (maxUniformBuffersPerShaderStage, max_uniform_buffers_per_shader_stage), (maxUniformBufferBindingSize, max_uniform_buffer_binding_size), (maxStorageBufferBindingSize, max_storage_buffer_binding_size), (minUniformBufferOffsetAlignment, min_uniform_buffer_offset_alignment), (minStorageBufferOffsetAlignment, min_storage_buffer_offset_alignment), (maxVertexBuffers, max_vertex_buffers), (maxBufferSize, max_buffer_size), (maxVertexAttributes, max_vertex_attributes), (maxVertexBufferArrayStride, max_vertex_buffer_array_stride), (maxInterStageShaderVariables, max_inter_stage_shader_variables), (maxColorAttachments, max_color_attachments), (maxColorAttachmentBytesPerSample, max_color_attachment_bytes_per_sample), (maxComputeWorkgroupStorageSize, max_compute_workgroup_storage_size), (maxComputeInvocationsPerWorkgroup, max_compute_invocations_per_workgroup), (maxComputeWorkgroupSizeX, max_compute_workgroup_size_x), (maxComputeWorkgroupSizeY, max_compute_workgroup_size_y), (maxComputeWorkgroupSizeZ, max_compute_workgroup_size_z), (maxComputeWorkgroupsPerDimension, max_compute_workgroups_per_dimension), ]; object } type JsFutureResult = Result; fn future_request_adapter( result: JsFutureResult, requested_backends: Backends, ) -> Result { let web_adapter: Option = result.and_then(wasm_bindgen::JsCast::dyn_into).ok(); web_adapter .map(|adapter| { WebAdapter { inner: adapter, ident: crate::cmp::Identifier::create(), } .into() }) .ok_or_else(|| request_adapter_null_error(requested_backends)) } // Translate WebGPU’s null return into our error. fn request_adapter_null_error(requested_backends: Backends) -> wgt::RequestAdapterError { wgt::RequestAdapterError::NotFound { active_backends: Backends::BROWSER_WEBGPU, requested_backends, // TODO: supported_backends should also include wgpu-core-based backends, // if they were compiled in. supported_backends: Backends::BROWSER_WEBGPU, no_fallback_backends: Backends::empty(), no_adapter_backends: Backends::BROWSER_WEBGPU, incompatible_surface_backends: Backends::empty(), } } fn future_request_device( result: JsFutureResult, ) -> Result<(dispatch::DispatchDevice, dispatch::DispatchQueue), crate::RequestDeviceError> { result .map(|js_value| { let device = webgpu_sys::GpuDevice::from(js_value); let queue = device.queue(); ( WebDevice { inner: device, ident: crate::cmp::Identifier::create(), error_scope_count: Rc::new(Cell::new(0)), } .into(), WebQueue { inner: queue, ident: crate::cmp::Identifier::create(), } .into(), ) }) .map_err(|error_value| crate::RequestDeviceError { // wasm-bindgen provides a reasonable error stringification via `Debug` impl inner: crate::RequestDeviceErrorKind::WebGpu(format!("{error_value:?}")), }) } fn future_pop_error_scope(result: JsFutureResult) -> Option { match result { Ok(js_value) if js_value.is_object() => { let js_error = wasm_bindgen::JsCast::dyn_into(js_value).unwrap(); Some(crate::Error::from_js(js_error)) } _ => None, } } fn future_compilation_info( result: JsFutureResult, base_compilation_info: &WebShaderCompilationInfo, ) -> crate::CompilationInfo { let base_messages = match base_compilation_info { WebShaderCompilationInfo::Transformed { compilation_info } => { compilation_info.messages.iter().cloned() } _ => [].iter().cloned(), }; let messages = match result { Ok(js_value) => { let info = webgpu_sys::GpuCompilationInfo::from(js_value); base_messages .chain(info.messages().into_iter().map(|message| { crate::CompilationMessage::from_js( webgpu_sys::GpuCompilationMessage::from(message), base_compilation_info, ) })) .collect() } Err(_v) => base_messages .chain(core::iter::once(crate::CompilationMessage { message: "Getting compilation info failed".to_string(), message_type: crate::CompilationMessageType::Error, location: None, })) .collect(), }; crate::CompilationInfo { messages } } /// Calls `callback(success_value)` when the promise completes successfully, calls `callback(failure_value)` /// when the promise completes unsuccessfully. fn register_then_closures(promise: &Promise, callback: F, success_value: T, failure_value: T) where F: FnOnce(T) + 'static, T: 'static, { // Both the 'success' and 'rejected' closures need access to callback, but only one // of them will ever run. We have them both hold a reference to a `Rc>>`, // and then take ownership of callback when invoked. // // We also only need Rc's because these will only ever be called on our thread. // // We also store the actual closure types inside this Rc, as the closures need to be kept alive // until they are actually called by the callback. It is valid to drop a closure inside of a callback. // This allows us to keep the closures alive without leaking them. let rc_callback: Rc>> = Rc::new(RefCell::new(None)); let rc_callback_clone1 = rc_callback.clone(); let rc_callback_clone2 = rc_callback.clone(); let closure_success = wasm_bindgen::closure::Closure::once(move |_| { let (success_closure, rejection_closure, callback) = rc_callback_clone1.borrow_mut().take().unwrap(); callback(success_value); // drop the closures, including ourselves, which will free any captured memory. drop((success_closure, rejection_closure)); }); let closure_rejected = wasm_bindgen::closure::Closure::once(move |_| { let (success_closure, rejection_closure, callback) = rc_callback_clone2.borrow_mut().take().unwrap(); callback(failure_value); // drop the closures, including ourselves, which will free any captured memory. drop((success_closure, rejection_closure)); }); // Calling then before setting the value in the Rc seems like a race, but it isn't // because the promise callback will run on this thread, so there is no race. let _ = promise.then2(&closure_success, &closure_rejected); *rc_callback.borrow_mut() = Some((closure_success, closure_rejected, callback)); } impl ContextWebGpu { /// Common portion of the internal branches of the public `instance_create_surface` function. /// /// Note: Analogous code also exists in the WebGL2 backend at /// `wgpu_hal::gles::web::Instance`. fn create_surface_from_context( &self, canvas: Canvas, context_result: Result, wasm_bindgen::JsValue>, ) -> Result { let context: js_sys::Object = match context_result { Ok(Some(context)) => context, Ok(None) => { // // A getContext() call “returns null if contextId is not supported, or if the // canvas has already been initialized with another context type”. Additionally, // “not supported” could include “insufficient GPU resources” or “the GPU process // previously crashed”. So, we must return it as an `Err` since it could occur // for circumstances outside the application author's control. return Err(crate::CreateSurfaceError { inner: crate::CreateSurfaceErrorKind::Web( String::from( "canvas.getContext() returned null; webgpu not available or canvas already in use" ) ) }); } Err(js_error) => { // // A thrown exception indicates misuse of the canvas state. return Err(crate::CreateSurfaceError { inner: crate::CreateSurfaceErrorKind::Web(format!( "canvas.getContext() threw exception {js_error:?}", )), }); } }; // Not returning this error because it is a type error that shouldn't happen unless // the browser, JS builtin objects, or wasm bindings are misbehaving somehow. let context: webgpu_sys::GpuCanvasContext = context .dyn_into() .expect("canvas context is not a GPUCanvasContext"); Ok(WebSurface { gpu: self.gpu.clone(), context, canvas, ident: crate::cmp::Identifier::create(), } .into()) } } // Represents the global object in the JavaScript context. // It can be cast to from `webgpu_sys::global` and exposes two getters `window` and `worker` of which only one is defined depending on the caller's context. // When called from the UI thread only `window` is defined whereas `worker` is only defined within a web worker context. // See: https://github.com/rustwasm/gloo/blob/2c9e776701ecb90c53e62dec1abd19c2b70e47c7/crates/timers/src/callback.rs#L8-L40 #[wasm_bindgen] extern "C" { type Global; #[wasm_bindgen(method, getter, js_name = Window)] fn window(this: &Global) -> JsValue; #[wasm_bindgen(method, getter, js_name = WorkerGlobalScope)] fn worker(this: &Global) -> JsValue; } #[derive(Debug, Clone)] pub enum Canvas { Canvas(web_sys::HtmlCanvasElement), Offscreen(web_sys::OffscreenCanvas), } #[derive(Debug, Clone, Copy)] pub struct BrowserGpuPropertyInaccessible; /// Returns the browser's gpu object or `Err(BrowserGpuPropertyInaccessible)` if /// the current context is neither the main thread nor a dedicated worker. /// /// If WebGPU is not supported, the Gpu property may (!) be `undefined`, /// and so this function will return `Ok(None)`. /// Note that this check is insufficient to determine whether WebGPU is /// supported, as the browser may define the Gpu property, but be unable to /// create any WebGPU adapters. /// To detect whether WebGPU is supported, use the [`crate::utils::is_browser_webgpu_supported`] function. /// /// See: /// * /// * pub fn get_browser_gpu_property( ) -> Result>, BrowserGpuPropertyInaccessible> { let global: Global = js_sys::global().unchecked_into(); let maybe_undefined_gpu: webgpu_sys::Gpu = if !global.window().is_undefined() { let navigator = global.unchecked_into::().navigator(); ext_bindings::NavigatorGpu::gpu(&navigator) } else if !global.worker().is_undefined() { let navigator = global .unchecked_into::() .navigator(); ext_bindings::NavigatorGpu::gpu(&navigator) } else { return Err(BrowserGpuPropertyInaccessible); }; Ok(DefinedNonNullJsValue::new(maybe_undefined_gpu)) } #[derive(Debug, Clone)] pub struct WebAdapter { pub(crate) inner: webgpu_sys::GpuAdapter, /// Unique identifier for this Adapter. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebDevice { pub(crate) inner: webgpu_sys::GpuDevice, /// Unique identifier for this Device. ident: crate::cmp::Identifier, /// Current number of error scopes that have been pushed on the device. error_scope_count: Rc>, } #[derive(Debug, Clone)] pub struct WebQueue { pub(crate) inner: webgpu_sys::GpuQueue, /// Unique identifier for this Queue. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebBindGroupLayout { pub(crate) inner: webgpu_sys::GpuBindGroupLayout, /// Unique identifier for this BindGroupLayout. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebBindGroup { pub(crate) inner: webgpu_sys::GpuBindGroup, /// Unique identifier for this BindGroup. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebTextureView { pub(crate) inner: webgpu_sys::GpuTextureView, /// Unique identifier for this TextureView. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebSampler { pub(crate) inner: webgpu_sys::GpuSampler, /// Unique identifier for this Sampler. ident: crate::cmp::Identifier, } /// Remembers which portion of a buffer has been mapped, along with a reference /// to the mapped portion. #[derive(Debug, Clone)] struct WebBufferMapState { /// The mapped memory of the buffer. pub mapped_buffer: Option, /// The total range which has been mapped in the buffer overall. pub range: Range, } /// Stores the state of a GPU buffer and a reference to its mapped `ArrayBuffer` (if any). /// The WebGPU specification forbids calling `getMappedRange` on a `webgpu_sys::GpuBuffer` more than /// once, so this struct stores the initial mapped range and re-uses it, allowing for multiple `get_mapped_range` /// calls on the Rust-side. #[derive(Debug, Clone)] pub struct WebBuffer { /// The associated GPU buffer. inner: webgpu_sys::GpuBuffer, /// The mapped array buffer and mapped range. mapping: Rc>, /// Unique identifier for this Buffer. ident: crate::cmp::Identifier, } impl WebBuffer { /// Creates a new web buffer for the given Javascript object and description. fn new(inner: webgpu_sys::GpuBuffer, desc: &crate::BufferDescriptor<'_>) -> Self { Self { inner, mapping: Rc::new(RefCell::new(WebBufferMapState { mapped_buffer: None, range: 0..desc.size, })), ident: crate::cmp::Identifier::create(), } } /// Obtains a reference to the re-usable buffer mapping as a Javascript array view. fn get_mapped_range(&self, sub_range: Range) -> js_sys::Uint8Array { let mut mapping = self.mapping.borrow_mut(); let range = mapping.range.clone(); let array_buffer = mapping.mapped_buffer.get_or_insert_with(|| { self.inner .get_mapped_range_with_f64_and_f64( range.start as f64, (range.end - range.start) as f64, ) .unwrap() }); js_sys::Uint8Array::new_with_byte_offset_and_length( array_buffer, (sub_range.start - range.start) as u32, (sub_range.end - sub_range.start) as u32, ) } /// Sets the range of the buffer which is presently mapped. fn set_mapped_range(&self, range: Range) { self.mapping.borrow_mut().range = range; } } #[derive(Debug, Clone)] pub struct WebTexture { pub(crate) inner: webgpu_sys::GpuTexture, /// Unique identifier for this Texture. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebExternalTexture { /// Unique identifier for this ExternalTexture. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebBlas { /// Unique identifier for this Blas. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebTlas { /// Unique identifier for this Blas. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebQuerySet { pub(crate) inner: webgpu_sys::GpuQuerySet, /// Unique identifier for this QuerySet. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebPipelineLayout { pub(crate) inner: webgpu_sys::GpuPipelineLayout, /// Unique identifier for this PipelineLayout. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebRenderPipeline { pub(crate) inner: webgpu_sys::GpuRenderPipeline, /// Unique identifier for this RenderPipeline. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebComputePipeline { pub(crate) inner: webgpu_sys::GpuComputePipeline, /// Unique identifier for this ComputePipeline. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebPipelineCache { /// Unique identifier for this PipelineCache. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebCommandEncoder { pub(crate) inner: webgpu_sys::GpuCommandEncoder, /// Unique identifier for this CommandEncoder. ident: crate::cmp::Identifier, } #[derive(Debug)] pub struct WebComputePassEncoder { pub(crate) inner: webgpu_sys::GpuComputePassEncoder, /// Unique identifier for this ComputePassEncoder. ident: crate::cmp::Identifier, } #[derive(Debug)] pub struct WebRenderPassEncoder { pub(crate) inner: webgpu_sys::GpuRenderPassEncoder, /// Unique identifier for this RenderPassEncoder. ident: crate::cmp::Identifier, } #[derive(Debug)] pub struct WebCommandBuffer { pub(crate) inner: webgpu_sys::GpuCommandBuffer, /// Unique identifier for this CommandBuffer. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebRenderBundleEncoder { pub(crate) inner: webgpu_sys::GpuRenderBundleEncoder, /// Unique identifier for this RenderBundleEncoder. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebRenderBundle { pub(crate) inner: webgpu_sys::GpuRenderBundle, /// Unique identifier for this RenderBundle. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebSurface { gpu: Option>, canvas: Canvas, context: webgpu_sys::GpuCanvasContext, /// Unique identifier for this Surface. ident: crate::cmp::Identifier, } #[derive(Debug, Clone)] pub struct WebSurfaceOutputDetail { /// Unique identifier for this SurfaceOutputDetail. ident: crate::cmp::Identifier, } #[derive(Debug)] pub struct WebQueueWriteBuffer { inner: Box<[u8]>, /// Unique identifier for this QueueWriteBuffer. ident: crate::cmp::Identifier, } #[derive(Debug)] pub struct WebBufferMappedRange { actual_mapping: js_sys::Uint8Array, /// Copy of actual_mapping that lives in the Rust/Wasm heap instead of JS. This /// is done only when accessed for the first time to avoid unnecessary allocations. temporary_mapping: OnceCell>, /// Whether `temporary_mapping` has possibly been written to and needs to be written back to JS. temporary_mapping_modified: bool, /// Unique identifier for this BufferMappedRange. ident: crate::cmp::Identifier, } impl_send_sync!(ContextWebGpu); impl_send_sync!(WebAdapter); impl_send_sync!(WebDevice); impl_send_sync!(WebQueue); impl_send_sync!(WebShaderModule); impl_send_sync!(WebBindGroupLayout); impl_send_sync!(WebBindGroup); impl_send_sync!(WebTextureView); impl_send_sync!(WebSampler); impl_send_sync!(WebBuffer); impl_send_sync!(WebTexture); impl_send_sync!(WebExternalTexture); impl_send_sync!(WebBlas); impl_send_sync!(WebTlas); impl_send_sync!(WebQuerySet); impl_send_sync!(WebPipelineLayout); impl_send_sync!(WebRenderPipeline); impl_send_sync!(WebComputePipeline); impl_send_sync!(WebPipelineCache); impl_send_sync!(WebCommandEncoder); impl_send_sync!(WebComputePassEncoder); impl_send_sync!(WebRenderPassEncoder); impl_send_sync!(WebCommandBuffer); impl_send_sync!(WebRenderBundleEncoder); impl_send_sync!(WebRenderBundle); impl_send_sync!(WebSurface); impl_send_sync!(WebSurfaceOutputDetail); impl_send_sync!(WebQueueWriteBuffer); impl_send_sync!(WebBufferMappedRange); crate::cmp::impl_eq_ord_hash_proxy!(ContextWebGpu => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebAdapter => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebDevice => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebQueue => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebShaderModule => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebBindGroupLayout => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebBindGroup => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebTextureView => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebSampler => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebBuffer => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebTexture => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebExternalTexture => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebBlas => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebTlas => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebQuerySet => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebPipelineLayout => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebRenderPipeline => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebComputePipeline => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebPipelineCache => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebCommandEncoder => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebComputePassEncoder => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebRenderPassEncoder => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebCommandBuffer => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebRenderBundleEncoder => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebRenderBundle => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebSurface => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebSurfaceOutputDetail => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebQueueWriteBuffer => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebBufferMappedRange => .ident); impl dispatch::InstanceInterface for ContextWebGpu { fn new(desc: crate::InstanceDescriptor) -> Self where Self: Sized, { let Ok(gpu) = get_browser_gpu_property() else { panic!( "Accessing the GPU is only supported on the main thread or from a dedicated worker" ); }; ContextWebGpu { gpu, requested_backends: desc.backends, ident: crate::cmp::Identifier::create(), } } unsafe fn create_surface( &self, target: crate::SurfaceTargetUnsafe, ) -> Result { match target { SurfaceTargetUnsafe::RawHandle { raw_display_handle: _, raw_window_handle, } => { let canvas_element: web_sys::HtmlCanvasElement = match raw_window_handle { raw_window_handle::RawWindowHandle::Web(handle) => { let canvas_node: wasm_bindgen::JsValue = web_sys::window() .and_then(|win| win.document()) .and_then(|doc| { doc.query_selector_all(&format!( "[data-raw-handle=\"{}\"]", handle.id )) .ok() }) .and_then(|nodes| nodes.get(0)) .expect("expected to find single canvas") .into(); canvas_node.into() } raw_window_handle::RawWindowHandle::WebCanvas(handle) => { let value: &JsValue = unsafe { handle.obj.cast().as_ref() }; value.clone().unchecked_into() } raw_window_handle::RawWindowHandle::WebOffscreenCanvas(handle) => { let value: &JsValue = unsafe { handle.obj.cast().as_ref() }; let canvas: web_sys::OffscreenCanvas = value.clone().unchecked_into(); let context_result = canvas.get_context("webgpu"); return self.create_surface_from_context( Canvas::Offscreen(canvas), context_result, ); } _ => panic!("expected valid handle for canvas"), }; let context_result = canvas_element.get_context("webgpu"); self.create_surface_from_context(Canvas::Canvas(canvas_element), context_result) } } } fn request_adapter( &self, options: &crate::RequestAdapterOptions<'_, '_>, ) -> Pin> { let requested_backends = self.requested_backends; //TODO: support this check, return `None` if the flag is not set. // It's not trivial, since we need the Future logic to have this check, // and currently the Future here has no room for extra parameter `backends`. if !(requested_backends.contains(wgt::Backends::BROWSER_WEBGPU)) { return Box::pin(core::future::ready(Err( wgt::RequestAdapterError::NotFound { active_backends: Backends::BROWSER_WEBGPU, requested_backends, // TODO: supported_backends should also include wgpu-core-based backends, // if they were compiled in. supported_backends: Backends::BROWSER_WEBGPU, no_fallback_backends: Backends::default(), no_adapter_backends: Backends::default(), incompatible_surface_backends: Backends::default(), }, ))); } let mapped_options = webgpu_sys::GpuRequestAdapterOptions::new(); let mapped_power_preference = match options.power_preference { wgt::PowerPreference::None => None, wgt::PowerPreference::LowPower => Some(webgpu_sys::GpuPowerPreference::LowPower), wgt::PowerPreference::HighPerformance => { Some(webgpu_sys::GpuPowerPreference::HighPerformance) } }; if let Some(mapped_pref) = mapped_power_preference { mapped_options.set_power_preference(mapped_pref); } if let Some(gpu) = &self.gpu { let adapter_promise = gpu.request_adapter_with_options(&mapped_options); Box::pin(MakeSendFuture::new( wasm_bindgen_futures::JsFuture::from(adapter_promise), move |result| future_request_adapter(result, requested_backends), )) } else { // Gpu is undefined; WebGPU is not supported in this browser. // Treat this exactly like requestAdapter() returned null. Box::pin(core::future::ready(Err(request_adapter_null_error( requested_backends, )))) } } fn enumerate_adapters( &self, _backends: crate::Backends, ) -> Pin> { let future = self.request_adapter(&crate::RequestAdapterOptions::default()); let enumerate_future = async move { let adapter = future.await; match adapter { Ok(a) => vec![a], Err(_) => vec![], } }; Box::pin(enumerate_future) } fn poll_all_devices(&self, _force_wait: bool) -> bool { // Devices are automatically polled. true } #[cfg(feature = "wgsl")] fn wgsl_language_features(&self) -> crate::WgslLanguageFeatures { let mut wgsl_language_features = crate::WgslLanguageFeatures::empty(); if let Some(gpu) = &self.gpu { gpu.wgsl_language_features() .keys() .into_iter() .map(|wlf| wlf.expect("`WgslLanguageFeatures` elements should be valid")) .map(|wlf| { wlf.as_string() .expect("`WgslLanguageFeatures` should be string set") }) .filter_map(|wlf| match wlf.as_str() { "readonly_and_readwrite_storage_textures" => { Some(crate::WgslLanguageFeatures::ReadOnlyAndReadWriteStorageTextures) } "packed_4x8_integer_dot_product" => { Some(crate::WgslLanguageFeatures::Packed4x8IntegerDotProduct) } "unrestricted_pointer_parameters" => { Some(crate::WgslLanguageFeatures::UnrestrictedPointerParameters) } "pointer_composite_access" => { Some(crate::WgslLanguageFeatures::PointerCompositeAccess) } _ => None, }) .for_each(|wlf| { wgsl_language_features |= wlf; }) } wgsl_language_features } } impl Drop for ContextWebGpu { fn drop(&mut self) { // no-op } } impl dispatch::AdapterInterface for WebAdapter { fn request_device( &self, desc: &crate::DeviceDescriptor<'_>, ) -> Pin> { if !matches!(desc.trace, wgt::Trace::Off) { log::warn!("The `trace` parameter is not supported on the WebGPU backend."); } let mapped_desc = webgpu_sys::GpuDeviceDescriptor::new(); let required_limits = map_js_sys_limits(&desc.required_limits); mapped_desc.set_required_limits(&required_limits); let required_features = FEATURES_MAPPING .iter() .copied() .flat_map(|(flag, value)| { if desc.required_features.contains(flag) { Some(JsValue::from(value)) } else { None } }) .collect::(); mapped_desc.set_required_features(&required_features); if let Some(label) = desc.label { mapped_desc.set_label(label); } let device_promise = self.inner.request_device_with_descriptor(&mapped_desc); Box::pin(MakeSendFuture::new( wasm_bindgen_futures::JsFuture::from(device_promise), future_request_device, )) } fn is_surface_supported(&self, _surface: &dispatch::DispatchSurface) -> bool { // All surfaces are inherently supported. true } fn features(&self) -> crate::Features { map_wgt_features(self.inner.features()) } fn limits(&self) -> crate::Limits { map_wgt_limits(self.inner.limits()) } fn downlevel_capabilities(&self) -> crate::DownlevelCapabilities { // WebGPU is assumed to be fully compliant crate::DownlevelCapabilities::default() } fn get_info(&self) -> crate::AdapterInfo { map_adapter_info(&self.inner.info()) } fn get_texture_format_features( &self, format: crate::TextureFormat, ) -> crate::TextureFormatFeatures { format.guaranteed_format_features(dispatch::AdapterInterface::features(self)) } fn get_presentation_timestamp(&self) -> crate::PresentationTimestamp { crate::PresentationTimestamp::INVALID_TIMESTAMP } fn cooperative_matrix_properties(&self) -> Vec { Vec::new() } } impl Drop for WebAdapter { fn drop(&mut self) { // no-op } } impl dispatch::DeviceInterface for WebDevice { fn features(&self) -> crate::Features { map_wgt_features(self.inner.features()) } fn limits(&self) -> crate::Limits { map_wgt_limits(self.inner.limits()) } fn adapter_info(&self) -> crate::AdapterInfo { map_adapter_info(&self.inner.adapter_info()) } fn create_shader_module( &self, desc: crate::ShaderModuleDescriptor<'_>, _shader_runtime_checks: crate::ShaderRuntimeChecks, ) -> dispatch::DispatchShaderModule { let shader_module_result = match desc.source { #[cfg(feature = "spirv")] crate::ShaderSource::SpirV(ref spv) => { use naga::front; let options = naga::front::spv::Options { adjust_coordinate_space: false, strict_capabilities: true, block_ctx_dump_prefix: None, }; let spv_parser = front::spv::Frontend::new(spv.iter().cloned(), &options); spv_parser .parse() .map_err(|inner| { crate::CompilationInfo::from(naga::error::ShaderError { source: String::new(), label: desc.label.map(|s| s.to_string()), inner: Box::new(inner), }) }) .and_then(|spv_module| { validate_transformed_shader_module(&spv_module, "", &desc).map(|v| { ( v, WebShaderCompilationInfo::Transformed { compilation_info: crate::CompilationInfo { messages: vec![] }, }, ) }) }) } #[cfg(feature = "glsl")] crate::ShaderSource::Glsl { ref shader, stage, defines, } => { use naga::front; // Parse the given shader code and store its representation. let options = front::glsl::Options { stage, defines: defines .iter() .map(|&(key, value)| (String::from(key), String::from(value))) .collect(), }; let mut parser = front::glsl::Frontend::default(); parser .parse(&options, shader) .map_err(|inner| { crate::CompilationInfo::from(naga::error::ShaderError { source: shader.to_string(), label: desc.label.map(|s| s.to_string()), inner: Box::new(inner), }) }) .and_then(|glsl_module| { validate_transformed_shader_module(&glsl_module, shader, &desc).map(|v| { ( v, WebShaderCompilationInfo::Transformed { compilation_info: crate::CompilationInfo { messages: vec![] }, }, ) }) }) } #[cfg(feature = "wgsl")] crate::ShaderSource::Wgsl(ref code) => { let shader_module = webgpu_sys::GpuShaderModuleDescriptor::new(code); Ok(( shader_module, WebShaderCompilationInfo::Wgsl { source: code.to_string(), }, )) } #[cfg(feature = "naga-ir")] crate::ShaderSource::Naga(ref module) => { validate_transformed_shader_module(module, "", &desc).map(|v| { ( v, WebShaderCompilationInfo::Transformed { compilation_info: crate::CompilationInfo { messages: vec![] }, }, ) }) } crate::ShaderSource::Dummy(_) => { panic!("found `ShaderSource::Dummy`") } }; #[cfg(naga)] fn validate_transformed_shader_module( module: &naga::Module, source: &str, desc: &crate::ShaderModuleDescriptor<'_>, ) -> Result { use naga::{back, valid}; let mut validator = valid::Validator::new(valid::ValidationFlags::all(), valid::Capabilities::all()); let module_info = validator.validate(module).map_err(|err| { crate::CompilationInfo::from(naga::error::ShaderError { source: source.to_string(), label: desc.label.map(|s| s.to_string()), inner: Box::new(err), }) })?; let writer_flags = naga::back::wgsl::WriterFlags::empty(); let wgsl_text = back::wgsl::write_string(module, &module_info, writer_flags).unwrap(); Ok(webgpu_sys::GpuShaderModuleDescriptor::new( wgsl_text.as_str(), )) } let (descriptor, compilation_info) = match shader_module_result { Ok(v) => v, Err(compilation_info) => ( webgpu_sys::GpuShaderModuleDescriptor::new(""), WebShaderCompilationInfo::Transformed { compilation_info }, ), }; if let Some(label) = desc.label { descriptor.set_label(label); } WebShaderModule { module: self.inner.create_shader_module(&descriptor), compilation_info, ident: crate::cmp::Identifier::create(), } .into() } unsafe fn create_shader_module_passthrough( &self, desc: &crate::ShaderModuleDescriptorPassthrough<'_>, ) -> dispatch::DispatchShaderModule { let shader_module_result = if let Some(ref code) = desc.wgsl { let shader_module = webgpu_sys::GpuShaderModuleDescriptor::new(code); Ok(( shader_module, WebShaderCompilationInfo::Wgsl { source: code.to_string(), }, )) } else { Err(crate::CompilationInfo { messages: vec![crate::CompilationMessage { message: "Passthrough shader not compiled for WGSL on WebGPU backend (wgpu error)" .to_string(), location: None, message_type: crate::CompilationMessageType::Error, }], }) }; let (descriptor, compilation_info) = match shader_module_result { Ok(v) => v, Err(compilation_info) => ( webgpu_sys::GpuShaderModuleDescriptor::new(""), WebShaderCompilationInfo::Transformed { compilation_info }, ), }; if let Some(label) = desc.label { descriptor.set_label(label); } WebShaderModule { module: self.inner.create_shader_module(&descriptor), compilation_info, ident: crate::cmp::Identifier::create(), } .into() } fn create_bind_group_layout( &self, desc: &crate::BindGroupLayoutDescriptor<'_>, ) -> dispatch::DispatchBindGroupLayout { let mapped_bindings = desc .entries .iter() .map(|bind| { let mapped_entry = webgpu_sys::GpuBindGroupLayoutEntry::new(bind.binding, bind.visibility.bits()); match bind.ty { wgt::BindingType::Buffer { ty, has_dynamic_offset, min_binding_size, } => { let buffer = webgpu_sys::GpuBufferBindingLayout::new(); buffer.set_has_dynamic_offset(has_dynamic_offset); if let Some(size) = min_binding_size { buffer.set_min_binding_size(size.get() as f64); } buffer.set_type(match ty { wgt::BufferBindingType::Uniform => { webgpu_sys::GpuBufferBindingType::Uniform } wgt::BufferBindingType::Storage { read_only: false } => { webgpu_sys::GpuBufferBindingType::Storage } wgt::BufferBindingType::Storage { read_only: true } => { webgpu_sys::GpuBufferBindingType::ReadOnlyStorage } }); mapped_entry.set_buffer(&buffer); } wgt::BindingType::Sampler(ty) => { let sampler = webgpu_sys::GpuSamplerBindingLayout::new(); sampler.set_type(match ty { wgt::SamplerBindingType::NonFiltering => { webgpu_sys::GpuSamplerBindingType::NonFiltering } wgt::SamplerBindingType::Filtering => { webgpu_sys::GpuSamplerBindingType::Filtering } wgt::SamplerBindingType::Comparison => { webgpu_sys::GpuSamplerBindingType::Comparison } }); mapped_entry.set_sampler(&sampler); } wgt::BindingType::Texture { multisampled, sample_type, view_dimension, } => { let texture = webgpu_sys::GpuTextureBindingLayout::new(); texture.set_multisampled(multisampled); texture.set_sample_type(map_texture_component_type(sample_type)); texture.set_view_dimension(map_texture_view_dimension(view_dimension)); mapped_entry.set_texture(&texture); } wgt::BindingType::StorageTexture { access, format, view_dimension, } => { let mapped_access = match access { wgt::StorageTextureAccess::WriteOnly => { webgpu_sys::GpuStorageTextureAccess::WriteOnly } wgt::StorageTextureAccess::ReadOnly => { webgpu_sys::GpuStorageTextureAccess::ReadOnly } wgt::StorageTextureAccess::ReadWrite => { webgpu_sys::GpuStorageTextureAccess::ReadWrite } wgt::StorageTextureAccess::Atomic => { // Validated out by `BindGroupLayoutEntryError::StorageTextureAtomic` unreachable!() } }; let storage_texture = webgpu_sys::GpuStorageTextureBindingLayout::new( map_texture_format(format), ); storage_texture.set_access(mapped_access); storage_texture .set_view_dimension(map_texture_view_dimension(view_dimension)); mapped_entry.set_storage_texture(&storage_texture); } wgt::BindingType::AccelerationStructure { .. } => todo!(), wgt::BindingType::ExternalTexture => { mapped_entry.set_external_texture( &webgpu_sys::GpuExternalTextureBindingLayout::new(), ); } } mapped_entry }) .collect::(); let mapped_desc = webgpu_sys::GpuBindGroupLayoutDescriptor::new(&mapped_bindings); if let Some(label) = desc.label { mapped_desc.set_label(label); } let bind_group_layout = self.inner.create_bind_group_layout(&mapped_desc).unwrap(); WebBindGroupLayout { inner: bind_group_layout, ident: crate::cmp::Identifier::create(), } .into() } fn create_bind_group( &self, desc: &crate::BindGroupDescriptor<'_>, ) -> dispatch::DispatchBindGroup { let mapped_entries = desc .entries .iter() .map(|binding| { let mapped_resource = match binding.resource { crate::BindingResource::Buffer(crate::BufferBinding { buffer, offset, size, }) => { let buffer = buffer.inner.as_webgpu(); let mapped_buffer_binding = webgpu_sys::GpuBufferBinding::new(&buffer.inner); mapped_buffer_binding.set_offset(offset as f64); if let Some(s) = size { mapped_buffer_binding.set_size(s.get() as f64); } JsValue::from(mapped_buffer_binding) } crate::BindingResource::BufferArray(..) => { panic!("Web backend does not support arrays of buffers") } crate::BindingResource::Sampler(sampler) => { let sampler = &sampler.inner.as_webgpu().inner; JsValue::from(sampler) } crate::BindingResource::SamplerArray(..) => { panic!("Web backend does not support arrays of samplers") } crate::BindingResource::TextureView(texture_view) => { let texture_view = &texture_view.inner.as_webgpu().inner; JsValue::from(texture_view) } crate::BindingResource::TextureViewArray(..) => { panic!("Web backend does not support BINDING_INDEXING extension") } crate::BindingResource::AccelerationStructure(_) => { unimplemented!("Raytracing not implemented for web") } crate::BindingResource::AccelerationStructureArray(_) => { unimplemented!("Raytracing not implemented for web") } crate::BindingResource::ExternalTexture(_) => { unimplemented!("ExternalTexture not implemented for web") } }; webgpu_sys::GpuBindGroupEntry::new(binding.binding, &mapped_resource) }) .collect::(); let bgl = &desc.layout.inner.as_webgpu().inner; let mapped_desc = webgpu_sys::GpuBindGroupDescriptor::new(&mapped_entries, bgl); if let Some(label) = desc.label { mapped_desc.set_label(label); } let bind_group = self.inner.create_bind_group(&mapped_desc); WebBindGroup { inner: bind_group, ident: crate::cmp::Identifier::create(), } .into() } fn create_pipeline_layout( &self, desc: &crate::PipelineLayoutDescriptor<'_>, ) -> dispatch::DispatchPipelineLayout { let null = wasm_bindgen::JsValue::NULL; let temp_layouts = desc .bind_group_layouts .iter() .map(|bgl| match bgl { Some(bgl) => bgl.inner.as_webgpu().inner.as_ref(), None => &null, }) .collect::(); let mapped_desc = webgpu_sys::GpuPipelineLayoutDescriptor::new(&temp_layouts); if let Some(label) = desc.label { mapped_desc.set_label(label); } let pipeline_layout = self.inner.create_pipeline_layout(&mapped_desc); WebPipelineLayout { inner: pipeline_layout, ident: crate::cmp::Identifier::create(), } .into() } fn create_render_pipeline( &self, desc: &crate::RenderPipelineDescriptor<'_>, ) -> dispatch::DispatchRenderPipeline { let module = desc.vertex.module.inner.as_webgpu(); let mapped_vertex_state = webgpu_sys::GpuVertexState::new(&module.module); insert_constants_map( &mapped_vertex_state, desc.vertex.compilation_options.constants, ); if let Some(ep) = desc.vertex.entry_point { mapped_vertex_state.set_entry_point(ep); } let buffers = desc .vertex .buffers .iter() .map(|vbuf| { let mapped_attributes = vbuf .attributes .iter() .map(|attr| { webgpu_sys::GpuVertexAttribute::new( map_vertex_format(attr.format), attr.offset as f64, attr.shader_location, ) }) .collect::(); let mapped_vbuf = webgpu_sys::GpuVertexBufferLayout::new( vbuf.array_stride as f64, &mapped_attributes, ); mapped_vbuf.set_step_mode(map_vertex_step_mode(vbuf.step_mode)); mapped_vbuf }) .collect::(); mapped_vertex_state.set_buffers(&buffers); let auto_layout = wasm_bindgen::JsValue::from(webgpu_sys::GpuAutoLayoutMode::Auto); let mapped_desc = webgpu_sys::GpuRenderPipelineDescriptor::new( &match desc.layout { Some(layout) => { let layout = &layout.inner.as_webgpu().inner; JsValue::from(layout) } None => auto_layout, }, &mapped_vertex_state, ); if let Some(label) = desc.label { mapped_desc.set_label(label); } if let Some(ref depth_stencil) = desc.depth_stencil { mapped_desc.set_depth_stencil(&map_depth_stencil_state(depth_stencil)); } if let Some(ref frag) = desc.fragment { let targets = frag .targets .iter() .map(|target| match target { Some(target) => { let mapped_format = map_texture_format(target.format); let mapped_color_state = webgpu_sys::GpuColorTargetState::new(mapped_format); if let Some(ref bs) = target.blend { let alpha = map_blend_component(&bs.alpha); let color = map_blend_component(&bs.color); let mapped_blend_state = webgpu_sys::GpuBlendState::new(&alpha, &color); mapped_color_state.set_blend(&mapped_blend_state); } mapped_color_state.set_write_mask(target.write_mask.bits()); wasm_bindgen::JsValue::from(mapped_color_state) } None => wasm_bindgen::JsValue::null(), }) .collect::(); let module = frag.module.inner.as_webgpu(); let mapped_fragment_desc = webgpu_sys::GpuFragmentState::new(&module.module, &targets); insert_constants_map(&mapped_fragment_desc, frag.compilation_options.constants); if let Some(ep) = frag.entry_point { mapped_fragment_desc.set_entry_point(ep); } mapped_desc.set_fragment(&mapped_fragment_desc); } let mapped_multisample = webgpu_sys::GpuMultisampleState::new(); mapped_multisample.set_count(desc.multisample.count); mapped_multisample.set_mask(desc.multisample.mask as u32); mapped_multisample .set_alpha_to_coverage_enabled(desc.multisample.alpha_to_coverage_enabled); mapped_desc.set_multisample(&mapped_multisample); let mapped_primitive = map_primitive_state(&desc.primitive); mapped_desc.set_primitive(&mapped_primitive); let render_pipeline = self.inner.create_render_pipeline(&mapped_desc).unwrap(); WebRenderPipeline { inner: render_pipeline, ident: crate::cmp::Identifier::create(), } .into() } fn create_mesh_pipeline( &self, _desc: &crate::MeshPipelineDescriptor<'_>, ) -> dispatch::DispatchRenderPipeline { panic!("MESH_SHADER feature must be enabled to call create_mesh_pipeline") } fn create_compute_pipeline( &self, desc: &crate::ComputePipelineDescriptor<'_>, ) -> dispatch::DispatchComputePipeline { let shader_module = desc.module.inner.as_webgpu(); let mapped_compute_stage = webgpu_sys::GpuProgrammableStage::new(&shader_module.module); insert_constants_map(&mapped_compute_stage, desc.compilation_options.constants); if let Some(ep) = desc.entry_point { mapped_compute_stage.set_entry_point(ep); } let auto_layout = wasm_bindgen::JsValue::from(webgpu_sys::GpuAutoLayoutMode::Auto); let mapped_desc = webgpu_sys::GpuComputePipelineDescriptor::new( &match desc.layout { Some(layout) => { let layout = &layout.inner.as_webgpu().inner; JsValue::from(layout) } None => auto_layout, }, &mapped_compute_stage, ); if let Some(label) = desc.label { mapped_desc.set_label(label); } let compute_pipeline = self.inner.create_compute_pipeline(&mapped_desc); WebComputePipeline { inner: compute_pipeline, ident: crate::cmp::Identifier::create(), } .into() } unsafe fn create_pipeline_cache( &self, _desc: &crate::PipelineCacheDescriptor<'_>, ) -> dispatch::DispatchPipelineCache { WebPipelineCache { ident: crate::cmp::Identifier::create(), } .into() } fn create_buffer(&self, desc: &crate::BufferDescriptor<'_>) -> dispatch::DispatchBuffer { let mapped_desc = webgpu_sys::GpuBufferDescriptor::new(desc.size as f64, desc.usage.bits()); mapped_desc.set_mapped_at_creation(desc.mapped_at_creation); if let Some(label) = desc.label { mapped_desc.set_label(label); } WebBuffer::new(self.inner.create_buffer(&mapped_desc).unwrap(), desc).into() } fn create_texture(&self, desc: &crate::TextureDescriptor<'_>) -> dispatch::DispatchTexture { let mapped_desc = webgpu_sys::GpuTextureDescriptor::new( map_texture_format(desc.format), &map_extent_3d(desc.size), (desc.usage - crate::TextureUsages::TRANSIENT).bits(), ); if let Some(label) = desc.label { mapped_desc.set_label(label); } mapped_desc.set_dimension(map_texture_dimension(desc.dimension)); mapped_desc.set_mip_level_count(desc.mip_level_count); mapped_desc.set_sample_count(desc.sample_count); let mapped_view_formats = desc .view_formats .iter() .map(|format| JsValue::from(map_texture_format(*format))) .collect::(); mapped_desc.set_view_formats(&mapped_view_formats); let texture = self.inner.create_texture(&mapped_desc).unwrap(); WebTexture { inner: texture, ident: crate::cmp::Identifier::create(), } .into() } fn create_external_texture( &self, _desc: &crate::ExternalTextureDescriptor<'_>, _planes: &[&crate::TextureView], ) -> dispatch::DispatchExternalTexture { unimplemented!("ExternalTexture not implemented for web"); } fn create_blas( &self, _desc: &crate::CreateBlasDescriptor<'_>, _sizes: crate::BlasGeometrySizeDescriptors, ) -> (Option, dispatch::DispatchBlas) { unimplemented!("Raytracing not implemented for web"); } fn create_tlas(&self, _desc: &crate::CreateTlasDescriptor<'_>) -> dispatch::DispatchTlas { unimplemented!("Raytracing not implemented for web"); } fn create_sampler(&self, desc: &crate::SamplerDescriptor<'_>) -> dispatch::DispatchSampler { let mapped_desc = webgpu_sys::GpuSamplerDescriptor::new(); mapped_desc.set_address_mode_u(map_address_mode(desc.address_mode_u)); mapped_desc.set_address_mode_v(map_address_mode(desc.address_mode_v)); mapped_desc.set_address_mode_w(map_address_mode(desc.address_mode_w)); if let Some(compare) = desc.compare { mapped_desc.set_compare(map_compare_function(compare)); } mapped_desc.set_lod_max_clamp(desc.lod_max_clamp); mapped_desc.set_lod_min_clamp(desc.lod_min_clamp); mapped_desc.set_mag_filter(map_filter_mode(desc.mag_filter)); mapped_desc.set_min_filter(map_filter_mode(desc.min_filter)); mapped_desc.set_mipmap_filter(map_mipmap_filter_mode(desc.mipmap_filter)); mapped_desc.set_max_anisotropy(desc.anisotropy_clamp); if let Some(label) = desc.label { mapped_desc.set_label(label); } let sampler = self.inner.create_sampler_with_descriptor(&mapped_desc); WebSampler { inner: sampler, ident: crate::cmp::Identifier::create(), } .into() } fn create_query_set(&self, desc: &crate::QuerySetDescriptor<'_>) -> dispatch::DispatchQuerySet { let ty = match desc.ty { wgt::QueryType::Occlusion => webgpu_sys::GpuQueryType::Occlusion, wgt::QueryType::Timestamp => webgpu_sys::GpuQueryType::Timestamp, wgt::QueryType::PipelineStatistics(_) => unreachable!(), }; let mapped_desc = webgpu_sys::GpuQuerySetDescriptor::new(desc.count, ty); if let Some(label) = desc.label { mapped_desc.set_label(label); } let query_set = self.inner.create_query_set(&mapped_desc).unwrap(); WebQuerySet { inner: query_set, ident: crate::cmp::Identifier::create(), } .into() } fn create_command_encoder( &self, desc: &crate::CommandEncoderDescriptor<'_>, ) -> dispatch::DispatchCommandEncoder { let mapped_desc = webgpu_sys::GpuCommandEncoderDescriptor::new(); if let Some(label) = desc.label { mapped_desc.set_label(label); } let command_encoder = self .inner .create_command_encoder_with_descriptor(&mapped_desc); WebCommandEncoder { inner: command_encoder, ident: crate::cmp::Identifier::create(), } .into() } fn create_render_bundle_encoder( &self, desc: &crate::RenderBundleEncoderDescriptor<'_>, ) -> dispatch::DispatchRenderBundleEncoder { let mapped_color_formats = desc .color_formats .iter() .map(|cf| match cf { Some(cf) => wasm_bindgen::JsValue::from(map_texture_format(*cf)), None => wasm_bindgen::JsValue::null(), }) .collect::(); let mapped_desc = webgpu_sys::GpuRenderBundleEncoderDescriptor::new(&mapped_color_formats); if let Some(label) = desc.label { mapped_desc.set_label(label); } if let Some(ds) = desc.depth_stencil { mapped_desc.set_depth_stencil_format(map_texture_format(ds.format)); mapped_desc.set_depth_read_only(ds.depth_read_only); mapped_desc.set_stencil_read_only(ds.stencil_read_only); } mapped_desc.set_sample_count(desc.sample_count); let render_bundle_encoder = self .inner .create_render_bundle_encoder(&mapped_desc) .unwrap(); WebRenderBundleEncoder { inner: render_bundle_encoder, ident: crate::cmp::Identifier::create(), } .into() } fn set_device_lost_callback(&self, device_lost_callback: dispatch::BoxDeviceLostCallback) { let closure = Closure::once(move |info: JsValue| { let info = info.dyn_into::().unwrap(); device_lost_callback( match info.reason() { webgpu_sys::GpuDeviceLostReason::Destroyed => { crate::DeviceLostReason::Destroyed } webgpu_sys::GpuDeviceLostReason::Unknown => crate::DeviceLostReason::Unknown, _ => crate::DeviceLostReason::Unknown, }, info.message(), ); }); let _ = self.inner.lost().then(&closure); // Release memory management of this closure from Rust to the JS GC. // TODO: This will leak if weak references is not supported. closure.forget(); } fn on_uncaptured_error(&self, handler: Arc) { let f = Closure::wrap(Box::new(move |event: webgpu_sys::GpuUncapturedErrorEvent| { let error = crate::Error::from_js(event.error().value_of()); handler(error); }) as Box); self.inner .set_onuncapturederror(Some(f.as_ref().unchecked_ref())); // Release memory management of this closure from Rust to the JS GC. // TODO: This will leak if weak references is not supported. f.forget(); } fn push_error_scope(&self, filter: crate::ErrorFilter) -> u32 { let index = self.error_scope_count.get(); self.error_scope_count.set( index .checked_add(1) .expect("Greater than 2^32 nested error scopes"), ); self.inner.push_error_scope(match filter { crate::ErrorFilter::OutOfMemory => webgpu_sys::GpuErrorFilter::OutOfMemory, crate::ErrorFilter::Validation => webgpu_sys::GpuErrorFilter::Validation, crate::ErrorFilter::Internal => webgpu_sys::GpuErrorFilter::Internal, }); index } fn pop_error_scope(&self, index: u32) -> Pin> { let current_scope_count = self.error_scope_count.get(); let is_panicking = crate::util::is_panicking(); if current_scope_count == 0 && !is_panicking { panic!("Mismatched pop_error_scope call: no error scope for this thread. Error scopes are thread-local."); } if index + 1 != current_scope_count && !is_panicking { panic!( "Mismatched pop_error_scope call: error scopes must be popped in reverse order." ); } // Decrement the error scope count. We've asserted that the current // size is `index + 1` above. self.error_scope_count.set(index); let error_promise = self.inner.pop_error_scope(); Box::pin(MakeSendFuture::new( wasm_bindgen_futures::JsFuture::from(error_promise), future_pop_error_scope, )) } unsafe fn start_graphics_debugger_capture(&self) { // No capturing api in webgpu } unsafe fn stop_graphics_debugger_capture(&self) { // No capturing api in webgpu } fn poll(&self, _poll_type: wgt::PollType) -> Result { // Device is polled automatically Ok(crate::PollStatus::QueueEmpty) } fn get_internal_counters(&self) -> crate::InternalCounters { crate::InternalCounters::default() } fn generate_allocator_report(&self) -> Option { None } fn destroy(&self) { self.inner.destroy(); } } impl Drop for WebDevice { fn drop(&mut self) { // no-op } } impl dispatch::QueueInterface for WebQueue { fn write_buffer( &self, buffer: &dispatch::DispatchBuffer, offset: crate::BufferAddress, data: &[u8], ) { let buffer = buffer.as_webgpu(); self.inner .write_buffer_with_f64_and_u8_slice_and_f64_and_f64( &buffer.inner, offset as f64, data, 0f64, data.len() as f64, ) .unwrap(); } fn create_staging_buffer( &self, size: crate::BufferSize, ) -> Option { Some( WebQueueWriteBuffer { inner: vec![0; size.get() as usize].into_boxed_slice(), ident: crate::cmp::Identifier::create(), } .into(), ) } fn validate_write_buffer( &self, buffer: &dispatch::DispatchBuffer, offset: wgt::BufferAddress, size: wgt::BufferSize, ) -> Option<()> { let buffer = buffer.as_webgpu(); let usage = wgt::BufferUsages::from_bits_truncate(buffer.inner.usage()); // TODO: actually send this down the error scope if !usage.contains(wgt::BufferUsages::COPY_DST) { log::error!("Destination buffer is missing the `COPY_DST` usage flag"); return None; } let write_size = u64::from(size); if !write_size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) { log::error!("Copy size {size} does not respect `COPY_BUFFER_ALIGNMENT`"); return None; } if !offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) { log::error!( "Buffer offset {offset} is not aligned to block size or `COPY_BUFFER_ALIGNMENT`" ); return None; } if write_size + offset > buffer.inner.size() as u64 { log::error!("copy of {}..{} would end up overrunning the bounds of the destination buffer of size {}", offset, offset + write_size, buffer.inner.size()); return None; } Some(()) } fn write_staging_buffer( &self, buffer: &dispatch::DispatchBuffer, offset: crate::BufferAddress, staging_buffer: &dispatch::DispatchQueueWriteBuffer, ) { let staging_buffer = staging_buffer.as_webgpu(); dispatch::QueueInterface::write_buffer(self, buffer, offset, &staging_buffer.inner) } fn write_texture( &self, texture: crate::TexelCopyTextureInfo<'_>, data: &[u8], data_layout: crate::TexelCopyBufferLayout, size: crate::Extent3d, ) { let mapped_data_layout = webgpu_sys::GpuTexelCopyBufferLayout::new(); if let Some(bytes_per_row) = data_layout.bytes_per_row { mapped_data_layout.set_bytes_per_row(bytes_per_row); } if let Some(rows_per_image) = data_layout.rows_per_image { mapped_data_layout.set_rows_per_image(rows_per_image); } mapped_data_layout.set_offset(data_layout.offset as f64); self.inner .write_texture_with_u8_slice_and_gpu_extent_3d_dict( &map_texture_copy_view(texture), data, &mapped_data_layout, &map_extent_3d(size), ) .unwrap(); } fn copy_external_image_to_texture( &self, source: &crate::CopyExternalImageSourceInfo, dest: crate::CopyExternalImageDestInfo<&crate::api::Texture>, size: crate::Extent3d, ) { self.inner .copy_external_image_to_texture_with_gpu_extent_3d_dict( &map_external_texture_copy_view(source), &map_tagged_texture_copy_view(dest), &map_extent_3d(size), ) .unwrap(); } fn submit( &self, command_buffers: &mut dyn Iterator, ) -> u64 { let temp_command_buffers = command_buffers.collect::>(); let array = temp_command_buffers .iter() .map(|buffer| &buffer.as_webgpu().inner) .collect::(); self.inner.submit(&array); 0 } fn get_timestamp_period(&self) -> f32 { // Timestamp values are always in nanoseconds, see https://gpuweb.github.io/gpuweb/#timestamp 1.0 } fn on_submitted_work_done(&self, callback: dispatch::BoxSubmittedWorkDoneCallback) { let promise = self.inner.on_submitted_work_done(); wasm_bindgen_futures::spawn_local(async move { match wasm_bindgen_futures::JsFuture::from(promise).await { Ok(_) => callback(), Err(error) => { log::error!("on_submitted_work_done promise failed: {error:?}"); callback(); } } }); } fn compact_blas( &self, _blas: &dispatch::DispatchBlas, ) -> (Option, dispatch::DispatchBlas) { unimplemented!("Raytracing not implemented for web") } } impl Drop for WebQueue { fn drop(&mut self) { // no-op } } impl dispatch::ShaderModuleInterface for WebShaderModule { fn get_compilation_info(&self) -> Pin> { let compilation_info_promise = self.module.get_compilation_info(); let map_future = Box::new({ let compilation_info = self.compilation_info.clone(); move |result| future_compilation_info(result, &compilation_info) }); Box::pin(MakeSendFuture::new( wasm_bindgen_futures::JsFuture::from(compilation_info_promise), map_future, )) } } impl Drop for WebShaderModule { fn drop(&mut self) { // no-op } } impl dispatch::BindGroupLayoutInterface for WebBindGroupLayout {} impl Drop for WebBindGroupLayout { fn drop(&mut self) { // no-op } } impl dispatch::BindGroupInterface for WebBindGroup {} impl Drop for WebBindGroup { fn drop(&mut self) { // no-op } } impl dispatch::TextureViewInterface for WebTextureView {} impl Drop for WebTextureView { fn drop(&mut self) { // no-op } } impl dispatch::SamplerInterface for WebSampler {} impl Drop for WebSampler { fn drop(&mut self) { // no-op } } impl dispatch::BufferInterface for WebBuffer { fn map_async( &self, mode: crate::MapMode, range: Range, callback: dispatch::BufferMapCallback, ) { let map_promise = self.inner.map_async_with_f64_and_f64( map_map_mode(mode), range.start as f64, (range.end - range.start) as f64, ); self.set_mapped_range(range); register_then_closures(&map_promise, callback, Ok(()), Err(crate::BufferAsyncError)); } fn get_mapped_range( &self, sub_range: Range, ) -> dispatch::DispatchBufferMappedRange { let actual_mapping = self.get_mapped_range(sub_range); WebBufferMappedRange { actual_mapping, temporary_mapping: OnceCell::new(), temporary_mapping_modified: false, ident: crate::cmp::Identifier::create(), } .into() } fn unmap(&self) { self.inner.unmap(); self.mapping.borrow_mut().mapped_buffer = None; } fn destroy(&self) { self.inner.destroy(); } } impl Drop for WebBuffer { fn drop(&mut self) { // no-op } } impl dispatch::TextureInterface for WebTexture { fn create_view( &self, desc: &crate::TextureViewDescriptor<'_>, ) -> dispatch::DispatchTextureView { let mapped = webgpu_sys::GpuTextureViewDescriptor::new(); if let Some(dim) = desc.dimension { mapped.set_dimension(map_texture_view_dimension(dim)); } if let Some(format) = desc.format { mapped.set_format(map_texture_format(format)); } mapped.set_aspect(map_texture_aspect(desc.aspect)); mapped.set_base_array_layer(desc.base_array_layer); if let Some(count) = desc.array_layer_count { mapped.set_array_layer_count(count); } mapped.set_base_mip_level(desc.base_mip_level); if let Some(count) = desc.mip_level_count { mapped.set_mip_level_count(count); } if let Some(label) = desc.label { mapped.set_label(label); } mapped.set_usage(desc.usage.unwrap_or(wgt::TextureUsages::empty()).bits()); let view = self.inner.create_view_with_descriptor(&mapped).unwrap(); WebTextureView { inner: view, ident: crate::cmp::Identifier::create(), } .into() } fn destroy(&self) { self.inner.destroy(); } } impl Drop for WebTexture { fn drop(&mut self) { // no-op } } impl dispatch::ExternalTextureInterface for WebExternalTexture { fn destroy(&self) { unimplemented!("ExternalTexture not implemented for web"); } } impl Drop for WebExternalTexture { fn drop(&mut self) { unimplemented!("ExternalTexture not implemented for web"); } } impl dispatch::BlasInterface for WebBlas { fn prepare_compact_async(&self, _callback: BlasCompactCallback) { unimplemented!("Raytracing not implemented for web") } fn ready_for_compaction(&self) -> bool { unimplemented!("Raytracing not implemented for web") } } impl Drop for WebBlas { fn drop(&mut self) { // no-op } } impl dispatch::TlasInterface for WebTlas {} impl Drop for WebTlas { fn drop(&mut self) { // no-op } } impl dispatch::QuerySetInterface for WebQuerySet {} impl Drop for WebQuerySet { fn drop(&mut self) { // no-op } } impl dispatch::PipelineLayoutInterface for WebPipelineLayout {} impl Drop for WebPipelineLayout { fn drop(&mut self) { // no-op } } impl dispatch::RenderPipelineInterface for WebRenderPipeline { fn get_bind_group_layout(&self, index: u32) -> dispatch::DispatchBindGroupLayout { let bind_group_layout = self.inner.get_bind_group_layout(index); WebBindGroupLayout { inner: bind_group_layout, ident: crate::cmp::Identifier::create(), } .into() } } impl Drop for WebRenderPipeline { fn drop(&mut self) { // no-op } } impl dispatch::ComputePipelineInterface for WebComputePipeline { fn get_bind_group_layout(&self, index: u32) -> dispatch::DispatchBindGroupLayout { let bind_group_layout = self.inner.get_bind_group_layout(index); WebBindGroupLayout { inner: bind_group_layout, ident: crate::cmp::Identifier::create(), } .into() } } impl Drop for WebComputePipeline { fn drop(&mut self) { // no-op } } impl dispatch::CommandEncoderInterface for WebCommandEncoder { fn copy_buffer_to_buffer( &self, source: &dispatch::DispatchBuffer, source_offset: crate::BufferAddress, destination: &dispatch::DispatchBuffer, destination_offset: crate::BufferAddress, copy_size: Option, ) { let source = source.as_webgpu(); let destination = destination.as_webgpu(); if let Some(size) = copy_size { self.inner .copy_buffer_to_buffer_with_f64_and_f64_and_f64( &source.inner, source_offset as f64, &destination.inner, destination_offset as f64, size as f64, ) .unwrap(); } else { self.inner .copy_buffer_to_buffer_with_f64_and_f64( &source.inner, source_offset as f64, &destination.inner, destination_offset as f64, ) .unwrap(); } } fn copy_buffer_to_texture( &self, source: crate::TexelCopyBufferInfo<'_>, destination: crate::TexelCopyTextureInfo<'_>, copy_size: crate::Extent3d, ) { self.inner .copy_buffer_to_texture_with_gpu_extent_3d_dict( &map_buffer_copy_view(source), &map_texture_copy_view(destination), &map_extent_3d(copy_size), ) .unwrap(); } fn copy_texture_to_buffer( &self, source: crate::TexelCopyTextureInfo<'_>, destination: crate::TexelCopyBufferInfo<'_>, copy_size: crate::Extent3d, ) { self.inner .copy_texture_to_buffer_with_gpu_extent_3d_dict( &map_texture_copy_view(source), &map_buffer_copy_view(destination), &map_extent_3d(copy_size), ) .unwrap(); } fn copy_texture_to_texture( &self, source: crate::TexelCopyTextureInfo<'_>, destination: crate::TexelCopyTextureInfo<'_>, copy_size: crate::Extent3d, ) { self.inner .copy_texture_to_texture_with_gpu_extent_3d_dict( &map_texture_copy_view(source), &map_texture_copy_view(destination), &map_extent_3d(copy_size), ) .unwrap(); } fn begin_compute_pass( &self, desc: &crate::ComputePassDescriptor<'_>, ) -> dispatch::DispatchComputePass { let mapped_desc = webgpu_sys::GpuComputePassDescriptor::new(); if let Some(label) = desc.label { mapped_desc.set_label(label); } if let Some(ref timestamp_writes) = desc.timestamp_writes { let query_set = timestamp_writes.query_set.inner.as_webgpu(); let writes = webgpu_sys::GpuComputePassTimestampWrites::new(&query_set.inner); if let Some(index) = timestamp_writes.beginning_of_pass_write_index { writes.set_beginning_of_pass_write_index(index); } if let Some(index) = timestamp_writes.end_of_pass_write_index { writes.set_end_of_pass_write_index(index); } mapped_desc.set_timestamp_writes(&writes); } let compute_pass = self.inner.begin_compute_pass_with_descriptor(&mapped_desc); WebComputePassEncoder { inner: compute_pass, ident: crate::cmp::Identifier::create(), } .into() } fn begin_render_pass( &self, desc: &crate::RenderPassDescriptor<'_>, ) -> dispatch::DispatchRenderPass { let mapped_color_attachments = desc .color_attachments .iter() .map(|attachment| match attachment { Some(ca) => { let mut clear_value: Option = None; let load_value = match ca.ops.load { crate::LoadOp::Clear(color) => { clear_value = Some(wasm_bindgen::JsValue::from(map_color(color))); webgpu_sys::GpuLoadOp::Clear } crate::LoadOp::DontCare(_token) => { // WebGPU can't safely have a ClearOp::DontCare, so we clear to black // which is ideal for most GPUs. clear_value = Some(wasm_bindgen::JsValue::from(map_color(crate::Color::BLACK))); webgpu_sys::GpuLoadOp::Clear } crate::LoadOp::Load => webgpu_sys::GpuLoadOp::Load, }; let view = &ca.view.inner.as_webgpu().inner; let mapped_color_attachment = webgpu_sys::GpuRenderPassColorAttachment::new( load_value, map_store_op(ca.ops.store), view, ); if let Some(cv) = clear_value { mapped_color_attachment.set_clear_value(&cv); } if let Some(rt) = ca.resolve_target { let resolve_target_view = &rt.inner.as_webgpu().inner; mapped_color_attachment.set_resolve_target(resolve_target_view); } mapped_color_attachment.set_store_op(map_store_op(ca.ops.store)); wasm_bindgen::JsValue::from(mapped_color_attachment) } None => wasm_bindgen::JsValue::null(), }) .collect::(); let mapped_desc = webgpu_sys::GpuRenderPassDescriptor::new(&mapped_color_attachments); if let Some(label) = desc.label { mapped_desc.set_label(label); } if let Some(dsa) = &desc.depth_stencil_attachment { let depth_stencil_attachment = &dsa.view.inner.as_webgpu().inner; let mapped_depth_stencil_attachment = webgpu_sys::GpuRenderPassDepthStencilAttachment::new(depth_stencil_attachment); if let Some(ref ops) = dsa.depth_ops { let load_op = match ops.load { crate::LoadOp::Clear(v) => { mapped_depth_stencil_attachment.set_depth_clear_value(v); webgpu_sys::GpuLoadOp::Clear } crate::LoadOp::DontCare(_token) => { // WebGPU can't safely have a ClearOp::DontCare, so we clear to 1.0 mapped_depth_stencil_attachment.set_depth_clear_value(1.0); webgpu_sys::GpuLoadOp::Clear } crate::LoadOp::Load => webgpu_sys::GpuLoadOp::Load, }; mapped_depth_stencil_attachment.set_depth_load_op(load_op); mapped_depth_stencil_attachment.set_depth_store_op(map_store_op(ops.store)); } mapped_depth_stencil_attachment.set_depth_read_only(dsa.depth_ops.is_none()); if let Some(ref ops) = dsa.stencil_ops { let load_op = match ops.load { crate::LoadOp::Clear(v) => { mapped_depth_stencil_attachment.set_stencil_clear_value(v); webgpu_sys::GpuLoadOp::Clear } crate::LoadOp::DontCare(_token) => { // WebGPU can't safely have a ClearOp::DontCare, so we clear to 0 mapped_depth_stencil_attachment.set_stencil_clear_value(0); webgpu_sys::GpuLoadOp::Clear } crate::LoadOp::Load => webgpu_sys::GpuLoadOp::Load, }; mapped_depth_stencil_attachment.set_stencil_load_op(load_op); mapped_depth_stencil_attachment.set_stencil_store_op(map_store_op(ops.store)); } mapped_depth_stencil_attachment.set_stencil_read_only(dsa.stencil_ops.is_none()); mapped_desc.set_depth_stencil_attachment(&mapped_depth_stencil_attachment); } if let Some(ref timestamp_writes) = desc.timestamp_writes { let query_set = ×tamp_writes.query_set.inner.as_webgpu().inner; let writes = webgpu_sys::GpuRenderPassTimestampWrites::new(query_set); if let Some(index) = timestamp_writes.beginning_of_pass_write_index { writes.set_beginning_of_pass_write_index(index); } if let Some(index) = timestamp_writes.end_of_pass_write_index { writes.set_end_of_pass_write_index(index); } mapped_desc.set_timestamp_writes(&writes); } let render_pass = self.inner.begin_render_pass(&mapped_desc).unwrap(); WebRenderPassEncoder { inner: render_pass, ident: crate::cmp::Identifier::create(), } .into() } fn finish(&mut self) -> dispatch::DispatchCommandBuffer { let label = self.inner.label(); let buffer = if label.is_empty() { self.inner.finish() } else { let mapped_desc = webgpu_sys::GpuCommandBufferDescriptor::new(); mapped_desc.set_label(&label); self.inner.finish_with_descriptor(&mapped_desc) }; WebCommandBuffer { inner: buffer, ident: crate::cmp::Identifier::create(), } .into() } fn clear_texture( &self, _texture: &dispatch::DispatchTexture, _subresource_range: &crate::ImageSubresourceRange, ) { unimplemented!("clear_texture is not yet implemented"); } fn clear_buffer( &self, buffer: &dispatch::DispatchBuffer, offset: crate::BufferAddress, size: Option, ) { let buffer = buffer.as_webgpu(); match size { Some(size) => { self.inner .clear_buffer_with_f64_and_f64(&buffer.inner, offset as f64, size as f64) } None => self .inner .clear_buffer_with_f64(&buffer.inner, offset as f64), } } fn insert_debug_marker(&self, label: &str) { self.inner.insert_debug_marker(label) } fn push_debug_group(&self, group_label: &str) { self.inner.push_debug_group(group_label) } fn pop_debug_group(&self) { self.inner.pop_debug_group() } fn write_timestamp(&self, _query_set: &dispatch::DispatchQuerySet, _query_index: u32) { // Not available on WebGPU. // This was part of the spec originally but got removed, see https://github.com/gpuweb/gpuweb/pull/4370 panic!("TIMESTAMP_QUERY_INSIDE_ENCODERS feature must be enabled to call write_timestamp on a command encoder.") } fn resolve_query_set( &self, query_set: &dispatch::DispatchQuerySet, first_query: u32, query_count: u32, destination: &dispatch::DispatchBuffer, destination_offset: crate::BufferAddress, ) { let query_set = &query_set.as_webgpu().inner; let destination = &destination.as_webgpu().inner; self.inner.resolve_query_set_with_u32( query_set, first_query, query_count, destination, destination_offset as u32, ); } fn mark_acceleration_structures_built<'a>( &self, _blas: &mut dyn Iterator, _tlas: &mut dyn Iterator, ) { unimplemented!("Raytracing not implemented for web"); } fn build_acceleration_structures<'a>( &self, _blas: &mut dyn Iterator>, _tlas: &mut dyn Iterator, ) { unimplemented!("Raytracing not implemented for web"); } fn transition_resources<'a>( &mut self, _buffer_transitions: &mut dyn Iterator< Item = wgt::BufferTransition<&'a dispatch::DispatchBuffer>, >, _texture_transitions: &mut dyn Iterator< Item = wgt::TextureTransition<&'a dispatch::DispatchTexture>, >, ) { // no-op } } impl Drop for WebCommandEncoder { fn drop(&mut self) { // no-op } } impl dispatch::PipelineCacheInterface for WebPipelineCache { fn get_data(&self) -> Option> { todo!() } } impl Drop for WebPipelineCache { fn drop(&mut self) { // no-op } } impl dispatch::ComputePassInterface for WebComputePassEncoder { fn set_pipeline(&mut self, pipeline: &dispatch::DispatchComputePipeline) { let pipeline = &pipeline.as_webgpu().inner; self.inner.set_pipeline(pipeline); } fn set_bind_group( &mut self, index: u32, bind_group: Option<&dispatch::DispatchBindGroup>, offsets: &[crate::DynamicOffset], ) { let bind_group = bind_group.map(|bind_group| &bind_group.as_webgpu().inner); if offsets.is_empty() { self.inner.set_bind_group(index, bind_group); } else { self.inner .set_bind_group_with_u32_slice_and_f64_and_dynamic_offsets_data_length( index, bind_group, offsets, 0f64, offsets.len() as u32, ) .unwrap(); } } fn set_immediates(&mut self, _offset: u32, _data: &[u8]) { panic!("IMMEDIATES feature must be enabled to call set_immediates") } fn insert_debug_marker(&mut self, label: &str) { self.inner.insert_debug_marker(label); } fn push_debug_group(&mut self, group_label: &str) { self.inner.push_debug_group(group_label); } fn pop_debug_group(&mut self) { self.inner.pop_debug_group(); } fn write_timestamp(&mut self, _query_set: &dispatch::DispatchQuerySet, _query_index: u32) { panic!("TIMESTAMP_QUERY_INSIDE_PASSES feature must be enabled to call write_timestamp in a compute pass.") } fn begin_pipeline_statistics_query( &mut self, _query_set: &dispatch::DispatchQuerySet, _query_index: u32, ) { // Not available in gecko yet } fn end_pipeline_statistics_query(&mut self) { // Not available in gecko yet } fn dispatch_workgroups(&mut self, x: u32, y: u32, z: u32) { self.inner .dispatch_workgroups_with_workgroup_count_y_and_workgroup_count_z(x, y, z); } fn dispatch_workgroups_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, ) { let indirect_buffer = indirect_buffer.as_webgpu(); self.inner .dispatch_workgroups_indirect_with_f64(&indirect_buffer.inner, indirect_offset as f64); } } impl Drop for WebComputePassEncoder { fn drop(&mut self) { self.inner.end(); } } impl dispatch::RenderPassInterface for WebRenderPassEncoder { fn set_pipeline(&mut self, pipeline: &dispatch::DispatchRenderPipeline) { let pipeline = &pipeline.as_webgpu().inner; self.inner.set_pipeline(pipeline); } fn set_bind_group( &mut self, index: u32, bind_group: Option<&dispatch::DispatchBindGroup>, offsets: &[crate::DynamicOffset], ) { let bind_group = bind_group.map(|bind_group| &bind_group.as_webgpu().inner); if offsets.is_empty() { self.inner.set_bind_group(index, bind_group); } else { self.inner .set_bind_group_with_u32_slice_and_f64_and_dynamic_offsets_data_length( index, bind_group, offsets, 0f64, offsets.len() as u32, ) .unwrap(); } } fn set_index_buffer( &mut self, buffer: &dispatch::DispatchBuffer, index_format: crate::IndexFormat, offset: crate::BufferAddress, size: Option, ) { let buffer = buffer.as_webgpu(); let index_format = map_index_format(index_format); if let Some(size) = size { self.inner.set_index_buffer_with_f64_and_f64( &buffer.inner, index_format, offset as f64, size.get() as f64, ); } else { self.inner .set_index_buffer_with_f64(&buffer.inner, index_format, offset as f64); } } fn set_vertex_buffer( &mut self, slot: u32, buffer: &dispatch::DispatchBuffer, offset: crate::BufferAddress, size: Option, ) { let buffer = buffer.as_webgpu(); if let Some(size) = size { self.inner.set_vertex_buffer_with_f64_and_f64( slot, Some(&buffer.inner), offset as f64, size.get() as f64, ); } else { self.inner .set_vertex_buffer_with_f64(slot, Some(&buffer.inner), offset as f64); } } fn set_immediates(&mut self, _offset: u32, _data: &[u8]) { panic!("IMMEDIATES feature must be enabled to call set_immediates") } fn set_blend_constant(&mut self, color: crate::Color) { self.inner .set_blend_constant_with_gpu_color_dict(&map_color(color)) .unwrap(); } fn set_scissor_rect(&mut self, x: u32, y: u32, width: u32, height: u32) { self.inner.set_scissor_rect(x, y, width, height); } fn set_viewport( &mut self, x: f32, y: f32, width: f32, height: f32, min_depth: f32, max_depth: f32, ) { self.inner .set_viewport(x, y, width, height, min_depth, max_depth); } fn set_stencil_reference(&mut self, reference: u32) { self.inner.set_stencil_reference(reference); } fn draw(&mut self, vertices: Range, instances: Range) { self.inner .draw_with_instance_count_and_first_vertex_and_first_instance( vertices.end - vertices.start, instances.end - instances.start, vertices.start, instances.start, ); } fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range) { self.inner .draw_indexed_with_instance_count_and_first_index_and_base_vertex_and_first_instance( indices.end - indices.start, instances.end - instances.start, indices.start, base_vertex, instances.start, ) } fn draw_mesh_tasks(&mut self, _group_count_x: u32, _group_count_y: u32, _group_count_z: u32) { panic!("MESH_SHADER feature must be enabled to call draw_mesh_tasks") } fn draw_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, ) { let buffer = indirect_buffer.as_webgpu(); self.inner .draw_indirect_with_f64(&buffer.inner, indirect_offset as f64); } fn draw_indexed_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, ) { let buffer = indirect_buffer.as_webgpu(); self.inner .draw_indexed_indirect_with_f64(&buffer.inner, indirect_offset as f64); } fn draw_mesh_tasks_indirect( &mut self, _indirect_buffer: &dispatch::DispatchBuffer, _indirect_offset: crate::BufferAddress, ) { panic!("MESH_SHADER feature must be enabled to call draw_mesh_tasks_indirect") } fn multi_draw_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, count: u32, ) { let buffer = indirect_buffer.as_webgpu(); for i in 0..count { let offset = indirect_offset + i as crate::BufferAddress * 16; self.inner .draw_indirect_with_f64(&buffer.inner, offset as f64); } } fn multi_draw_indexed_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, count: u32, ) { let buffer = indirect_buffer.as_webgpu(); for i in 0..count { let offset = indirect_offset + i as crate::BufferAddress * 20; self.inner .draw_indexed_indirect_with_f64(&buffer.inner, offset as f64); } } fn multi_draw_mesh_tasks_indirect( &mut self, _indirect_buffer: &dispatch::DispatchBuffer, _indirect_offset: crate::BufferAddress, _count: u32, ) { panic!("MESH_SHADER feature must be enabled to call multi_draw_mesh_tasks_indirect") } fn multi_draw_indirect_count( &mut self, _indirect_buffer: &dispatch::DispatchBuffer, _indirect_offset: crate::BufferAddress, _count_buffer: &dispatch::DispatchBuffer, _count_buffer_offset: crate::BufferAddress, _max_count: u32, ) { panic!( "MULTI_DRAW_INDIRECT_COUNT feature must be enabled to call multi_draw_indirect_count" ) } fn multi_draw_indexed_indirect_count( &mut self, _indirect_buffer: &dispatch::DispatchBuffer, _indirect_offset: crate::BufferAddress, _count_buffer: &dispatch::DispatchBuffer, _count_buffer_offset: crate::BufferAddress, _max_count: u32, ) { panic!("MULTI_DRAW_INDIRECT_COUNT feature must be enabled to call multi_draw_indexed_indirect_count") } fn multi_draw_mesh_tasks_indirect_count( &mut self, _indirect_buffer: &dispatch::DispatchBuffer, _indirect_offset: crate::BufferAddress, _count_buffer: &dispatch::DispatchBuffer, _count_buffer_offset: crate::BufferAddress, _max_count: u32, ) { panic!("MESH_SHADER feature must be enabled to call multi_draw_mesh_tasks_indirect_count") } fn insert_debug_marker(&mut self, label: &str) { self.inner.insert_debug_marker(label); } fn push_debug_group(&mut self, group_label: &str) { self.inner.push_debug_group(group_label); } fn pop_debug_group(&mut self) { self.inner.pop_debug_group(); } fn write_timestamp(&mut self, _query_set: &dispatch::DispatchQuerySet, _query_index: u32) { panic!("TIMESTAMP_QUERY_INSIDE_PASSES feature must be enabled to call write_timestamp in a render pass.") } fn begin_occlusion_query(&mut self, query_index: u32) { self.inner.begin_occlusion_query(query_index); } fn end_occlusion_query(&mut self) { self.inner.end_occlusion_query(); } fn begin_pipeline_statistics_query( &mut self, _query_set: &dispatch::DispatchQuerySet, _query_index: u32, ) { // Removed from WebGPU in https://github.com/gpuweb/gpuweb/pull/2296 } fn end_pipeline_statistics_query(&mut self) { // Removed from WebGPU https://github.com/gpuweb/gpuweb/pull/2296 } fn execute_bundles( &mut self, render_bundles: &mut dyn Iterator, ) { let mapped = render_bundles .map(|bundle| &bundle.as_webgpu().inner) .collect::(); self.inner.execute_bundles(&mapped); } } impl Drop for WebRenderPassEncoder { fn drop(&mut self) { self.inner.end(); } } impl dispatch::CommandBufferInterface for WebCommandBuffer {} impl Drop for WebCommandBuffer { fn drop(&mut self) { // no-op } } impl dispatch::RenderBundleEncoderInterface for WebRenderBundleEncoder { fn set_pipeline(&mut self, pipeline: &dispatch::DispatchRenderPipeline) { let pipeline = &pipeline.as_webgpu().inner; self.inner.set_pipeline(pipeline); } fn set_bind_group( &mut self, index: u32, bind_group: Option<&dispatch::DispatchBindGroup>, offsets: &[crate::DynamicOffset], ) { let bind_group = bind_group.map(|bind_group| &bind_group.as_webgpu().inner); if offsets.is_empty() { self.inner.set_bind_group(index, bind_group); } else { self.inner .set_bind_group_with_u32_slice_and_f64_and_dynamic_offsets_data_length( index, bind_group, offsets, 0f64, offsets.len() as u32, ) .unwrap(); } } fn set_index_buffer( &mut self, buffer: &dispatch::DispatchBuffer, index_format: crate::IndexFormat, offset: crate::BufferAddress, size: Option, ) { let buffer = buffer.as_webgpu(); let index_format = map_index_format(index_format); if let Some(size) = size { self.inner.set_index_buffer_with_f64_and_f64( &buffer.inner, index_format, offset as f64, size.get() as f64, ); } else { self.inner .set_index_buffer_with_f64(&buffer.inner, index_format, offset as f64); } } fn set_vertex_buffer( &mut self, slot: u32, buffer: &dispatch::DispatchBuffer, offset: crate::BufferAddress, size: Option, ) { let buffer = buffer.as_webgpu(); if let Some(size) = size { self.inner.set_vertex_buffer_with_f64_and_f64( slot, Some(&buffer.inner), offset as f64, size.get() as f64, ); } else { self.inner .set_vertex_buffer_with_f64(slot, Some(&buffer.inner), offset as f64); } } fn set_immediates(&mut self, _offset: u32, _data: &[u8]) { panic!("IMMEDIATES feature must be enabled to call set_immediates") } fn draw(&mut self, vertices: Range, instances: Range) { self.inner .draw_with_instance_count_and_first_vertex_and_first_instance( vertices.end - vertices.start, instances.end - instances.start, vertices.start, instances.start, ); } fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range) { self.inner .draw_indexed_with_instance_count_and_first_index_and_base_vertex_and_first_instance( indices.end - indices.start, instances.end - instances.start, indices.start, base_vertex, instances.start, ) } fn draw_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, ) { let buffer = indirect_buffer.as_webgpu(); self.inner .draw_indirect_with_f64(&buffer.inner, indirect_offset as f64); } fn draw_indexed_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, ) { let buffer = indirect_buffer.as_webgpu(); self.inner .draw_indexed_indirect_with_f64(&buffer.inner, indirect_offset as f64); } fn finish(self, desc: &crate::RenderBundleDescriptor<'_>) -> dispatch::DispatchRenderBundle where Self: Sized, { let bundle = match desc.label { Some(label) => { let mapped_desc = webgpu_sys::GpuRenderBundleDescriptor::new(); mapped_desc.set_label(label); self.inner.finish_with_descriptor(&mapped_desc) } None => self.inner.finish(), }; WebRenderBundle { inner: bundle, ident: crate::cmp::Identifier::create(), } .into() } } impl Drop for WebRenderBundleEncoder { fn drop(&mut self) { // no-op } } impl dispatch::RenderBundleInterface for WebRenderBundle {} impl Drop for WebRenderBundle { fn drop(&mut self) { // no-op } } impl dispatch::SurfaceInterface for WebSurface { fn get_capabilities(&self, _adapter: &dispatch::DispatchAdapter) -> wgt::SurfaceCapabilities { let mut formats = vec![ wgt::TextureFormat::Rgba8Unorm, wgt::TextureFormat::Bgra8Unorm, wgt::TextureFormat::Rgba16Float, ]; let mut mapped_formats = formats.iter().map(|format| map_texture_format(*format)); // Preferred canvas format will only be either "rgba8unorm" or "bgra8unorm". // https://www.w3.org/TR/webgpu/#dom-gpu-getpreferredcanvasformat let preferred_format = self .gpu .as_ref() .expect("Caller could not have created an adapter if gpu is undefined.") .get_preferred_canvas_format(); if let Some(index) = mapped_formats.position(|format| format == preferred_format) { formats.swap(0, index); } wgt::SurfaceCapabilities { // https://gpuweb.github.io/gpuweb/#supported-context-formats formats, // Doesn't really have meaning on the web. present_modes: vec![wgt::PresentMode::Fifo], alpha_modes: vec![wgt::CompositeAlphaMode::Opaque], // Statically set to RENDER_ATTACHMENT for now. See https://gpuweb.github.io/gpuweb/#dom-gpucanvasconfiguration-usage usages: wgt::TextureUsages::RENDER_ATTACHMENT, } } fn configure(&self, device: &dispatch::DispatchDevice, config: &crate::SurfaceConfiguration) { let device = device.as_webgpu(); match self.canvas { Canvas::Canvas(ref canvas) => { canvas.set_width(config.width); canvas.set_height(config.height); } Canvas::Offscreen(ref canvas) => { canvas.set_width(config.width); canvas.set_height(config.height); } } if let wgt::PresentMode::Mailbox | wgt::PresentMode::Immediate = config.present_mode { panic!("Only FIFO/Auto* is supported on web"); } if let wgt::CompositeAlphaMode::PostMultiplied | wgt::CompositeAlphaMode::Inherit = config.alpha_mode { panic!("Only Opaque/Auto or PreMultiplied alpha mode are supported on web"); } let alpha_mode = match config.alpha_mode { wgt::CompositeAlphaMode::PreMultiplied => webgpu_sys::GpuCanvasAlphaMode::Premultiplied, _ => webgpu_sys::GpuCanvasAlphaMode::Opaque, }; let mapped = webgpu_sys::GpuCanvasConfiguration::new( &device.inner, map_texture_format(config.format), ); mapped.set_usage(config.usage.bits()); mapped.set_alpha_mode(alpha_mode); let mapped_view_formats = config .view_formats .iter() .map(|format| JsValue::from(map_texture_format(*format))) .collect::(); mapped.set_view_formats(&mapped_view_formats); self.context.configure(&mapped).unwrap(); } fn get_current_texture( &self, ) -> ( Option, crate::SurfaceStatus, dispatch::DispatchSurfaceOutputDetail, ) { let surface_texture = self.context.get_current_texture().unwrap(); let web_surface_texture = WebTexture { inner: surface_texture, ident: crate::cmp::Identifier::create(), }; ( Some(web_surface_texture.into()), crate::SurfaceStatus::Good, WebSurfaceOutputDetail { ident: crate::cmp::Identifier::create(), } .into(), ) } } impl Drop for WebSurface { fn drop(&mut self) { // no-op } } impl dispatch::SurfaceOutputDetailInterface for WebSurfaceOutputDetail { fn present(&self) { // Swapchain is presented automatically on the web. } fn texture_discard(&self) { // Can't really discard the texture on the web. } } impl Drop for WebSurfaceOutputDetail { fn drop(&mut self) { // no-op } } impl WebBufferMappedRange { fn get_temporary_mapping(&self) -> &[u8] { self.temporary_mapping .get_or_init(|| self.actual_mapping.to_vec()) } } impl dispatch::BufferMappedRangeInterface for WebBufferMappedRange { fn len(&self) -> usize { self.get_temporary_mapping().len() } #[inline] unsafe fn read_slice(&self) -> &[u8] { self.get_temporary_mapping() } #[inline] unsafe fn write_slice(&mut self) -> WriteOnly<'_, [u8]> { self.temporary_mapping_modified = true; self.get_temporary_mapping(); let t: &mut Vec = self.temporary_mapping.get_mut().unwrap(); WriteOnly::from_mut(t) } #[inline] fn as_uint8array(&self) -> &js_sys::Uint8Array { &self.actual_mapping } } impl Drop for WebBufferMappedRange { fn drop(&mut self) { if !self.temporary_mapping_modified { // For efficiency, skip the copy if it is not needed. // This is also how we skip copying back on *read-only* mappings. return; } // Copy from the temporary mapping back into the array buffer that was // originally provided by the browser let temporary_mapping_slice = self.temporary_mapping.get().unwrap().as_slice(); unsafe { // Note: no allocations can happen between `view` and `set`, or this // will break self.actual_mapping .set(&js_sys::Uint8Array::view(temporary_mapping_slice), 0); } } } impl dispatch::QueueWriteBufferInterface for WebQueueWriteBuffer { #[inline] fn len(&self) -> usize { self.inner.len() } #[inline] unsafe fn write_slice(&mut self) -> WriteOnly<'_, [u8]> { WriteOnly::from_mut(&mut *self.inner) } } impl Drop for WebQueueWriteBuffer { fn drop(&mut self) { // The api struct calls write_staging_buffer // no-op } } /// Adds the constants map to the given pipeline descriptor if the map is nonempty. /// Panics if the map cannot be set. /// /// This function is necessary because the constants array is not currently /// exposed by `wasm-bindgen`. See the following issues for details: /// - [gfx-rs/wgpu#5688](https://github.com/gfx-rs/wgpu/pull/5688) /// - [rustwasm/wasm-bindgen#3587](https://github.com/rustwasm/wasm-bindgen/issues/3587) fn insert_constants_map(target: &JsValue, map: &[(&str, f64)]) { if !map.is_empty() { js_sys::Reflect::set( target, &JsValue::from_str("constants"), &hashmap_to_jsvalue(map), ) .expect("Setting the values in a Javascript pipeline descriptor should never fail"); } } /// Converts a hashmap to a Javascript object. fn hashmap_to_jsvalue(map: &[(&str, f64)]) -> JsValue { let obj = js_sys::Object::new(); for &(key, v) in map.iter() { js_sys::Reflect::set(&obj, &JsValue::from_str(key), &JsValue::from_f64(v)) .expect("Setting the values in a Javascript map should never fail"); } JsValue::from(obj) } ================================================ FILE: wgpu/src/backend/wgpu_core/thread_id.rs ================================================ //! Implementation of thread IDs for error scope tracking. //! //! Supports both std and no_std environments, though //! the no_std implementation is a stub that does not //! actually distinguish between threads. #[cfg(feature = "std")] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ThreadId(std::thread::ThreadId); #[cfg(feature = "std")] impl ThreadId { pub fn current() -> Self { ThreadId(std::thread::current().id()) } } #[cfg(not(feature = "std"))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ThreadId(()); #[cfg(not(feature = "std"))] impl ThreadId { pub fn current() -> Self { // A simple stub implementation for non-std environments. On // no_std but multithreaded platforms, this will work, but // make error scope global rather than thread-local. ThreadId(()) } } ================================================ FILE: wgpu/src/backend/wgpu_core.rs ================================================ use alloc::{ borrow::Cow::{self, Borrowed}, boxed::Box, format, string::{String, ToString as _}, sync::Arc, vec, vec::Vec, }; use core::{ error::Error, fmt, future::ready, ops::{Deref, Range}, pin::Pin, ptr::NonNull, slice, }; use hashbrown::HashMap; use arrayvec::ArrayVec; use smallvec::SmallVec; use wgc::{ command::bundle_ffi::*, error::ContextErrorSource, pipeline::CreateShaderModuleError, resource::BlasPrepareCompactResult, }; use wgt::{ error::{ErrorType, WebGpuError}, WasmNotSendSync, }; use crate::{ api, dispatch::{self, BlasCompactCallback, BufferMappedRangeInterface}, BindingResource, Blas, BufferBinding, BufferDescriptor, CompilationInfo, CompilationMessage, CompilationMessageType, ErrorSource, Features, Label, LoadOp, MapMode, Operations, ShaderSource, SurfaceTargetUnsafe, TextureDescriptor, Tlas, WriteOnly, }; use crate::{dispatch::DispatchAdapter, util::Mutex}; mod thread_id; #[derive(Clone)] pub struct ContextWgpuCore(Arc); impl Drop for ContextWgpuCore { fn drop(&mut self) { //nothing } } impl fmt::Debug for ContextWgpuCore { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ContextWgpuCore") .field("type", &"Native") .finish() } } impl ContextWgpuCore { pub unsafe fn from_hal_instance(hal_instance: A::Instance) -> Self { Self(unsafe { Arc::new(wgc::global::Global::from_hal_instance::( "wgpu", hal_instance, )) }) } /// # Safety /// /// - The raw instance handle returned must not be manually destroyed. pub unsafe fn instance_as_hal(&self) -> Option<&A::Instance> { unsafe { self.0.instance_as_hal::() } } pub unsafe fn from_core_instance(core_instance: wgc::instance::Instance) -> Self { Self(unsafe { Arc::new(wgc::global::Global::from_instance(core_instance)) }) } #[cfg(wgpu_core)] pub fn enumerate_adapters(&self, backends: wgt::Backends) -> Vec { self.0.enumerate_adapters(backends) } pub unsafe fn create_adapter_from_hal( &self, hal_adapter: hal::ExposedAdapter, ) -> wgc::id::AdapterId { unsafe { self.0.create_adapter_from_hal(hal_adapter.into(), None) } } pub unsafe fn adapter_as_hal( &self, adapter: &CoreAdapter, ) -> Option + WasmNotSendSync> { unsafe { self.0.adapter_as_hal::(adapter.id) } } pub unsafe fn buffer_as_hal( &self, buffer: &CoreBuffer, ) -> Option> { unsafe { self.0.buffer_as_hal::(buffer.id) } } pub unsafe fn create_device_from_hal( &self, adapter: &CoreAdapter, hal_device: hal::OpenDevice, desc: &crate::DeviceDescriptor<'_>, ) -> Result<(CoreDevice, CoreQueue), crate::RequestDeviceError> { let (device_id, queue_id) = unsafe { self.0.create_device_from_hal( adapter.id, hal_device.into(), &desc.map_label(|l| l.map(Borrowed)), None, None, ) }?; let error_sink = Arc::new(Mutex::new(ErrorSinkRaw::new())); let device = CoreDevice { context: self.clone(), id: device_id, error_sink: error_sink.clone(), features: desc.required_features, }; let queue = CoreQueue { context: self.clone(), id: queue_id, error_sink, }; Ok((device, queue)) } pub unsafe fn create_texture_from_hal( &self, hal_texture: A::Texture, device: &CoreDevice, desc: &TextureDescriptor<'_>, ) -> CoreTexture { let descriptor = desc.map_label_and_view_formats(|l| l.map(Borrowed), |v| v.to_vec()); let (id, error) = unsafe { self.0 .create_texture_from_hal(Box::new(hal_texture), device.id, &descriptor, None) }; if let Some(cause) = error { self.handle_error( &device.error_sink, cause, desc.label, "Device::create_texture_from_hal", ); } CoreTexture { context: self.clone(), id, error_sink: Arc::clone(&device.error_sink), } } /// # Safety /// /// - `hal_buffer` must be created from `device`. /// - `hal_buffer` must be created respecting `desc` /// - `hal_buffer` must be initialized /// - `hal_buffer` must not have zero size. pub unsafe fn create_buffer_from_hal( &self, hal_buffer: A::Buffer, device: &CoreDevice, desc: &BufferDescriptor<'_>, ) -> CoreBuffer { let (id, error) = unsafe { self.0.create_buffer_from_hal::( hal_buffer, device.id, &desc.map_label(|l| l.map(Borrowed)), None, ) }; if let Some(cause) = error { self.handle_error( &device.error_sink, cause, desc.label, "Device::create_buffer_from_hal", ); } CoreBuffer { context: self.clone(), id, error_sink: Arc::clone(&device.error_sink), } } pub unsafe fn device_as_hal( &self, device: &CoreDevice, ) -> Option> { unsafe { self.0.device_as_hal::(device.id) } } pub unsafe fn surface_as_hal( &self, surface: &CoreSurface, ) -> Option> { unsafe { self.0.surface_as_hal::(surface.id) } } pub unsafe fn texture_as_hal( &self, texture: &CoreTexture, ) -> Option> { unsafe { self.0.texture_as_hal::(texture.id) } } pub unsafe fn texture_view_as_hal( &self, texture_view: &CoreTextureView, ) -> Option> { unsafe { self.0.texture_view_as_hal::(texture_view.id) } } /// This method will start the wgpu_core level command recording. pub unsafe fn command_encoder_as_hal_mut< A: hal::Api, F: FnOnce(Option<&mut A::CommandEncoder>) -> R, R, >( &self, command_encoder: &CoreCommandEncoder, hal_command_encoder_callback: F, ) -> R { unsafe { self.0.command_encoder_as_hal_mut::( command_encoder.id, hal_command_encoder_callback, ) } } pub unsafe fn blas_as_hal( &self, blas: &CoreBlas, ) -> Option> { unsafe { self.0.blas_as_hal::(blas.id) } } pub unsafe fn tlas_as_hal( &self, tlas: &CoreTlas, ) -> Option> { unsafe { self.0.tlas_as_hal::(tlas.id) } } pub fn generate_report(&self) -> wgc::global::GlobalReport { self.0.generate_report() } #[cold] #[track_caller] #[inline(never)] fn handle_error_inner( &self, sink_mutex: &Mutex, error_type: ErrorType, source: ContextErrorSource, label: Label<'_>, fn_ident: &'static str, ) { let source: ErrorSource = Box::new(wgc::error::ContextError { fn_ident, source, label: label.unwrap_or_default().to_string(), }); let final_error_handling = { let mut sink = sink_mutex.lock(); let description = || self.format_error(&*source); let error = match error_type { ErrorType::Internal => { let description = description(); crate::Error::Internal { source, description, } } ErrorType::OutOfMemory => crate::Error::OutOfMemory { source }, ErrorType::Validation => { let description = description(); crate::Error::Validation { source, description, } } ErrorType::DeviceLost => return, // will be surfaced via callback }; sink.handle_error_or_return_handler(error) }; if let Some(f) = final_error_handling { // If the user has provided their own `uncaptured_handler` callback, invoke it now, // having released our lock on `sink_mutex`. See the comments on // `handle_error_or_return_handler` for details. f(); } } #[inline] #[track_caller] fn handle_error( &self, sink_mutex: &Mutex, source: impl WebGpuError + WasmNotSendSync + 'static, label: Label<'_>, fn_ident: &'static str, ) { let error_type = source.webgpu_error_type(); self.handle_error_inner(sink_mutex, error_type, Box::new(source), label, fn_ident) } #[inline] #[track_caller] fn handle_error_nolabel( &self, sink_mutex: &Mutex, source: impl WebGpuError + WasmNotSendSync + 'static, fn_ident: &'static str, ) { let error_type = source.webgpu_error_type(); self.handle_error_inner(sink_mutex, error_type, Box::new(source), None, fn_ident) } #[track_caller] #[cold] fn handle_error_fatal( &self, cause: impl Error + WasmNotSendSync + 'static, operation: &'static str, ) -> ! { panic!("Error in {operation}: {f}", f = self.format_error(&cause)); } #[inline(never)] fn format_error(&self, err: &(dyn Error + 'static)) -> String { let mut output = String::new(); let mut level = 1; fn print_tree(output: &mut String, level: &mut usize, e: &(dyn Error + 'static)) { let mut print = |e: &(dyn Error + 'static)| { use core::fmt::Write; writeln!(output, "{}{}", " ".repeat(*level * 2), e).unwrap(); if let Some(e) = e.source() { *level += 1; print_tree(output, level, e); *level -= 1; } }; if let Some(multi) = e.downcast_ref::() { for e in multi.errors() { print(e); } } else { print(e); } } print_tree(&mut output, &mut level, err); format!("Validation Error\n\nCaused by:\n{output}") } pub unsafe fn queue_as_hal( &self, queue: &CoreQueue, ) -> Option + WasmNotSendSync> { unsafe { self.0.queue_as_hal::(queue.id) } } } fn map_buffer_copy_view( view: crate::TexelCopyBufferInfo<'_>, ) -> wgt::TexelCopyBufferInfo { wgt::TexelCopyBufferInfo { buffer: view.buffer.inner.as_core().id, layout: view.layout, } } fn map_texture_copy_view( view: crate::TexelCopyTextureInfo<'_>, ) -> wgt::TexelCopyTextureInfo { wgt::TexelCopyTextureInfo { texture: view.texture.inner.as_core().id, mip_level: view.mip_level, origin: view.origin, aspect: view.aspect, } } #[cfg_attr(not(webgl), expect(unused))] fn map_texture_tagged_copy_view( view: crate::CopyExternalImageDestInfo<&api::Texture>, ) -> wgt::CopyExternalImageDestInfo { wgt::CopyExternalImageDestInfo { texture: view.texture.inner.as_core().id, mip_level: view.mip_level, origin: view.origin, aspect: view.aspect, color_space: view.color_space, premultiplied_alpha: view.premultiplied_alpha, } } fn map_load_op(load: &LoadOp) -> LoadOp> { match *load { LoadOp::Clear(clear_value) => LoadOp::Clear(Some(clear_value)), LoadOp::DontCare(token) => LoadOp::DontCare(token), LoadOp::Load => LoadOp::Load, } } fn map_pass_channel(ops: Option<&Operations>) -> wgc::command::PassChannel> { match ops { Some(&Operations { load, store }) => wgc::command::PassChannel { load_op: Some(map_load_op(&load)), store_op: Some(store), read_only: false, }, None => wgc::command::PassChannel { load_op: None, store_op: None, read_only: true, }, } } #[derive(Debug)] pub struct CoreSurface { pub(crate) context: ContextWgpuCore, id: wgc::id::SurfaceId, /// Configured device is needed to know which backend /// code to execute when acquiring a new frame. configured_device: Mutex>, /// The error sink with which to report errors. /// `None` if the surface has not been configured. error_sink: Mutex>, } #[derive(Debug)] pub struct CoreAdapter { pub(crate) context: ContextWgpuCore, pub(crate) id: wgc::id::AdapterId, } #[derive(Debug)] pub struct CoreDevice { pub(crate) context: ContextWgpuCore, id: wgc::id::DeviceId, error_sink: ErrorSink, features: Features, } #[derive(Debug)] pub struct CoreBuffer { pub(crate) context: ContextWgpuCore, id: wgc::id::BufferId, error_sink: ErrorSink, } #[derive(Debug)] pub struct CoreShaderModule { pub(crate) context: ContextWgpuCore, id: wgc::id::ShaderModuleId, compilation_info: CompilationInfo, } #[derive(Debug)] pub struct CoreBindGroupLayout { pub(crate) context: ContextWgpuCore, id: wgc::id::BindGroupLayoutId, } #[derive(Debug)] pub struct CoreBindGroup { pub(crate) context: ContextWgpuCore, id: wgc::id::BindGroupId, } #[derive(Debug)] pub struct CoreTexture { pub(crate) context: ContextWgpuCore, id: wgc::id::TextureId, error_sink: ErrorSink, } #[derive(Debug)] pub struct CoreTextureView { pub(crate) context: ContextWgpuCore, id: wgc::id::TextureViewId, } #[derive(Debug)] pub struct CoreExternalTexture { pub(crate) context: ContextWgpuCore, id: wgc::id::ExternalTextureId, } #[derive(Debug)] pub struct CoreSampler { pub(crate) context: ContextWgpuCore, id: wgc::id::SamplerId, } #[derive(Debug)] pub struct CoreQuerySet { pub(crate) context: ContextWgpuCore, id: wgc::id::QuerySetId, } #[derive(Debug)] pub struct CorePipelineLayout { pub(crate) context: ContextWgpuCore, id: wgc::id::PipelineLayoutId, } #[derive(Debug)] pub struct CorePipelineCache { pub(crate) context: ContextWgpuCore, id: wgc::id::PipelineCacheId, } #[derive(Debug)] pub struct CoreCommandBuffer { pub(crate) context: ContextWgpuCore, id: wgc::id::CommandBufferId, } #[derive(Debug)] pub struct CoreRenderBundleEncoder { pub(crate) context: ContextWgpuCore, encoder: wgc::command::RenderBundleEncoder, id: crate::cmp::Identifier, } #[derive(Debug)] pub struct CoreRenderBundle { context: ContextWgpuCore, id: wgc::id::RenderBundleId, } #[derive(Debug)] pub struct CoreQueue { pub(crate) context: ContextWgpuCore, id: wgc::id::QueueId, error_sink: ErrorSink, } #[derive(Debug)] pub struct CoreComputePipeline { pub(crate) context: ContextWgpuCore, id: wgc::id::ComputePipelineId, error_sink: ErrorSink, } #[derive(Debug)] pub struct CoreRenderPipeline { pub(crate) context: ContextWgpuCore, id: wgc::id::RenderPipelineId, error_sink: ErrorSink, } #[derive(Debug)] pub struct CoreComputePass { pub(crate) context: ContextWgpuCore, pass: wgc::command::ComputePass, error_sink: ErrorSink, id: crate::cmp::Identifier, } #[derive(Debug)] pub struct CoreRenderPass { pub(crate) context: ContextWgpuCore, pass: wgc::command::RenderPass, error_sink: ErrorSink, id: crate::cmp::Identifier, } #[derive(Debug)] pub struct CoreCommandEncoder { pub(crate) context: ContextWgpuCore, id: wgc::id::CommandEncoderId, error_sink: ErrorSink, } #[derive(Debug)] pub struct CoreBlas { pub(crate) context: ContextWgpuCore, id: wgc::id::BlasId, error_sink: ErrorSink, } #[derive(Debug)] pub struct CoreTlas { pub(crate) context: ContextWgpuCore, id: wgc::id::TlasId, // error_sink: ErrorSink, } #[derive(Debug)] pub struct CoreSurfaceOutputDetail { context: ContextWgpuCore, surface_id: wgc::id::SurfaceId, error_sink: ErrorSink, } type ErrorSink = Arc>; struct ErrorScope { error: Option, filter: crate::ErrorFilter, } struct ErrorSinkRaw { scopes: HashMap>, uncaptured_handler: Option>, } impl ErrorSinkRaw { fn new() -> ErrorSinkRaw { ErrorSinkRaw { scopes: HashMap::new(), uncaptured_handler: None, } } /// Deliver the error to /// /// * the innermost error scope, if any, or /// * the uncaptured error handler, if there is one, or /// * [`default_error_handler()`]. /// /// If a closure is returned, the caller should call it immediately after dropping the /// [`ErrorSink`] mutex guard. This makes sure that the user callback is not called with /// a wgpu mutex held. #[track_caller] #[must_use] fn handle_error_or_return_handler(&mut self, err: crate::Error) -> Option { let filter = match err { crate::Error::OutOfMemory { .. } => crate::ErrorFilter::OutOfMemory, crate::Error::Validation { .. } => crate::ErrorFilter::Validation, crate::Error::Internal { .. } => crate::ErrorFilter::Internal, }; let thread_id = thread_id::ThreadId::current(); let scopes = self.scopes.entry(thread_id).or_default(); match scopes.iter_mut().rev().find(|scope| scope.filter == filter) { Some(scope) => { if scope.error.is_none() { scope.error = Some(err); } None } None => { if let Some(custom_handler) = &self.uncaptured_handler { let custom_handler = Arc::clone(custom_handler); Some(move || (custom_handler)(err)) } else { // direct call preserves #[track_caller] where dyn can't default_error_handler(err) } } } } } impl fmt::Debug for ErrorSinkRaw { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "ErrorSink") } } #[track_caller] fn default_error_handler(err: crate::Error) -> ! { log::error!("Handling wgpu errors as fatal by default"); panic!("wgpu error: {err}\n"); } impl From for CompilationInfo { fn from(value: CreateShaderModuleError) -> Self { match value { #[cfg(feature = "wgsl")] CreateShaderModuleError::Parsing(v) => v.into(), #[cfg(feature = "glsl")] CreateShaderModuleError::ParsingGlsl(v) => v.into(), #[cfg(feature = "spirv")] CreateShaderModuleError::ParsingSpirV(v) => v.into(), CreateShaderModuleError::Validation(v) => v.into(), // Device errors are reported through the error sink, and are not compilation errors. // Same goes for native shader module generation errors. CreateShaderModuleError::Device(_) | CreateShaderModuleError::Generation => { CompilationInfo { messages: Vec::new(), } } // Everything else is an error message without location information. _ => CompilationInfo { messages: vec![CompilationMessage { message: value.to_string(), message_type: CompilationMessageType::Error, location: None, }], }, } } } #[derive(Debug)] pub struct CoreQueueWriteBuffer { buffer_id: wgc::id::StagingBufferId, mapping: CoreBufferMappedRange, } #[derive(Debug)] pub struct CoreBufferMappedRange { ptr: NonNull, size: usize, } #[cfg(send_sync)] unsafe impl Send for CoreBufferMappedRange {} #[cfg(send_sync)] unsafe impl Sync for CoreBufferMappedRange {} impl Drop for CoreBufferMappedRange { fn drop(&mut self) { // Intentionally left blank so that `BufferMappedRange` still // implements `Drop`, to match the web backend } } crate::cmp::impl_eq_ord_hash_arc_address!(ContextWgpuCore => .0); crate::cmp::impl_eq_ord_hash_proxy!(CoreAdapter => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreDevice => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreQueue => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreShaderModule => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreBindGroupLayout => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreBindGroup => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreTextureView => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreSampler => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreBuffer => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreTexture => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreExternalTexture => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreBlas => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreTlas => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreQuerySet => .id); crate::cmp::impl_eq_ord_hash_proxy!(CorePipelineLayout => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreRenderPipeline => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreComputePipeline => .id); crate::cmp::impl_eq_ord_hash_proxy!(CorePipelineCache => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreCommandEncoder => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreComputePass => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreRenderPass => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreCommandBuffer => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreRenderBundleEncoder => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreRenderBundle => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreSurface => .id); crate::cmp::impl_eq_ord_hash_proxy!(CoreSurfaceOutputDetail => .surface_id); crate::cmp::impl_eq_ord_hash_proxy!(CoreQueueWriteBuffer => .mapping.ptr); crate::cmp::impl_eq_ord_hash_proxy!(CoreBufferMappedRange => .ptr); impl dispatch::InstanceInterface for ContextWgpuCore { fn new(desc: wgt::InstanceDescriptor) -> Self where Self: Sized, { Self(Arc::new(wgc::global::Global::new("wgpu", desc, None))) } unsafe fn create_surface( &self, target: crate::api::SurfaceTargetUnsafe, ) -> Result { let id = match target { SurfaceTargetUnsafe::RawHandle { raw_display_handle, raw_window_handle, } => unsafe { self.0 .instance_create_surface(raw_display_handle, raw_window_handle, None) }, #[cfg(all( unix, not(target_vendor = "apple"), not(target_family = "wasm"), not(target_os = "netbsd") ))] SurfaceTargetUnsafe::Drm { fd, plane, connector_id, width, height, refresh_rate, } => unsafe { self.0.instance_create_surface_from_drm( fd, plane, connector_id, width, height, refresh_rate, None, ) }, #[cfg(metal)] SurfaceTargetUnsafe::CoreAnimationLayer(layer) => unsafe { self.0.instance_create_surface_metal(layer, None) }, #[cfg(target_os = "netbsd")] SurfaceTargetUnsafe::Drm { .. } => Err( wgc::instance::CreateSurfaceError::BackendNotEnabled(wgt::Backend::Vulkan), ), #[cfg(dx12)] SurfaceTargetUnsafe::CompositionVisual(visual) => unsafe { self.0.instance_create_surface_from_visual(visual, None) }, #[cfg(dx12)] SurfaceTargetUnsafe::SurfaceHandle(surface_handle) => unsafe { self.0 .instance_create_surface_from_surface_handle(surface_handle, None) }, #[cfg(dx12)] SurfaceTargetUnsafe::SwapChainPanel(swap_chain_panel) => unsafe { self.0 .instance_create_surface_from_swap_chain_panel(swap_chain_panel, None) }, }?; Ok(CoreSurface { context: self.clone(), id, configured_device: Mutex::default(), error_sink: Mutex::default(), } .into()) } fn request_adapter( &self, options: &crate::api::RequestAdapterOptions<'_, '_>, ) -> Pin> { let id = self.0.request_adapter( &wgc::instance::RequestAdapterOptions { power_preference: options.power_preference, force_fallback_adapter: options.force_fallback_adapter, compatible_surface: options .compatible_surface .map(|surface| surface.inner.as_core().id), }, wgt::Backends::all(), None, ); let adapter = id.map(|id| { let core = CoreAdapter { context: self.clone(), id, }; let generic: dispatch::DispatchAdapter = core.into(); generic }); Box::pin(ready(adapter)) } fn poll_all_devices(&self, force_wait: bool) -> bool { match self.0.poll_all_devices(force_wait) { Ok(all_queue_empty) => all_queue_empty, Err(err) => self.handle_error_fatal(err, "Instance::poll_all_devices"), } } #[cfg(feature = "wgsl")] fn wgsl_language_features(&self) -> crate::WgslLanguageFeatures { use wgc::naga::front::wgsl::ImplementedLanguageExtension; ImplementedLanguageExtension::all().iter().copied().fold( crate::WgslLanguageFeatures::empty(), |acc, wle| { acc | match wle { ImplementedLanguageExtension::ReadOnlyAndReadWriteStorageTextures => { crate::WgslLanguageFeatures::ReadOnlyAndReadWriteStorageTextures } ImplementedLanguageExtension::Packed4x8IntegerDotProduct => { crate::WgslLanguageFeatures::Packed4x8IntegerDotProduct } ImplementedLanguageExtension::PointerCompositeAccess => { crate::WgslLanguageFeatures::PointerCompositeAccess } } }, ) } fn enumerate_adapters( &self, backends: crate::Backends, ) -> Pin> { let adapters: Vec = self .enumerate_adapters(backends) .into_iter() .map(|adapter| { let core = crate::backend::wgpu_core::CoreAdapter { context: self.clone(), id: adapter, }; core.into() }) .collect(); Box::pin(ready(adapters)) } } impl dispatch::AdapterInterface for CoreAdapter { fn request_device( &self, desc: &crate::DeviceDescriptor<'_>, ) -> Pin> { let res = self.context.0.adapter_request_device( self.id, &desc.map_label(|l| l.map(Borrowed)), None, None, ); let (device_id, queue_id) = match res { Ok(ids) => ids, Err(err) => { return Box::pin(ready(Err(err.into()))); } }; let error_sink = Arc::new(Mutex::new(ErrorSinkRaw::new())); let device = CoreDevice { context: self.context.clone(), id: device_id, error_sink: error_sink.clone(), features: desc.required_features, }; let queue = CoreQueue { context: self.context.clone(), id: queue_id, error_sink, }; Box::pin(ready(Ok((device.into(), queue.into())))) } fn is_surface_supported(&self, surface: &dispatch::DispatchSurface) -> bool { let surface = surface.as_core(); self.context .0 .adapter_is_surface_supported(self.id, surface.id) } fn features(&self) -> crate::Features { self.context.0.adapter_features(self.id) } fn limits(&self) -> crate::Limits { self.context.0.adapter_limits(self.id) } fn downlevel_capabilities(&self) -> crate::DownlevelCapabilities { self.context.0.adapter_downlevel_capabilities(self.id) } fn get_info(&self) -> crate::AdapterInfo { self.context.0.adapter_get_info(self.id) } fn get_texture_format_features( &self, format: crate::TextureFormat, ) -> crate::TextureFormatFeatures { self.context .0 .adapter_get_texture_format_features(self.id, format) } fn get_presentation_timestamp(&self) -> crate::PresentationTimestamp { self.context.0.adapter_get_presentation_timestamp(self.id) } fn cooperative_matrix_properties(&self) -> Vec { self.context .0 .adapter_cooperative_matrix_properties(self.id) } } impl Drop for CoreAdapter { fn drop(&mut self) { self.context.0.adapter_drop(self.id) } } impl dispatch::DeviceInterface for CoreDevice { fn features(&self) -> crate::Features { self.context.0.device_features(self.id) } fn limits(&self) -> crate::Limits { self.context.0.device_limits(self.id) } fn adapter_info(&self) -> crate::AdapterInfo { self.context.0.device_adapter_info(self.id) } // If we have no way to create a shader module, we can't return one, and so most of the function is unreachable. #[cfg_attr( not(any( feature = "spirv", feature = "glsl", feature = "wgsl", feature = "naga-ir" )), expect(unused) )] fn create_shader_module( &self, desc: crate::ShaderModuleDescriptor<'_>, shader_bound_checks: wgt::ShaderRuntimeChecks, ) -> dispatch::DispatchShaderModule { let descriptor = wgc::pipeline::ShaderModuleDescriptor { label: desc.label.map(Borrowed), runtime_checks: shader_bound_checks, }; let source = match desc.source { #[cfg(feature = "spirv")] ShaderSource::SpirV(ref spv) => { // Parse the given shader code and store its representation. let options = naga::front::spv::Options { adjust_coordinate_space: false, // we require NDC_Y_UP feature strict_capabilities: true, block_ctx_dump_prefix: None, }; wgc::pipeline::ShaderModuleSource::SpirV(Borrowed(spv), options) } #[cfg(feature = "glsl")] ShaderSource::Glsl { ref shader, stage, defines, } => { let options = naga::front::glsl::Options { stage, defines: defines .iter() .map(|&(key, value)| (String::from(key), String::from(value))) .collect(), }; wgc::pipeline::ShaderModuleSource::Glsl(Borrowed(shader), options) } #[cfg(feature = "wgsl")] ShaderSource::Wgsl(ref code) => wgc::pipeline::ShaderModuleSource::Wgsl(Borrowed(code)), #[cfg(feature = "naga-ir")] ShaderSource::Naga(module) => wgc::pipeline::ShaderModuleSource::Naga(module), ShaderSource::Dummy(_) => panic!("found `ShaderSource::Dummy`"), }; let (id, error) = self.context .0 .device_create_shader_module(self.id, &descriptor, source, None); let compilation_info = match error { Some(cause) => { self.context.handle_error( &self.error_sink, cause.clone(), desc.label, "Device::create_shader_module", ); CompilationInfo::from(cause) } None => CompilationInfo { messages: vec![] }, }; CoreShaderModule { context: self.context.clone(), id, compilation_info, } .into() } unsafe fn create_shader_module_passthrough( &self, desc: &crate::ShaderModuleDescriptorPassthrough<'_>, ) -> dispatch::DispatchShaderModule { let desc = desc.map_label(|l| l.map(Cow::from)); let (id, error) = unsafe { self.context .0 .device_create_shader_module_passthrough(self.id, &desc, None) }; let compilation_info = match error { Some(cause) => { self.context.handle_error( &self.error_sink, cause.clone(), desc.label.as_deref(), "Device::create_shader_module_passthrough", ); CompilationInfo::from(cause) } None => CompilationInfo { messages: vec![] }, }; CoreShaderModule { context: self.context.clone(), id, compilation_info, } .into() } fn create_bind_group_layout( &self, desc: &crate::BindGroupLayoutDescriptor<'_>, ) -> dispatch::DispatchBindGroupLayout { let descriptor = wgc::binding_model::BindGroupLayoutDescriptor { label: desc.label.map(Borrowed), entries: Borrowed(desc.entries), }; let (id, error) = self.context .0 .device_create_bind_group_layout(self.id, &descriptor, None); if let Some(cause) = error { self.context.handle_error( &self.error_sink, cause, desc.label, "Device::create_bind_group_layout", ); } CoreBindGroupLayout { context: self.context.clone(), id, } .into() } fn create_bind_group( &self, desc: &crate::BindGroupDescriptor<'_>, ) -> dispatch::DispatchBindGroup { use wgc::binding_model as bm; let mut arrayed_texture_views = Vec::new(); let mut arrayed_samplers = Vec::new(); if self.features.contains(Features::TEXTURE_BINDING_ARRAY) { // gather all the array view IDs first for entry in desc.entries.iter() { if let BindingResource::TextureViewArray(array) = entry.resource { arrayed_texture_views.extend(array.iter().map(|view| view.inner.as_core().id)); } if let BindingResource::SamplerArray(array) = entry.resource { arrayed_samplers.extend(array.iter().map(|sampler| sampler.inner.as_core().id)); } } } let mut remaining_arrayed_texture_views = &arrayed_texture_views[..]; let mut remaining_arrayed_samplers = &arrayed_samplers[..]; let mut arrayed_buffer_bindings = Vec::new(); if self.features.contains(Features::BUFFER_BINDING_ARRAY) { // gather all the buffers first for entry in desc.entries.iter() { if let BindingResource::BufferArray(array) = entry.resource { arrayed_buffer_bindings.extend(array.iter().map(|binding| bm::BufferBinding { buffer: binding.buffer.inner.as_core().id, offset: binding.offset, size: binding.size.map(wgt::BufferSize::get), })); } } } let mut remaining_arrayed_buffer_bindings = &arrayed_buffer_bindings[..]; let mut arrayed_acceleration_structures = Vec::new(); if self .features .contains(Features::ACCELERATION_STRUCTURE_BINDING_ARRAY) { // Gather all the TLAS IDs used by TLAS arrays first (same pattern as other arrayed resources). for entry in desc.entries.iter() { if let BindingResource::AccelerationStructureArray(array) = entry.resource { arrayed_acceleration_structures .extend(array.iter().map(|tlas| tlas.inner.as_core().id)); } } } let mut remaining_arrayed_acceleration_structures = &arrayed_acceleration_structures[..]; let entries = desc .entries .iter() .map(|entry| bm::BindGroupEntry { binding: entry.binding, resource: match entry.resource { BindingResource::Buffer(BufferBinding { buffer, offset, size, }) => bm::BindingResource::Buffer(bm::BufferBinding { buffer: buffer.inner.as_core().id, offset, size: size.map(wgt::BufferSize::get), }), BindingResource::BufferArray(array) => { let slice = &remaining_arrayed_buffer_bindings[..array.len()]; remaining_arrayed_buffer_bindings = &remaining_arrayed_buffer_bindings[array.len()..]; bm::BindingResource::BufferArray(Borrowed(slice)) } BindingResource::Sampler(sampler) => { bm::BindingResource::Sampler(sampler.inner.as_core().id) } BindingResource::SamplerArray(array) => { let slice = &remaining_arrayed_samplers[..array.len()]; remaining_arrayed_samplers = &remaining_arrayed_samplers[array.len()..]; bm::BindingResource::SamplerArray(Borrowed(slice)) } BindingResource::TextureView(texture_view) => { bm::BindingResource::TextureView(texture_view.inner.as_core().id) } BindingResource::TextureViewArray(array) => { let slice = &remaining_arrayed_texture_views[..array.len()]; remaining_arrayed_texture_views = &remaining_arrayed_texture_views[array.len()..]; bm::BindingResource::TextureViewArray(Borrowed(slice)) } BindingResource::AccelerationStructure(acceleration_structure) => { bm::BindingResource::AccelerationStructure( acceleration_structure.inner.as_core().id, ) } BindingResource::AccelerationStructureArray(array) => { let slice = &remaining_arrayed_acceleration_structures[..array.len()]; remaining_arrayed_acceleration_structures = &remaining_arrayed_acceleration_structures[array.len()..]; bm::BindingResource::AccelerationStructureArray(Borrowed(slice)) } BindingResource::ExternalTexture(external_texture) => { bm::BindingResource::ExternalTexture(external_texture.inner.as_core().id) } }, }) .collect::>(); let descriptor = bm::BindGroupDescriptor { label: desc.label.as_ref().map(|label| Borrowed(&label[..])), layout: desc.layout.inner.as_core().id, entries: Borrowed(&entries), }; let (id, error) = self .context .0 .device_create_bind_group(self.id, &descriptor, None); if let Some(cause) = error { self.context.handle_error( &self.error_sink, cause, desc.label, "Device::create_bind_group", ); } CoreBindGroup { context: self.context.clone(), id, } .into() } fn create_pipeline_layout( &self, desc: &crate::PipelineLayoutDescriptor<'_>, ) -> dispatch::DispatchPipelineLayout { // Limit is always less or equal to hal::MAX_BIND_GROUPS, so this is always right // Guards following ArrayVec assert!( desc.bind_group_layouts.len() <= wgc::MAX_BIND_GROUPS, "Bind group layout count {} exceeds device bind group limit {}", desc.bind_group_layouts.len(), wgc::MAX_BIND_GROUPS ); let temp_layouts = desc .bind_group_layouts .iter() .map(|bgl| bgl.map(|bgl| bgl.inner.as_core().id)) .collect::>(); let descriptor = wgc::binding_model::PipelineLayoutDescriptor { label: desc.label.map(Borrowed), bind_group_layouts: Borrowed(&temp_layouts), immediate_size: desc.immediate_size, }; let (id, error) = self .context .0 .device_create_pipeline_layout(self.id, &descriptor, None); if let Some(cause) = error { self.context.handle_error( &self.error_sink, cause, desc.label, "Device::create_pipeline_layout", ); } CorePipelineLayout { context: self.context.clone(), id, } .into() } fn create_render_pipeline( &self, desc: &crate::RenderPipelineDescriptor<'_>, ) -> dispatch::DispatchRenderPipeline { use wgc::pipeline as pipe; let vertex_buffers: ArrayVec<_, { wgc::MAX_VERTEX_BUFFERS }> = desc .vertex .buffers .iter() .map(|vbuf| pipe::VertexBufferLayout { array_stride: vbuf.array_stride, step_mode: vbuf.step_mode, attributes: Borrowed(vbuf.attributes), }) .collect(); let vert_constants = desc .vertex .compilation_options .constants .iter() .map(|&(key, value)| (String::from(key), value)) .collect(); let descriptor = pipe::RenderPipelineDescriptor { label: desc.label.map(Borrowed), layout: desc.layout.map(|layout| layout.inner.as_core().id), vertex: pipe::VertexState { stage: pipe::ProgrammableStageDescriptor { module: desc.vertex.module.inner.as_core().id, entry_point: desc.vertex.entry_point.map(Borrowed), constants: vert_constants, zero_initialize_workgroup_memory: desc .vertex .compilation_options .zero_initialize_workgroup_memory, }, buffers: Borrowed(&vertex_buffers), }, primitive: desc.primitive, depth_stencil: desc.depth_stencil.clone(), multisample: desc.multisample, fragment: desc.fragment.as_ref().map(|frag| { let frag_constants = frag .compilation_options .constants .iter() .map(|&(key, value)| (String::from(key), value)) .collect(); pipe::FragmentState { stage: pipe::ProgrammableStageDescriptor { module: frag.module.inner.as_core().id, entry_point: frag.entry_point.map(Borrowed), constants: frag_constants, zero_initialize_workgroup_memory: frag .compilation_options .zero_initialize_workgroup_memory, }, targets: Borrowed(frag.targets), } }), multiview_mask: desc.multiview_mask, cache: desc.cache.map(|cache| cache.inner.as_core().id), }; let (id, error) = self .context .0 .device_create_render_pipeline(self.id, &descriptor, None); if let Some(cause) = error { if let wgc::pipeline::CreateRenderPipelineError::Internal { stage, ref error } = cause { log::error!("Shader translation error for stage {stage:?}: {error}"); log::error!("Please report it to https://github.com/gfx-rs/wgpu"); } self.context.handle_error( &self.error_sink, cause, desc.label, "Device::create_render_pipeline", ); } CoreRenderPipeline { context: self.context.clone(), id, error_sink: Arc::clone(&self.error_sink), } .into() } fn create_mesh_pipeline( &self, desc: &crate::MeshPipelineDescriptor<'_>, ) -> dispatch::DispatchRenderPipeline { use wgc::pipeline as pipe; let mesh_constants = desc .mesh .compilation_options .constants .iter() .map(|&(key, value)| (String::from(key), value)) .collect(); let descriptor = pipe::MeshPipelineDescriptor { label: desc.label.map(Borrowed), task: desc.task.as_ref().map(|task| { let task_constants = task .compilation_options .constants .iter() .map(|&(key, value)| (String::from(key), value)) .collect(); pipe::TaskState { stage: pipe::ProgrammableStageDescriptor { module: task.module.inner.as_core().id, entry_point: task.entry_point.map(Borrowed), constants: task_constants, zero_initialize_workgroup_memory: desc .mesh .compilation_options .zero_initialize_workgroup_memory, }, } }), mesh: pipe::MeshState { stage: pipe::ProgrammableStageDescriptor { module: desc.mesh.module.inner.as_core().id, entry_point: desc.mesh.entry_point.map(Borrowed), constants: mesh_constants, zero_initialize_workgroup_memory: desc .mesh .compilation_options .zero_initialize_workgroup_memory, }, }, layout: desc.layout.map(|layout| layout.inner.as_core().id), primitive: desc.primitive, depth_stencil: desc.depth_stencil.clone(), multisample: desc.multisample, fragment: desc.fragment.as_ref().map(|frag| { let frag_constants = frag .compilation_options .constants .iter() .map(|&(key, value)| (String::from(key), value)) .collect(); pipe::FragmentState { stage: pipe::ProgrammableStageDescriptor { module: frag.module.inner.as_core().id, entry_point: frag.entry_point.map(Borrowed), constants: frag_constants, zero_initialize_workgroup_memory: frag .compilation_options .zero_initialize_workgroup_memory, }, targets: Borrowed(frag.targets), } }), multiview: desc.multiview, cache: desc.cache.map(|cache| cache.inner.as_core().id), }; let (id, error) = self .context .0 .device_create_mesh_pipeline(self.id, &descriptor, None); if let Some(cause) = error { if let wgc::pipeline::CreateRenderPipelineError::Internal { stage, ref error } = cause { log::error!("Shader translation error for stage {stage:?}: {error}"); log::error!("Please report it to https://github.com/gfx-rs/wgpu"); } self.context.handle_error( &self.error_sink, cause, desc.label, "Device::create_render_pipeline", ); } CoreRenderPipeline { context: self.context.clone(), id, error_sink: Arc::clone(&self.error_sink), } .into() } fn create_compute_pipeline( &self, desc: &crate::ComputePipelineDescriptor<'_>, ) -> dispatch::DispatchComputePipeline { use wgc::pipeline as pipe; let constants = desc .compilation_options .constants .iter() .map(|&(key, value)| (String::from(key), value)) .collect(); let descriptor = pipe::ComputePipelineDescriptor { label: desc.label.map(Borrowed), layout: desc.layout.map(|pll| pll.inner.as_core().id), stage: pipe::ProgrammableStageDescriptor { module: desc.module.inner.as_core().id, entry_point: desc.entry_point.map(Borrowed), constants, zero_initialize_workgroup_memory: desc .compilation_options .zero_initialize_workgroup_memory, }, cache: desc.cache.map(|cache| cache.inner.as_core().id), }; let (id, error) = self .context .0 .device_create_compute_pipeline(self.id, &descriptor, None); if let Some(cause) = error { if let wgc::pipeline::CreateComputePipelineError::Internal(ref error) = cause { log::error!( "Shader translation error for stage {:?}: {}", wgt::ShaderStages::COMPUTE, error ); log::error!("Please report it to https://github.com/gfx-rs/wgpu"); } self.context.handle_error( &self.error_sink, cause, desc.label, "Device::create_compute_pipeline", ); } CoreComputePipeline { context: self.context.clone(), id, error_sink: Arc::clone(&self.error_sink), } .into() } unsafe fn create_pipeline_cache( &self, desc: &crate::PipelineCacheDescriptor<'_>, ) -> dispatch::DispatchPipelineCache { use wgc::pipeline as pipe; let descriptor = pipe::PipelineCacheDescriptor { label: desc.label.map(Borrowed), data: desc.data.map(Borrowed), fallback: desc.fallback, }; let (id, error) = unsafe { self.context .0 .device_create_pipeline_cache(self.id, &descriptor, None) }; if let Some(cause) = error { self.context.handle_error( &self.error_sink, cause, desc.label, "Device::device_create_pipeline_cache_init", ); } CorePipelineCache { context: self.context.clone(), id, } .into() } fn create_buffer(&self, desc: &crate::BufferDescriptor<'_>) -> dispatch::DispatchBuffer { let (id, error) = self.context.0.device_create_buffer( self.id, &desc.map_label(|l| l.map(Borrowed)), None, ); if let Some(cause) = error { self.context .handle_error(&self.error_sink, cause, desc.label, "Device::create_buffer"); } CoreBuffer { context: self.context.clone(), id, error_sink: Arc::clone(&self.error_sink), } .into() } fn create_texture(&self, desc: &crate::TextureDescriptor<'_>) -> dispatch::DispatchTexture { let wgt_desc = desc.map_label_and_view_formats(|l| l.map(Borrowed), |v| v.to_vec()); let (id, error) = self .context .0 .device_create_texture(self.id, &wgt_desc, None); if let Some(cause) = error { self.context.handle_error( &self.error_sink, cause, desc.label, "Device::create_texture", ); } CoreTexture { context: self.context.clone(), id, error_sink: Arc::clone(&self.error_sink), } .into() } fn create_external_texture( &self, desc: &crate::ExternalTextureDescriptor<'_>, planes: &[&crate::TextureView], ) -> dispatch::DispatchExternalTexture { let wgt_desc = desc.map_label(|l| l.map(Borrowed)); let planes = planes .iter() .map(|plane| plane.inner.as_core().id) .collect::>(); let (id, error) = self .context .0 .device_create_external_texture(self.id, &wgt_desc, &planes, None); if let Some(cause) = error { self.context.handle_error( &self.error_sink, cause, desc.label, "Device::create_external_texture", ); } CoreExternalTexture { context: self.context.clone(), id, } .into() } fn create_blas( &self, desc: &crate::CreateBlasDescriptor<'_>, sizes: crate::BlasGeometrySizeDescriptors, ) -> (Option, dispatch::DispatchBlas) { let global = &self.context.0; let (id, handle, error) = global.device_create_blas(self.id, &desc.map_label(|l| l.map(Borrowed)), sizes, None); if let Some(cause) = error { self.context .handle_error(&self.error_sink, cause, desc.label, "Device::create_blas"); } ( handle, CoreBlas { context: self.context.clone(), id, error_sink: Arc::clone(&self.error_sink), } .into(), ) } fn create_tlas(&self, desc: &crate::CreateTlasDescriptor<'_>) -> dispatch::DispatchTlas { let global = &self.context.0; let (id, error) = global.device_create_tlas(self.id, &desc.map_label(|l| l.map(Borrowed)), None); if let Some(cause) = error { self.context .handle_error(&self.error_sink, cause, desc.label, "Device::create_tlas"); } CoreTlas { context: self.context.clone(), id, // error_sink: Arc::clone(&self.error_sink), } .into() } fn create_sampler(&self, desc: &crate::SamplerDescriptor<'_>) -> dispatch::DispatchSampler { let descriptor = wgc::resource::SamplerDescriptor { label: desc.label.map(Borrowed), address_modes: [ desc.address_mode_u, desc.address_mode_v, desc.address_mode_w, ], mag_filter: desc.mag_filter, min_filter: desc.min_filter, mipmap_filter: desc.mipmap_filter, lod_min_clamp: desc.lod_min_clamp, lod_max_clamp: desc.lod_max_clamp, compare: desc.compare, anisotropy_clamp: desc.anisotropy_clamp, border_color: desc.border_color, }; let (id, error) = self .context .0 .device_create_sampler(self.id, &descriptor, None); if let Some(cause) = error { self.context.handle_error( &self.error_sink, cause, desc.label, "Device::create_sampler", ); } CoreSampler { context: self.context.clone(), id, } .into() } fn create_query_set(&self, desc: &crate::QuerySetDescriptor<'_>) -> dispatch::DispatchQuerySet { let (id, error) = self.context.0.device_create_query_set( self.id, &desc.map_label(|l| l.map(Borrowed)), None, ); if let Some(cause) = error { self.context .handle_error_nolabel(&self.error_sink, cause, "Device::create_query_set"); } CoreQuerySet { context: self.context.clone(), id, } .into() } fn create_command_encoder( &self, desc: &crate::CommandEncoderDescriptor<'_>, ) -> dispatch::DispatchCommandEncoder { let (id, error) = self.context.0.device_create_command_encoder( self.id, &desc.map_label(|l| l.map(Borrowed)), None, ); if let Some(cause) = error { self.context.handle_error( &self.error_sink, cause, desc.label, "Device::create_command_encoder", ); } CoreCommandEncoder { context: self.context.clone(), id, error_sink: Arc::clone(&self.error_sink), } .into() } fn create_render_bundle_encoder( &self, desc: &crate::RenderBundleEncoderDescriptor<'_>, ) -> dispatch::DispatchRenderBundleEncoder { let descriptor = wgc::command::RenderBundleEncoderDescriptor { label: desc.label.map(Borrowed), color_formats: Borrowed(desc.color_formats), depth_stencil: desc.depth_stencil, sample_count: desc.sample_count, multiview: desc.multiview, }; let encoder = match wgc::command::RenderBundleEncoder::new(&descriptor, self.id) { Ok(encoder) => encoder, Err(e) => panic!("Error in Device::create_render_bundle_encoder: {e}"), }; CoreRenderBundleEncoder { context: self.context.clone(), encoder, id: crate::cmp::Identifier::create(), } .into() } fn set_device_lost_callback(&self, device_lost_callback: dispatch::BoxDeviceLostCallback) { self.context .0 .device_set_device_lost_closure(self.id, device_lost_callback); } fn on_uncaptured_error(&self, handler: Arc) { let mut error_sink = self.error_sink.lock(); error_sink.uncaptured_handler = Some(handler); } fn push_error_scope(&self, filter: crate::ErrorFilter) -> u32 { let mut error_sink = self.error_sink.lock(); let thread_id = thread_id::ThreadId::current(); let scopes = error_sink.scopes.entry(thread_id).or_default(); let index = scopes .len() .try_into() .expect("Greater than 2^32 nested error scopes"); scopes.push(ErrorScope { error: None, filter, }); index } fn pop_error_scope(&self, index: u32) -> Pin> { let mut error_sink = self.error_sink.lock(); // We go out of our way to avoid panicking while unwinding, because that would abort the process, // and we are supposed to just drop the error scope on the floor. let is_panicking = crate::util::is_panicking(); let thread_id = thread_id::ThreadId::current(); let err = "Mismatched pop_error_scope call: no error scope for this thread. Error scopes are thread-local."; let scopes = match error_sink.scopes.get_mut(&thread_id) { Some(s) => s, None => { if !is_panicking { panic!("{err}"); } else { return Box::pin(ready(None)); } } }; if scopes.is_empty() && !is_panicking { panic!("{err}"); } if index as usize != scopes.len() - 1 && !is_panicking { panic!( "Mismatched pop_error_scope call: error scopes must be popped in reverse order." ); } // It would be more correct in this case to use `remove` here so that when unwinding is occurring // we would remove the correct error scope, but we don't have such a primitive on the web // and having consistent behavior here is more important. If you are unwinding and it unwinds // the guards in the wrong order, it's totally reasonable to have incorrect behavior. let scope = match scopes.pop() { Some(s) => s, None if !is_panicking => unreachable!(), None => return Box::pin(ready(None)), }; Box::pin(ready(scope.error)) } unsafe fn start_graphics_debugger_capture(&self) { unsafe { self.context .0 .device_start_graphics_debugger_capture(self.id) }; } unsafe fn stop_graphics_debugger_capture(&self) { unsafe { self.context .0 .device_stop_graphics_debugger_capture(self.id) }; } fn poll(&self, poll_type: wgt::PollType) -> Result { match self.context.0.device_poll(self.id, poll_type) { Ok(status) => Ok(status), Err(err) => { if let Some(poll_error) = err.to_poll_error() { return Err(poll_error); } self.context.handle_error_fatal(err, "Device::poll") } } } fn get_internal_counters(&self) -> crate::InternalCounters { self.context.0.device_get_internal_counters(self.id) } fn generate_allocator_report(&self) -> Option { self.context.0.device_generate_allocator_report(self.id) } fn destroy(&self) { self.context.0.device_destroy(self.id); } } impl Drop for CoreDevice { fn drop(&mut self) { self.context.0.device_drop(self.id) } } impl dispatch::QueueInterface for CoreQueue { fn write_buffer( &self, buffer: &dispatch::DispatchBuffer, offset: crate::BufferAddress, data: &[u8], ) { let buffer = buffer.as_core(); match self .context .0 .queue_write_buffer(self.id, buffer.id, offset, data) { Ok(()) => (), Err(err) => { self.context .handle_error_nolabel(&self.error_sink, err, "Queue::write_buffer") } } } fn create_staging_buffer( &self, size: crate::BufferSize, ) -> Option { match self .context .0 .queue_create_staging_buffer(self.id, size, None) { Ok((buffer_id, ptr)) => Some( CoreQueueWriteBuffer { buffer_id, mapping: CoreBufferMappedRange { ptr, size: size.get() as usize, }, } .into(), ), Err(err) => { self.context.handle_error_nolabel( &self.error_sink, err, "Queue::write_buffer_with", ); None } } } fn validate_write_buffer( &self, buffer: &dispatch::DispatchBuffer, offset: wgt::BufferAddress, size: wgt::BufferSize, ) -> Option<()> { let buffer = buffer.as_core(); match self .context .0 .queue_validate_write_buffer(self.id, buffer.id, offset, size) { Ok(()) => Some(()), Err(err) => { self.context.handle_error_nolabel( &self.error_sink, err, "Queue::write_buffer_with", ); None } } } fn write_staging_buffer( &self, buffer: &dispatch::DispatchBuffer, offset: crate::BufferAddress, staging_buffer: &dispatch::DispatchQueueWriteBuffer, ) { let buffer = buffer.as_core(); let staging_buffer = staging_buffer.as_core(); match self.context.0.queue_write_staging_buffer( self.id, buffer.id, offset, staging_buffer.buffer_id, ) { Ok(()) => (), Err(err) => { self.context.handle_error_nolabel( &self.error_sink, err, "Queue::write_buffer_with", ); } } } fn write_texture( &self, texture: crate::TexelCopyTextureInfo<'_>, data: &[u8], data_layout: crate::TexelCopyBufferLayout, size: crate::Extent3d, ) { match self.context.0.queue_write_texture( self.id, &map_texture_copy_view(texture), data, &data_layout, &size, ) { Ok(()) => (), Err(err) => { self.context .handle_error_nolabel(&self.error_sink, err, "Queue::write_texture") } } } // This method needs to exist if either webgpu or webgl is enabled, // but we only actually have an implementation if webgl is enabled. #[cfg(web)] #[cfg_attr(not(webgl), expect(unused_variables))] fn copy_external_image_to_texture( &self, source: &crate::CopyExternalImageSourceInfo, dest: crate::CopyExternalImageDestInfo<&crate::api::Texture>, size: crate::Extent3d, ) { #[cfg(webgl)] match self.context.0.queue_copy_external_image_to_texture( self.id, source, map_texture_tagged_copy_view(dest), size, ) { Ok(()) => (), Err(err) => self.context.handle_error_nolabel( &self.error_sink, err, "Queue::copy_external_image_to_texture", ), } } fn submit( &self, command_buffers: &mut dyn Iterator, ) -> u64 { let temp_command_buffers = command_buffers.collect::>(); let command_buffer_ids = temp_command_buffers .iter() .map(|cmdbuf| cmdbuf.as_core().id) .collect::>(); let index = match self.context.0.queue_submit(self.id, &command_buffer_ids) { Ok(index) => index, Err((index, err)) => { self.context .handle_error_nolabel(&self.error_sink, err, "Queue::submit"); index } }; drop(temp_command_buffers); index } fn get_timestamp_period(&self) -> f32 { self.context.0.queue_get_timestamp_period(self.id) } fn on_submitted_work_done(&self, callback: dispatch::BoxSubmittedWorkDoneCallback) { self.context .0 .queue_on_submitted_work_done(self.id, callback); } fn compact_blas(&self, blas: &dispatch::DispatchBlas) -> (Option, dispatch::DispatchBlas) { let (id, handle, error) = self.context .0 .queue_compact_blas(self.id, blas.as_core().id, None); if let Some(cause) = error { self.context .handle_error_nolabel(&self.error_sink, cause, "Queue::compact_blas"); } ( handle, CoreBlas { context: self.context.clone(), id, error_sink: Arc::clone(&self.error_sink), } .into(), ) } } impl Drop for CoreQueue { fn drop(&mut self) { self.context.0.queue_drop(self.id) } } impl dispatch::ShaderModuleInterface for CoreShaderModule { fn get_compilation_info(&self) -> Pin> { Box::pin(ready(self.compilation_info.clone())) } } impl Drop for CoreShaderModule { fn drop(&mut self) { self.context.0.shader_module_drop(self.id) } } impl dispatch::BindGroupLayoutInterface for CoreBindGroupLayout {} impl Drop for CoreBindGroupLayout { fn drop(&mut self) { self.context.0.bind_group_layout_drop(self.id) } } impl dispatch::BindGroupInterface for CoreBindGroup {} impl Drop for CoreBindGroup { fn drop(&mut self) { self.context.0.bind_group_drop(self.id) } } impl dispatch::TextureViewInterface for CoreTextureView {} impl Drop for CoreTextureView { fn drop(&mut self) { self.context.0.texture_view_drop(self.id); } } impl dispatch::ExternalTextureInterface for CoreExternalTexture { fn destroy(&self) { self.context.0.external_texture_destroy(self.id); } } impl Drop for CoreExternalTexture { fn drop(&mut self) { self.context.0.external_texture_drop(self.id); } } impl dispatch::SamplerInterface for CoreSampler {} impl Drop for CoreSampler { fn drop(&mut self) { self.context.0.sampler_drop(self.id) } } impl dispatch::BufferInterface for CoreBuffer { fn map_async( &self, mode: crate::MapMode, range: Range, callback: dispatch::BufferMapCallback, ) { let operation = wgc::resource::BufferMapOperation { host: match mode { MapMode::Read => wgc::device::HostMap::Read, MapMode::Write => wgc::device::HostMap::Write, }, callback: Some(Box::new(|status| { let res = status.map_err(|_| crate::BufferAsyncError); callback(res); })), }; match self.context.0.buffer_map_async( self.id, range.start, Some(range.end - range.start), operation, ) { Ok(_) => (), Err(cause) => { self.context .handle_error_nolabel(&self.error_sink, cause, "Buffer::map_async") } } } fn get_mapped_range( &self, sub_range: Range, ) -> dispatch::DispatchBufferMappedRange { let size = sub_range.end - sub_range.start; match self .context .0 .buffer_get_mapped_range(self.id, sub_range.start, Some(size)) { Ok((ptr, size)) => CoreBufferMappedRange { ptr, size: size as usize, } .into(), Err(err) => self .context .handle_error_fatal(err, "Buffer::get_mapped_range"), } } fn unmap(&self) { match self.context.0.buffer_unmap(self.id) { Ok(()) => (), Err(cause) => { self.context .handle_error_nolabel(&self.error_sink, cause, "Buffer::buffer_unmap") } } } fn destroy(&self) { self.context.0.buffer_destroy(self.id); } } impl Drop for CoreBuffer { fn drop(&mut self) { self.context.0.buffer_drop(self.id) } } impl dispatch::TextureInterface for CoreTexture { fn create_view( &self, desc: &crate::TextureViewDescriptor<'_>, ) -> dispatch::DispatchTextureView { let descriptor = wgc::resource::TextureViewDescriptor { label: desc.label.map(Borrowed), format: desc.format, dimension: desc.dimension, usage: desc.usage, range: wgt::ImageSubresourceRange { aspect: desc.aspect, base_mip_level: desc.base_mip_level, mip_level_count: desc.mip_level_count, base_array_layer: desc.base_array_layer, array_layer_count: desc.array_layer_count, }, }; let (id, error) = self .context .0 .texture_create_view(self.id, &descriptor, None); if let Some(cause) = error { self.context .handle_error(&self.error_sink, cause, desc.label, "Texture::create_view"); } CoreTextureView { context: self.context.clone(), id, } .into() } fn destroy(&self) { self.context.0.texture_destroy(self.id); } } impl Drop for CoreTexture { fn drop(&mut self) { self.context.0.texture_drop(self.id) } } impl dispatch::BlasInterface for CoreBlas { fn prepare_compact_async(&self, callback: BlasCompactCallback) { let callback: Option = Some(Box::new(|status: BlasPrepareCompactResult| { let res = status.map_err(|_| crate::BlasAsyncError); callback(res); })); match self.context.0.blas_prepare_compact_async(self.id, callback) { Ok(_) => (), Err(cause) => self.context.handle_error_nolabel( &self.error_sink, cause, "Blas::prepare_compact_async", ), } } fn ready_for_compaction(&self) -> bool { match self.context.0.ready_for_compaction(self.id) { Ok(ready) => ready, Err(cause) => { self.context.handle_error_nolabel( &self.error_sink, cause, "Blas::ready_for_compaction", ); // A BLAS is definitely not ready for compaction if it's not valid false } } } } impl Drop for CoreBlas { fn drop(&mut self) { self.context.0.blas_drop(self.id) } } impl dispatch::TlasInterface for CoreTlas {} impl Drop for CoreTlas { fn drop(&mut self) { self.context.0.tlas_drop(self.id) } } impl dispatch::QuerySetInterface for CoreQuerySet {} impl Drop for CoreQuerySet { fn drop(&mut self) { self.context.0.query_set_drop(self.id) } } impl dispatch::PipelineLayoutInterface for CorePipelineLayout {} impl Drop for CorePipelineLayout { fn drop(&mut self) { self.context.0.pipeline_layout_drop(self.id) } } impl dispatch::RenderPipelineInterface for CoreRenderPipeline { fn get_bind_group_layout(&self, index: u32) -> dispatch::DispatchBindGroupLayout { let (id, error) = self .context .0 .render_pipeline_get_bind_group_layout(self.id, index, None); if let Some(err) = error { self.context.handle_error_nolabel( &self.error_sink, err, "RenderPipeline::get_bind_group_layout", ) } CoreBindGroupLayout { context: self.context.clone(), id, } .into() } } impl Drop for CoreRenderPipeline { fn drop(&mut self) { self.context.0.render_pipeline_drop(self.id) } } impl dispatch::ComputePipelineInterface for CoreComputePipeline { fn get_bind_group_layout(&self, index: u32) -> dispatch::DispatchBindGroupLayout { let (id, error) = self .context .0 .compute_pipeline_get_bind_group_layout(self.id, index, None); if let Some(err) = error { self.context.handle_error_nolabel( &self.error_sink, err, "ComputePipeline::get_bind_group_layout", ) } CoreBindGroupLayout { context: self.context.clone(), id, } .into() } } impl Drop for CoreComputePipeline { fn drop(&mut self) { self.context.0.compute_pipeline_drop(self.id) } } impl dispatch::PipelineCacheInterface for CorePipelineCache { fn get_data(&self) -> Option> { self.context.0.pipeline_cache_get_data(self.id) } } impl Drop for CorePipelineCache { fn drop(&mut self) { self.context.0.pipeline_cache_drop(self.id) } } impl dispatch::CommandEncoderInterface for CoreCommandEncoder { fn copy_buffer_to_buffer( &self, source: &dispatch::DispatchBuffer, source_offset: crate::BufferAddress, destination: &dispatch::DispatchBuffer, destination_offset: crate::BufferAddress, copy_size: Option, ) { let source = source.as_core(); let destination = destination.as_core(); if let Err(cause) = self.context.0.command_encoder_copy_buffer_to_buffer( self.id, source.id, source_offset, destination.id, destination_offset, copy_size, ) { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::copy_buffer_to_buffer", ); } } fn copy_buffer_to_texture( &self, source: crate::TexelCopyBufferInfo<'_>, destination: crate::TexelCopyTextureInfo<'_>, copy_size: crate::Extent3d, ) { if let Err(cause) = self.context.0.command_encoder_copy_buffer_to_texture( self.id, &map_buffer_copy_view(source), &map_texture_copy_view(destination), ©_size, ) { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::copy_buffer_to_texture", ); } } fn copy_texture_to_buffer( &self, source: crate::TexelCopyTextureInfo<'_>, destination: crate::TexelCopyBufferInfo<'_>, copy_size: crate::Extent3d, ) { if let Err(cause) = self.context.0.command_encoder_copy_texture_to_buffer( self.id, &map_texture_copy_view(source), &map_buffer_copy_view(destination), ©_size, ) { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::copy_texture_to_buffer", ); } } fn copy_texture_to_texture( &self, source: crate::TexelCopyTextureInfo<'_>, destination: crate::TexelCopyTextureInfo<'_>, copy_size: crate::Extent3d, ) { if let Err(cause) = self.context.0.command_encoder_copy_texture_to_texture( self.id, &map_texture_copy_view(source), &map_texture_copy_view(destination), ©_size, ) { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::copy_texture_to_texture", ); } } fn begin_compute_pass( &self, desc: &crate::ComputePassDescriptor<'_>, ) -> dispatch::DispatchComputePass { let timestamp_writes = desc.timestamp_writes .as_ref() .map(|tw| wgc::command::PassTimestampWrites { query_set: tw.query_set.inner.as_core().id, beginning_of_pass_write_index: tw.beginning_of_pass_write_index, end_of_pass_write_index: tw.end_of_pass_write_index, }); let (pass, err) = self.context.0.command_encoder_begin_compute_pass( self.id, &wgc::command::ComputePassDescriptor { label: desc.label.map(Borrowed), timestamp_writes, }, ); if let Some(cause) = err { self.context.handle_error( &self.error_sink, cause, desc.label, "CommandEncoder::begin_compute_pass", ); } CoreComputePass { context: self.context.clone(), pass, error_sink: self.error_sink.clone(), id: crate::cmp::Identifier::create(), } .into() } fn begin_render_pass( &self, desc: &crate::RenderPassDescriptor<'_>, ) -> dispatch::DispatchRenderPass { let colors = desc .color_attachments .iter() .map(|ca| { ca.as_ref() .map(|at| wgc::command::RenderPassColorAttachment { view: at.view.inner.as_core().id, depth_slice: at.depth_slice, resolve_target: at.resolve_target.map(|view| view.inner.as_core().id), load_op: at.ops.load, store_op: at.ops.store, }) }) .collect::>(); let depth_stencil = desc.depth_stencil_attachment.as_ref().map(|dsa| { wgc::command::RenderPassDepthStencilAttachment { view: dsa.view.inner.as_core().id, depth: map_pass_channel(dsa.depth_ops.as_ref()), stencil: map_pass_channel(dsa.stencil_ops.as_ref()), } }); let timestamp_writes = desc.timestamp_writes .as_ref() .map(|tw| wgc::command::PassTimestampWrites { query_set: tw.query_set.inner.as_core().id, beginning_of_pass_write_index: tw.beginning_of_pass_write_index, end_of_pass_write_index: tw.end_of_pass_write_index, }); let (pass, err) = self.context.0.command_encoder_begin_render_pass( self.id, &wgc::command::RenderPassDescriptor { label: desc.label.map(Borrowed), timestamp_writes: timestamp_writes.as_ref(), color_attachments: Borrowed(&colors), depth_stencil_attachment: depth_stencil.as_ref(), occlusion_query_set: desc.occlusion_query_set.map(|qs| qs.inner.as_core().id), multiview_mask: desc.multiview_mask, }, ); if let Some(cause) = err { self.context.handle_error( &self.error_sink, cause, desc.label, "CommandEncoder::begin_render_pass", ); } CoreRenderPass { context: self.context.clone(), pass, error_sink: self.error_sink.clone(), id: crate::cmp::Identifier::create(), } .into() } fn finish(&mut self) -> dispatch::DispatchCommandBuffer { let descriptor = wgt::CommandBufferDescriptor::default(); let (id, opt_label_and_error) = self.context .0 .command_encoder_finish(self.id, &descriptor, None); if let Some((label, cause)) = opt_label_and_error { self.context .handle_error(&self.error_sink, cause, Some(&label), "a CommandEncoder"); } CoreCommandBuffer { context: self.context.clone(), id, } .into() } fn clear_texture( &self, texture: &dispatch::DispatchTexture, subresource_range: &crate::ImageSubresourceRange, ) { let texture = texture.as_core(); if let Err(cause) = self.context .0 .command_encoder_clear_texture(self.id, texture.id, subresource_range) { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::clear_texture", ); } } fn clear_buffer( &self, buffer: &dispatch::DispatchBuffer, offset: crate::BufferAddress, size: Option, ) { let buffer = buffer.as_core(); if let Err(cause) = self .context .0 .command_encoder_clear_buffer(self.id, buffer.id, offset, size) { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::fill_buffer", ); } } fn insert_debug_marker(&self, label: &str) { if let Err(cause) = self .context .0 .command_encoder_insert_debug_marker(self.id, label) { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::insert_debug_marker", ); } } fn push_debug_group(&self, label: &str) { if let Err(cause) = self .context .0 .command_encoder_push_debug_group(self.id, label) { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::push_debug_group", ); } } fn pop_debug_group(&self) { if let Err(cause) = self.context.0.command_encoder_pop_debug_group(self.id) { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::pop_debug_group", ); } } fn write_timestamp(&self, query_set: &dispatch::DispatchQuerySet, query_index: u32) { let query_set = query_set.as_core(); if let Err(cause) = self.context .0 .command_encoder_write_timestamp(self.id, query_set.id, query_index) { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::write_timestamp", ); } } fn resolve_query_set( &self, query_set: &dispatch::DispatchQuerySet, first_query: u32, query_count: u32, destination: &dispatch::DispatchBuffer, destination_offset: crate::BufferAddress, ) { let query_set = query_set.as_core(); let destination = destination.as_core(); if let Err(cause) = self.context.0.command_encoder_resolve_query_set( self.id, query_set.id, first_query, query_count, destination.id, destination_offset, ) { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::resolve_query_set", ); } } fn mark_acceleration_structures_built<'a>( &self, blas: &mut dyn Iterator, tlas: &mut dyn Iterator, ) { let blas = blas .map(|b| b.inner.as_core().id) .collect::>(); let tlas = tlas .map(|t| t.inner.as_core().id) .collect::>(); if let Err(cause) = self .context .0 .command_encoder_mark_acceleration_structures_built(self.id, &blas, &tlas) { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::build_acceleration_structures_unsafe_tlas", ); } } fn build_acceleration_structures<'a>( &self, blas: &mut dyn Iterator>, tlas: &mut dyn Iterator, ) { let blas = blas.map(|e: &crate::BlasBuildEntry<'_>| { let geometries = match e.geometry { crate::BlasGeometries::TriangleGeometries(ref triangle_geometries) => { let iter = triangle_geometries.iter().map(|tg| { wgc::ray_tracing::BlasTriangleGeometry { vertex_buffer: tg.vertex_buffer.inner.as_core().id, index_buffer: tg.index_buffer.map(|buf| buf.inner.as_core().id), transform_buffer: tg.transform_buffer.map(|buf| buf.inner.as_core().id), size: tg.size, transform_buffer_offset: tg.transform_buffer_offset, first_vertex: tg.first_vertex, vertex_stride: tg.vertex_stride, first_index: tg.first_index, } }); wgc::ray_tracing::BlasGeometries::TriangleGeometries(Box::new(iter)) } }; wgc::ray_tracing::BlasBuildEntry { blas_id: e.blas.inner.as_core().id, geometries, } }); let tlas = tlas.into_iter().map(|e| { let instances = e .instances .iter() .map(|instance: &Option| { instance .as_ref() .map(|instance| wgc::ray_tracing::TlasInstance { blas_id: instance.blas.as_core().id, transform: &instance.transform, custom_data: instance.custom_data, mask: instance.mask, }) }); wgc::ray_tracing::TlasPackage { tlas_id: e.inner.as_core().id, instances: Box::new(instances), lowest_unmodified: e.lowest_unmodified, } }); if let Err(cause) = self .context .0 .command_encoder_build_acceleration_structures(self.id, blas, tlas) { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::build_acceleration_structures_unsafe_tlas", ); } } fn transition_resources<'a>( &mut self, buffer_transitions: &mut dyn Iterator< Item = wgt::BufferTransition<&'a dispatch::DispatchBuffer>, >, texture_transitions: &mut dyn Iterator< Item = wgt::TextureTransition<&'a dispatch::DispatchTexture>, >, ) { let result = self.context.0.command_encoder_transition_resources( self.id, buffer_transitions.map(|t| wgt::BufferTransition { buffer: t.buffer.as_core().id, state: t.state, }), texture_transitions.map(|t| wgt::TextureTransition { texture: t.texture.as_core().id, selector: t.selector.clone(), state: t.state, }), ); if let Err(cause) = result { self.context.handle_error_nolabel( &self.error_sink, cause, "CommandEncoder::transition_resources", ); } } } impl Drop for CoreCommandEncoder { fn drop(&mut self) { self.context.0.command_encoder_drop(self.id) } } impl dispatch::CommandBufferInterface for CoreCommandBuffer {} impl Drop for CoreCommandBuffer { fn drop(&mut self) { self.context.0.command_buffer_drop(self.id) } } impl dispatch::ComputePassInterface for CoreComputePass { fn set_pipeline(&mut self, pipeline: &dispatch::DispatchComputePipeline) { let pipeline = pipeline.as_core(); if let Err(cause) = self .context .0 .compute_pass_set_pipeline(&mut self.pass, pipeline.id) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "ComputePass::set_pipeline", ); } } fn set_bind_group( &mut self, index: u32, bind_group: Option<&dispatch::DispatchBindGroup>, offsets: &[crate::DynamicOffset], ) { let bg = bind_group.map(|bg| bg.as_core().id); if let Err(cause) = self.context .0 .compute_pass_set_bind_group(&mut self.pass, index, bg, offsets) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "ComputePass::set_bind_group", ); } } fn set_immediates(&mut self, offset: u32, data: &[u8]) { if let Err(cause) = self .context .0 .compute_pass_set_immediates(&mut self.pass, offset, data) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "ComputePass::set_immediates", ); } } fn insert_debug_marker(&mut self, label: &str) { if let Err(cause) = self.context .0 .compute_pass_insert_debug_marker(&mut self.pass, label, 0) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "ComputePass::insert_debug_marker", ); } } fn push_debug_group(&mut self, group_label: &str) { if let Err(cause) = self.context .0 .compute_pass_push_debug_group(&mut self.pass, group_label, 0) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "ComputePass::push_debug_group", ); } } fn pop_debug_group(&mut self) { if let Err(cause) = self.context.0.compute_pass_pop_debug_group(&mut self.pass) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "ComputePass::pop_debug_group", ); } } fn write_timestamp(&mut self, query_set: &dispatch::DispatchQuerySet, query_index: u32) { let query_set = query_set.as_core(); if let Err(cause) = self.context .0 .compute_pass_write_timestamp(&mut self.pass, query_set.id, query_index) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "ComputePass::write_timestamp", ); } } fn begin_pipeline_statistics_query( &mut self, query_set: &dispatch::DispatchQuerySet, query_index: u32, ) { let query_set = query_set.as_core(); if let Err(cause) = self.context.0.compute_pass_begin_pipeline_statistics_query( &mut self.pass, query_set.id, query_index, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "ComputePass::begin_pipeline_statistics_query", ); } } fn end_pipeline_statistics_query(&mut self) { if let Err(cause) = self .context .0 .compute_pass_end_pipeline_statistics_query(&mut self.pass) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "ComputePass::end_pipeline_statistics_query", ); } } fn dispatch_workgroups(&mut self, x: u32, y: u32, z: u32) { if let Err(cause) = self .context .0 .compute_pass_dispatch_workgroups(&mut self.pass, x, y, z) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "ComputePass::dispatch_workgroups", ); } } fn dispatch_workgroups_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, ) { let indirect_buffer = indirect_buffer.as_core(); if let Err(cause) = self.context.0.compute_pass_dispatch_workgroups_indirect( &mut self.pass, indirect_buffer.id, indirect_offset, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "ComputePass::dispatch_workgroups_indirect", ); } } } impl Drop for CoreComputePass { fn drop(&mut self) { if let Err(cause) = self.context.0.compute_pass_end(&mut self.pass) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "ComputePass::end", ); } } } impl dispatch::RenderPassInterface for CoreRenderPass { fn set_pipeline(&mut self, pipeline: &dispatch::DispatchRenderPipeline) { let pipeline = pipeline.as_core(); if let Err(cause) = self .context .0 .render_pass_set_pipeline(&mut self.pass, pipeline.id) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::set_pipeline", ); } } fn set_bind_group( &mut self, index: u32, bind_group: Option<&dispatch::DispatchBindGroup>, offsets: &[crate::DynamicOffset], ) { let bg = bind_group.map(|bg| bg.as_core().id); if let Err(cause) = self.context .0 .render_pass_set_bind_group(&mut self.pass, index, bg, offsets) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::set_bind_group", ); } } fn set_index_buffer( &mut self, buffer: &dispatch::DispatchBuffer, index_format: crate::IndexFormat, offset: crate::BufferAddress, size: Option, ) { let buffer = buffer.as_core(); if let Err(cause) = self.context.0.render_pass_set_index_buffer( &mut self.pass, buffer.id, index_format, offset, size, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::set_index_buffer", ); } } fn set_vertex_buffer( &mut self, slot: u32, buffer: &dispatch::DispatchBuffer, offset: crate::BufferAddress, size: Option, ) { let buffer = buffer.as_core(); if let Err(cause) = self.context.0.render_pass_set_vertex_buffer( &mut self.pass, slot, buffer.id, offset, size, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::set_vertex_buffer", ); } } fn set_immediates(&mut self, offset: u32, data: &[u8]) { if let Err(cause) = self .context .0 .render_pass_set_immediates(&mut self.pass, offset, data) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::set_immediates", ); } } fn set_blend_constant(&mut self, color: crate::Color) { if let Err(cause) = self .context .0 .render_pass_set_blend_constant(&mut self.pass, color) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::set_blend_constant", ); } } fn set_scissor_rect(&mut self, x: u32, y: u32, width: u32, height: u32) { if let Err(cause) = self.context .0 .render_pass_set_scissor_rect(&mut self.pass, x, y, width, height) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::set_scissor_rect", ); } } fn set_viewport( &mut self, x: f32, y: f32, width: f32, height: f32, min_depth: f32, max_depth: f32, ) { if let Err(cause) = self.context.0.render_pass_set_viewport( &mut self.pass, x, y, width, height, min_depth, max_depth, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::set_viewport", ); } } fn set_stencil_reference(&mut self, reference: u32) { if let Err(cause) = self .context .0 .render_pass_set_stencil_reference(&mut self.pass, reference) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::set_stencil_reference", ); } } fn draw(&mut self, vertices: Range, instances: Range) { if let Err(cause) = self.context.0.render_pass_draw( &mut self.pass, vertices.end - vertices.start, instances.end - instances.start, vertices.start, instances.start, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::draw", ); } } fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range) { if let Err(cause) = self.context.0.render_pass_draw_indexed( &mut self.pass, indices.end - indices.start, instances.end - instances.start, indices.start, base_vertex, instances.start, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::draw_indexed", ); } } fn draw_mesh_tasks(&mut self, group_count_x: u32, group_count_y: u32, group_count_z: u32) { if let Err(cause) = self.context.0.render_pass_draw_mesh_tasks( &mut self.pass, group_count_x, group_count_y, group_count_z, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::draw_mesh_tasks", ); } } fn draw_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, ) { let indirect_buffer = indirect_buffer.as_core(); if let Err(cause) = self.context.0.render_pass_draw_indirect( &mut self.pass, indirect_buffer.id, indirect_offset, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::draw_indirect", ); } } fn draw_indexed_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, ) { let indirect_buffer = indirect_buffer.as_core(); if let Err(cause) = self.context.0.render_pass_draw_indexed_indirect( &mut self.pass, indirect_buffer.id, indirect_offset, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::draw_indexed_indirect", ); } } fn draw_mesh_tasks_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, ) { let indirect_buffer = indirect_buffer.as_core(); if let Err(cause) = self.context.0.render_pass_draw_mesh_tasks_indirect( &mut self.pass, indirect_buffer.id, indirect_offset, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::draw_mesh_tasks_indirect", ); } } fn multi_draw_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, count: u32, ) { let indirect_buffer = indirect_buffer.as_core(); if let Err(cause) = self.context.0.render_pass_multi_draw_indirect( &mut self.pass, indirect_buffer.id, indirect_offset, count, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::multi_draw_indirect", ); } } fn multi_draw_indexed_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, count: u32, ) { let indirect_buffer = indirect_buffer.as_core(); if let Err(cause) = self.context.0.render_pass_multi_draw_indexed_indirect( &mut self.pass, indirect_buffer.id, indirect_offset, count, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::multi_draw_indexed_indirect", ); } } fn multi_draw_mesh_tasks_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, count: u32, ) { let indirect_buffer = indirect_buffer.as_core(); if let Err(cause) = self.context.0.render_pass_multi_draw_mesh_tasks_indirect( &mut self.pass, indirect_buffer.id, indirect_offset, count, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::multi_draw_mesh_tasks_indirect", ); } } fn multi_draw_indirect_count( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, count_buffer: &dispatch::DispatchBuffer, count_buffer_offset: crate::BufferAddress, max_count: u32, ) { let indirect_buffer = indirect_buffer.as_core(); let count_buffer = count_buffer.as_core(); if let Err(cause) = self.context.0.render_pass_multi_draw_indirect_count( &mut self.pass, indirect_buffer.id, indirect_offset, count_buffer.id, count_buffer_offset, max_count, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::multi_draw_indirect_count", ); } } fn multi_draw_indexed_indirect_count( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, count_buffer: &dispatch::DispatchBuffer, count_buffer_offset: crate::BufferAddress, max_count: u32, ) { let indirect_buffer = indirect_buffer.as_core(); let count_buffer = count_buffer.as_core(); if let Err(cause) = self .context .0 .render_pass_multi_draw_indexed_indirect_count( &mut self.pass, indirect_buffer.id, indirect_offset, count_buffer.id, count_buffer_offset, max_count, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::multi_draw_indexed_indirect_count", ); } } fn multi_draw_mesh_tasks_indirect_count( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, count_buffer: &dispatch::DispatchBuffer, count_buffer_offset: crate::BufferAddress, max_count: u32, ) { let indirect_buffer = indirect_buffer.as_core(); let count_buffer = count_buffer.as_core(); if let Err(cause) = self .context .0 .render_pass_multi_draw_mesh_tasks_indirect_count( &mut self.pass, indirect_buffer.id, indirect_offset, count_buffer.id, count_buffer_offset, max_count, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::multi_draw_mesh_tasks_indirect_count", ); } } fn insert_debug_marker(&mut self, label: &str) { if let Err(cause) = self .context .0 .render_pass_insert_debug_marker(&mut self.pass, label, 0) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::insert_debug_marker", ); } } fn push_debug_group(&mut self, group_label: &str) { if let Err(cause) = self.context .0 .render_pass_push_debug_group(&mut self.pass, group_label, 0) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::push_debug_group", ); } } fn pop_debug_group(&mut self) { if let Err(cause) = self.context.0.render_pass_pop_debug_group(&mut self.pass) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::pop_debug_group", ); } } fn write_timestamp(&mut self, query_set: &dispatch::DispatchQuerySet, query_index: u32) { let query_set = query_set.as_core(); if let Err(cause) = self.context .0 .render_pass_write_timestamp(&mut self.pass, query_set.id, query_index) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::write_timestamp", ); } } fn begin_occlusion_query(&mut self, query_index: u32) { if let Err(cause) = self .context .0 .render_pass_begin_occlusion_query(&mut self.pass, query_index) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::begin_occlusion_query", ); } } fn end_occlusion_query(&mut self) { if let Err(cause) = self .context .0 .render_pass_end_occlusion_query(&mut self.pass) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::end_occlusion_query", ); } } fn begin_pipeline_statistics_query( &mut self, query_set: &dispatch::DispatchQuerySet, query_index: u32, ) { let query_set = query_set.as_core(); if let Err(cause) = self.context.0.render_pass_begin_pipeline_statistics_query( &mut self.pass, query_set.id, query_index, ) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::begin_pipeline_statistics_query", ); } } fn end_pipeline_statistics_query(&mut self) { if let Err(cause) = self .context .0 .render_pass_end_pipeline_statistics_query(&mut self.pass) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::end_pipeline_statistics_query", ); } } fn execute_bundles( &mut self, render_bundles: &mut dyn Iterator, ) { let temp_render_bundles = render_bundles .map(|rb| rb.as_core().id) .collect::>(); if let Err(cause) = self .context .0 .render_pass_execute_bundles(&mut self.pass, &temp_render_bundles) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::execute_bundles", ); } } } impl Drop for CoreRenderPass { fn drop(&mut self) { if let Err(cause) = self.context.0.render_pass_end(&mut self.pass) { self.context.handle_error( &self.error_sink, cause, self.pass.label(), "RenderPass::end", ); } } } impl dispatch::RenderBundleEncoderInterface for CoreRenderBundleEncoder { fn set_pipeline(&mut self, pipeline: &dispatch::DispatchRenderPipeline) { let pipeline = pipeline.as_core(); wgpu_render_bundle_set_pipeline(&mut self.encoder, pipeline.id) } fn set_bind_group( &mut self, index: u32, bind_group: Option<&dispatch::DispatchBindGroup>, offsets: &[crate::DynamicOffset], ) { let bg = bind_group.map(|bg| bg.as_core().id); unsafe { wgpu_render_bundle_set_bind_group( &mut self.encoder, index, bg, offsets.as_ptr(), offsets.len(), ) } } fn set_index_buffer( &mut self, buffer: &dispatch::DispatchBuffer, index_format: crate::IndexFormat, offset: crate::BufferAddress, size: Option, ) { let buffer = buffer.as_core(); self.encoder .set_index_buffer(buffer.id, index_format, offset, size) } fn set_vertex_buffer( &mut self, slot: u32, buffer: &dispatch::DispatchBuffer, offset: crate::BufferAddress, size: Option, ) { let buffer = buffer.as_core(); wgpu_render_bundle_set_vertex_buffer(&mut self.encoder, slot, buffer.id, offset, size) } fn set_immediates(&mut self, offset: u32, data: &[u8]) { unsafe { wgpu_render_bundle_set_immediates( &mut self.encoder, offset, data.len().try_into().unwrap(), data.as_ptr(), ) } } fn draw(&mut self, vertices: Range, instances: Range) { wgpu_render_bundle_draw( &mut self.encoder, vertices.end - vertices.start, instances.end - instances.start, vertices.start, instances.start, ) } fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range) { wgpu_render_bundle_draw_indexed( &mut self.encoder, indices.end - indices.start, instances.end - instances.start, indices.start, base_vertex, instances.start, ) } fn draw_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, ) { let indirect_buffer = indirect_buffer.as_core(); wgpu_render_bundle_draw_indirect(&mut self.encoder, indirect_buffer.id, indirect_offset) } fn draw_indexed_indirect( &mut self, indirect_buffer: &dispatch::DispatchBuffer, indirect_offset: crate::BufferAddress, ) { let indirect_buffer = indirect_buffer.as_core(); wgpu_render_bundle_draw_indexed_indirect( &mut self.encoder, indirect_buffer.id, indirect_offset, ) } fn finish(self, desc: &crate::RenderBundleDescriptor<'_>) -> dispatch::DispatchRenderBundle where Self: Sized, { let (id, error) = self.context.0.render_bundle_encoder_finish( self.encoder, &desc.map_label(|l| l.map(Borrowed)), None, ); if let Some(err) = error { self.context .handle_error_fatal(err, "RenderBundleEncoder::finish"); } CoreRenderBundle { context: self.context.clone(), id, } .into() } } impl dispatch::RenderBundleInterface for CoreRenderBundle {} impl Drop for CoreRenderBundle { fn drop(&mut self) { self.context.0.render_bundle_drop(self.id) } } impl dispatch::SurfaceInterface for CoreSurface { fn get_capabilities(&self, adapter: &dispatch::DispatchAdapter) -> wgt::SurfaceCapabilities { let adapter = adapter.as_core(); self.context .0 .surface_get_capabilities(self.id, adapter.id) .unwrap_or_default() } fn configure(&self, device: &dispatch::DispatchDevice, config: &crate::SurfaceConfiguration) { let device = device.as_core(); let error = self.context.0.surface_configure(self.id, device.id, config); if let Some(e) = error { self.context .handle_error_nolabel(&device.error_sink, e, "Surface::configure"); } else { *self.configured_device.lock() = Some(device.id); *self.error_sink.lock() = Some(device.error_sink.clone()); } } fn get_current_texture( &self, ) -> ( Option, crate::SurfaceStatus, dispatch::DispatchSurfaceOutputDetail, ) { let error_sink = if let Some(error_sink) = self.error_sink.lock().as_ref() { error_sink.clone() } else { Arc::new(Mutex::new(ErrorSinkRaw::new())) }; let output_detail = CoreSurfaceOutputDetail { context: self.context.clone(), surface_id: self.id, error_sink: error_sink.clone(), } .into(); match self.context.0.surface_get_current_texture(self.id, None) { Ok(wgc::present::SurfaceOutput { status, texture: texture_id, }) => { let data = texture_id .map(|id| CoreTexture { context: self.context.clone(), id, error_sink, }) .map(Into::into); (data, status, output_detail) } Err(err) => { let error_sink = self.error_sink.lock(); match error_sink.as_ref() { Some(error_sink) => { self.context.handle_error_nolabel( error_sink, err, "Surface::get_current_texture_view", ); (None, crate::SurfaceStatus::Validation, output_detail) } None => self .context .handle_error_fatal(err, "Surface::get_current_texture_view"), } } } } } impl Drop for CoreSurface { fn drop(&mut self) { self.context.0.surface_drop(self.id) } } impl dispatch::SurfaceOutputDetailInterface for CoreSurfaceOutputDetail { fn present(&self) { match self.context.0.surface_present(self.surface_id) { Ok(_status) => (), Err(err) => { self.context .handle_error_nolabel(&self.error_sink, err, "Surface::present"); } } } fn texture_discard(&self) { match self.context.0.surface_texture_discard(self.surface_id) { Ok(_status) => (), Err(err) => self .context .handle_error_fatal(err, "Surface::discard_texture"), } } } impl Drop for CoreSurfaceOutputDetail { fn drop(&mut self) { // Discard gets called by the api struct // no-op } } impl dispatch::QueueWriteBufferInterface for CoreQueueWriteBuffer { #[inline] fn len(&self) -> usize { self.mapping.len() } #[inline] unsafe fn write_slice(&mut self) -> WriteOnly<'_, [u8]> { unsafe { self.mapping.write_slice() } } } impl Drop for CoreQueueWriteBuffer { fn drop(&mut self) { // The api struct calls queue.write_staging_buffer // no-op } } impl dispatch::BufferMappedRangeInterface for CoreBufferMappedRange { #[inline] fn len(&self) -> usize { self.size } #[inline] unsafe fn read_slice(&self) -> &[u8] { unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.size) } } #[inline] unsafe fn write_slice(&mut self) -> WriteOnly<'_, [u8]> { unsafe { WriteOnly::new(NonNull::slice_from_raw_parts(self.ptr, self.size)) } } #[cfg(webgpu)] fn as_uint8array(&self) -> &js_sys::Uint8Array { panic!("Only available on WebGPU") } } ================================================ FILE: wgpu/src/cmp.rs ================================================ //! We need to impl `PartialEq`, `Eq`, `PartialOrd`, `Ord`, and `Hash` for all handle types in wgpu. //! //! For types that have some already-unique property, we can use that property to implement these traits. //! //! For types (like WebGPU) that don't have such a property, we generate an identifier and use that. #[cfg(supports_64bit_atomics)] pub use core::sync::atomic::AtomicU64; #[cfg(not(supports_64bit_atomics))] pub use portable_atomic::AtomicU64; use core::{num::NonZeroU64, sync::atomic::Ordering}; static NEXT_ID: AtomicU64 = AtomicU64::new(1); #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Identifier { inner: NonZeroU64, } impl Identifier { pub fn create() -> Self { let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); // Safety: Will take 7000+ years of constant incrementing to overflow. It's fine. let inner = unsafe { NonZeroU64::new_unchecked(id) }; Self { inner } } } /// Implements `PartialEq`, `Eq`, `PartialOrd`, `Ord`, and `Hash` for a type by proxying the operations to a single field. /// /// ```ignore /// impl_eq_ord_hash_proxy!(MyType => .field); /// ``` macro_rules! impl_eq_ord_hash_proxy { ($type:ty => $($access:tt)*) => { impl PartialEq for $type { fn eq(&self, other: &Self) -> bool { self $($access)* == other $($access)* } } impl Eq for $type {} impl PartialOrd for $type { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for $type { fn cmp(&self, other: &Self) -> core::cmp::Ordering { self $($access)*.cmp(&other $($access)*) } } impl core::hash::Hash for $type { fn hash(&self, state: &mut H) { self $($access)*.hash(state) } } }; } /// Implements `PartialEq`, `Eq`, `PartialOrd`, `Ord`, and `Hash` for a type by comparing the addresses of the `Arc`s. /// /// ```ignore /// impl_eq_ord_hash_arc_address!(MyType => .field); /// ``` #[cfg_attr(not(any(wgpu_core, custom)), expect(unused_macros))] macro_rules! impl_eq_ord_hash_arc_address { ($type:ty => $($access:tt)*) => { impl PartialEq for $type { fn eq(&self, other: &Self) -> bool { let address_left = alloc::sync::Arc::as_ptr(&self $($access)*); let address_right = alloc::sync::Arc::as_ptr(&other $($access)*); address_left == address_right } } impl Eq for $type {} impl PartialOrd for $type { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for $type { fn cmp(&self, other: &Self) -> core::cmp::Ordering { let address_left = alloc::sync::Arc::as_ptr(&self $($access)*); let address_right = alloc::sync::Arc::as_ptr(&other $($access)*); address_left.cmp(&address_right) } } impl core::hash::Hash for $type { fn hash(&self, state: &mut H) { let address = alloc::sync::Arc::as_ptr(&self $($access)*); address.hash(state) } } }; } #[cfg_attr(not(any(wgpu_core, custom)), expect(unused_imports))] pub(crate) use {impl_eq_ord_hash_arc_address, impl_eq_ord_hash_proxy}; ================================================ FILE: wgpu/src/dispatch.rs ================================================ //! Infrastructure for dispatching calls to the appropriate "backend". The "backends" are: //! //! - `wgpu_core`: An implementation of the the wgpu api on top of various native graphics APIs. //! - `webgpu`: An implementation of the wgpu api which calls WebGPU directly. //! //! The interface traits are all object safe and listed in the `InterfaceTypes` trait. //! //! The method for dispatching should optimize well if only one backend is //! compiled in, as-if there was no dispatching at all. See the comments on //! [`dispatch_types`] for details. //! //! [`dispatch_types`]: macro.dispatch_types.html #![allow( drop_bounds, reason = "This exists to remind implementors to impl drop." )] #![allow(clippy::too_many_arguments, reason = "It's fine.")] #![allow( missing_docs, clippy::missing_safety_doc, reason = "Interfaces are not documented" )] #![allow( clippy::len_without_is_empty, reason = "trait is minimal, not ergonomic" )] use crate::{Blas, Tlas, WasmNotSend, WasmNotSendSync, WriteOnly}; use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec}; use core::{any::Any, fmt::Debug, future::Future, hash::Hash, ops::Range, pin::Pin}; #[cfg(custom)] use crate::backend::custom::*; #[cfg(webgpu)] use crate::backend::webgpu::*; #[cfg(wgpu_core)] use crate::backend::wgpu_core::*; /// Create a single trait with the given supertraits and a blanket impl for all types that implement them. /// /// This is useful for creating a trait alias as a shorthand. macro_rules! trait_alias { ($name:ident: $($bound:tt)+) => { pub trait $name: $($bound)+ {} impl $name for T {} }; } // Various return futures in the API. trait_alias!(RequestAdapterFuture: Future> + WasmNotSend + 'static); trait_alias!(RequestDeviceFuture: Future> + WasmNotSend + 'static); trait_alias!(PopErrorScopeFuture: Future> + WasmNotSend + 'static); trait_alias!(ShaderCompilationInfoFuture: Future + WasmNotSend + 'static); trait_alias!(EnumerateAdapterFuture: Future> + WasmNotSend + 'static); // We can't use trait aliases here, as you can't convert from a dyn Trait to dyn Supertrait _yet_. #[cfg(send_sync)] pub type BoxDeviceLostCallback = Box; #[cfg(not(send_sync))] pub type BoxDeviceLostCallback = Box; #[cfg(send_sync)] pub type BoxSubmittedWorkDoneCallback = Box; #[cfg(not(send_sync))] pub type BoxSubmittedWorkDoneCallback = Box; #[cfg(send_sync)] pub type BufferMapCallback = Box) + Send + 'static>; #[cfg(not(send_sync))] pub type BufferMapCallback = Box) + 'static>; #[cfg(send_sync)] pub type BlasCompactCallback = Box) + Send + 'static>; #[cfg(not(send_sync))] pub type BlasCompactCallback = Box) + 'static>; // remove when rust 1.86 #[cfg_attr(not(custom), expect(dead_code))] pub trait AsAny { fn as_any(&self) -> &dyn Any; } impl AsAny for T { fn as_any(&self) -> &dyn Any { self } } // Common traits on all the interface traits trait_alias!(CommonTraits: AsAny + Any + Debug + WasmNotSendSync); pub trait InstanceInterface: CommonTraits { fn new(desc: crate::InstanceDescriptor) -> Self where Self: Sized; unsafe fn create_surface( &self, target: crate::SurfaceTargetUnsafe, ) -> Result; fn request_adapter( &self, options: &crate::RequestAdapterOptions<'_, '_>, ) -> Pin>; fn poll_all_devices(&self, force_wait: bool) -> bool; #[cfg(feature = "wgsl")] fn wgsl_language_features(&self) -> crate::WgslLanguageFeatures; fn enumerate_adapters(&self, backends: crate::Backends) -> Pin>; } pub trait AdapterInterface: CommonTraits { fn request_device( &self, desc: &crate::DeviceDescriptor<'_>, ) -> Pin>; fn is_surface_supported(&self, surface: &DispatchSurface) -> bool; fn features(&self) -> crate::Features; fn limits(&self) -> crate::Limits; fn downlevel_capabilities(&self) -> crate::DownlevelCapabilities; fn get_info(&self) -> crate::AdapterInfo; fn get_texture_format_features( &self, format: crate::TextureFormat, ) -> crate::TextureFormatFeatures; fn get_presentation_timestamp(&self) -> crate::PresentationTimestamp; fn cooperative_matrix_properties(&self) -> Vec; } pub trait DeviceInterface: CommonTraits { fn features(&self) -> crate::Features; fn limits(&self) -> crate::Limits; fn adapter_info(&self) -> crate::AdapterInfo; fn create_shader_module( &self, desc: crate::ShaderModuleDescriptor<'_>, shader_bound_checks: crate::ShaderRuntimeChecks, ) -> DispatchShaderModule; unsafe fn create_shader_module_passthrough( &self, desc: &crate::ShaderModuleDescriptorPassthrough<'_>, ) -> DispatchShaderModule; fn create_bind_group_layout( &self, desc: &crate::BindGroupLayoutDescriptor<'_>, ) -> DispatchBindGroupLayout; fn create_bind_group(&self, desc: &crate::BindGroupDescriptor<'_>) -> DispatchBindGroup; fn create_pipeline_layout( &self, desc: &crate::PipelineLayoutDescriptor<'_>, ) -> DispatchPipelineLayout; fn create_render_pipeline( &self, desc: &crate::RenderPipelineDescriptor<'_>, ) -> DispatchRenderPipeline; fn create_mesh_pipeline( &self, desc: &crate::MeshPipelineDescriptor<'_>, ) -> DispatchRenderPipeline; fn create_compute_pipeline( &self, desc: &crate::ComputePipelineDescriptor<'_>, ) -> DispatchComputePipeline; unsafe fn create_pipeline_cache( &self, desc: &crate::PipelineCacheDescriptor<'_>, ) -> DispatchPipelineCache; fn create_buffer(&self, desc: &crate::BufferDescriptor<'_>) -> DispatchBuffer; fn create_texture(&self, desc: &crate::TextureDescriptor<'_>) -> DispatchTexture; fn create_external_texture( &self, desc: &crate::ExternalTextureDescriptor<'_>, planes: &[&crate::TextureView], ) -> DispatchExternalTexture; fn create_blas( &self, desc: &crate::CreateBlasDescriptor<'_>, sizes: crate::BlasGeometrySizeDescriptors, ) -> (Option, DispatchBlas); fn create_tlas(&self, desc: &crate::CreateTlasDescriptor<'_>) -> DispatchTlas; fn create_sampler(&self, desc: &crate::SamplerDescriptor<'_>) -> DispatchSampler; fn create_query_set(&self, desc: &crate::QuerySetDescriptor<'_>) -> DispatchQuerySet; fn create_command_encoder( &self, desc: &crate::CommandEncoderDescriptor<'_>, ) -> DispatchCommandEncoder; fn create_render_bundle_encoder( &self, desc: &crate::RenderBundleEncoderDescriptor<'_>, ) -> DispatchRenderBundleEncoder; fn set_device_lost_callback(&self, device_lost_callback: BoxDeviceLostCallback); fn on_uncaptured_error(&self, handler: Arc); // Returns index on the stack of the pushed error scope. fn push_error_scope(&self, filter: crate::ErrorFilter) -> u32; fn pop_error_scope(&self, index: u32) -> Pin>; unsafe fn start_graphics_debugger_capture(&self); unsafe fn stop_graphics_debugger_capture(&self); fn poll(&self, poll_type: wgt::PollType) -> Result; fn get_internal_counters(&self) -> crate::InternalCounters; fn generate_allocator_report(&self) -> Option; fn destroy(&self); } pub trait QueueInterface: CommonTraits { fn write_buffer(&self, buffer: &DispatchBuffer, offset: crate::BufferAddress, data: &[u8]); fn create_staging_buffer(&self, size: crate::BufferSize) -> Option; fn validate_write_buffer( &self, buffer: &DispatchBuffer, offset: crate::BufferAddress, size: crate::BufferSize, ) -> Option<()>; fn write_staging_buffer( &self, buffer: &DispatchBuffer, offset: crate::BufferAddress, staging_buffer: &DispatchQueueWriteBuffer, ); fn write_texture( &self, texture: crate::TexelCopyTextureInfo<'_>, data: &[u8], data_layout: crate::TexelCopyBufferLayout, size: crate::Extent3d, ); #[cfg(web)] fn copy_external_image_to_texture( &self, source: &crate::CopyExternalImageSourceInfo, dest: crate::CopyExternalImageDestInfo<&crate::api::Texture>, size: crate::Extent3d, ); /// Submit must always drain the iterator, even in the case of error. fn submit(&self, command_buffers: &mut dyn Iterator) -> u64; fn get_timestamp_period(&self) -> f32; fn on_submitted_work_done(&self, callback: BoxSubmittedWorkDoneCallback); fn compact_blas(&self, blas: &DispatchBlas) -> (Option, DispatchBlas); } pub trait ShaderModuleInterface: CommonTraits { fn get_compilation_info(&self) -> Pin>; } pub trait BindGroupLayoutInterface: CommonTraits {} pub trait BindGroupInterface: CommonTraits {} pub trait TextureViewInterface: CommonTraits {} pub trait SamplerInterface: CommonTraits {} pub trait BufferInterface: CommonTraits { fn map_async( &self, mode: crate::MapMode, range: Range, callback: BufferMapCallback, ); fn get_mapped_range(&self, sub_range: Range) -> DispatchBufferMappedRange; fn unmap(&self); fn destroy(&self); } pub trait TextureInterface: CommonTraits { fn create_view(&self, desc: &crate::TextureViewDescriptor<'_>) -> DispatchTextureView; fn destroy(&self); } pub trait ExternalTextureInterface: CommonTraits { fn destroy(&self); } pub trait BlasInterface: CommonTraits { fn prepare_compact_async(&self, callback: BlasCompactCallback); fn ready_for_compaction(&self) -> bool; } pub trait TlasInterface: CommonTraits {} pub trait QuerySetInterface: CommonTraits {} pub trait PipelineLayoutInterface: CommonTraits {} pub trait RenderPipelineInterface: CommonTraits { fn get_bind_group_layout(&self, index: u32) -> DispatchBindGroupLayout; } pub trait ComputePipelineInterface: CommonTraits { fn get_bind_group_layout(&self, index: u32) -> DispatchBindGroupLayout; } pub trait PipelineCacheInterface: CommonTraits { fn get_data(&self) -> Option>; } pub trait CommandEncoderInterface: CommonTraits { fn copy_buffer_to_buffer( &self, source: &DispatchBuffer, source_offset: crate::BufferAddress, destination: &DispatchBuffer, destination_offset: crate::BufferAddress, copy_size: Option, ); fn copy_buffer_to_texture( &self, source: crate::TexelCopyBufferInfo<'_>, destination: crate::TexelCopyTextureInfo<'_>, copy_size: crate::Extent3d, ); fn copy_texture_to_buffer( &self, source: crate::TexelCopyTextureInfo<'_>, destination: crate::TexelCopyBufferInfo<'_>, copy_size: crate::Extent3d, ); fn copy_texture_to_texture( &self, source: crate::TexelCopyTextureInfo<'_>, destination: crate::TexelCopyTextureInfo<'_>, copy_size: crate::Extent3d, ); fn begin_compute_pass(&self, desc: &crate::ComputePassDescriptor<'_>) -> DispatchComputePass; fn begin_render_pass(&self, desc: &crate::RenderPassDescriptor<'_>) -> DispatchRenderPass; fn finish(&mut self) -> DispatchCommandBuffer; fn clear_texture( &self, texture: &DispatchTexture, subresource_range: &crate::ImageSubresourceRange, ); fn clear_buffer( &self, buffer: &DispatchBuffer, offset: crate::BufferAddress, size: Option, ); fn insert_debug_marker(&self, label: &str); fn push_debug_group(&self, label: &str); fn pop_debug_group(&self); fn write_timestamp(&self, query_set: &DispatchQuerySet, query_index: u32); fn resolve_query_set( &self, query_set: &DispatchQuerySet, first_query: u32, query_count: u32, destination: &DispatchBuffer, destination_offset: crate::BufferAddress, ); fn mark_acceleration_structures_built<'a>( &self, blas: &mut dyn Iterator, tlas: &mut dyn Iterator, ); fn build_acceleration_structures<'a>( &self, blas: &mut dyn Iterator>, tlas: &mut dyn Iterator, ); fn transition_resources<'a>( &mut self, buffer_transitions: &mut dyn Iterator>, texture_transitions: &mut dyn Iterator>, ); } pub trait ComputePassInterface: CommonTraits + Drop { fn set_pipeline(&mut self, pipeline: &DispatchComputePipeline); fn set_bind_group( &mut self, index: u32, bind_group: Option<&DispatchBindGroup>, offsets: &[crate::DynamicOffset], ); fn set_immediates(&mut self, offset: u32, data: &[u8]); fn insert_debug_marker(&mut self, label: &str); fn push_debug_group(&mut self, group_label: &str); fn pop_debug_group(&mut self); fn write_timestamp(&mut self, query_set: &DispatchQuerySet, query_index: u32); fn begin_pipeline_statistics_query(&mut self, query_set: &DispatchQuerySet, query_index: u32); fn end_pipeline_statistics_query(&mut self); fn dispatch_workgroups(&mut self, x: u32, y: u32, z: u32); fn dispatch_workgroups_indirect( &mut self, indirect_buffer: &DispatchBuffer, indirect_offset: crate::BufferAddress, ); } pub trait RenderPassInterface: CommonTraits + Drop { fn set_pipeline(&mut self, pipeline: &DispatchRenderPipeline); fn set_bind_group( &mut self, index: u32, bind_group: Option<&DispatchBindGroup>, offsets: &[crate::DynamicOffset], ); fn set_index_buffer( &mut self, buffer: &DispatchBuffer, index_format: crate::IndexFormat, offset: crate::BufferAddress, size: Option, ); fn set_vertex_buffer( &mut self, slot: u32, buffer: &DispatchBuffer, offset: crate::BufferAddress, size: Option, ); fn set_immediates(&mut self, offset: u32, data: &[u8]); fn set_blend_constant(&mut self, color: crate::Color); fn set_scissor_rect(&mut self, x: u32, y: u32, width: u32, height: u32); fn set_viewport( &mut self, x: f32, y: f32, width: f32, height: f32, min_depth: f32, max_depth: f32, ); fn set_stencil_reference(&mut self, reference: u32); fn draw(&mut self, vertices: Range, instances: Range); fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range); fn draw_mesh_tasks(&mut self, group_count_x: u32, group_count_y: u32, group_count_z: u32); fn draw_indirect( &mut self, indirect_buffer: &DispatchBuffer, indirect_offset: crate::BufferAddress, ); fn draw_indexed_indirect( &mut self, indirect_buffer: &DispatchBuffer, indirect_offset: crate::BufferAddress, ); fn draw_mesh_tasks_indirect( &mut self, indirect_buffer: &DispatchBuffer, indirect_offset: crate::BufferAddress, ); fn multi_draw_indirect( &mut self, indirect_buffer: &DispatchBuffer, indirect_offset: crate::BufferAddress, count: u32, ); fn multi_draw_indexed_indirect( &mut self, indirect_buffer: &DispatchBuffer, indirect_offset: crate::BufferAddress, count: u32, ); fn multi_draw_indirect_count( &mut self, indirect_buffer: &DispatchBuffer, indirect_offset: crate::BufferAddress, count_buffer: &DispatchBuffer, count_buffer_offset: crate::BufferAddress, max_count: u32, ); fn multi_draw_mesh_tasks_indirect( &mut self, indirect_buffer: &DispatchBuffer, indirect_offset: crate::BufferAddress, count: u32, ); fn multi_draw_indexed_indirect_count( &mut self, indirect_buffer: &DispatchBuffer, indirect_offset: crate::BufferAddress, count_buffer: &DispatchBuffer, count_buffer_offset: crate::BufferAddress, max_count: u32, ); fn multi_draw_mesh_tasks_indirect_count( &mut self, indirect_buffer: &DispatchBuffer, indirect_offset: crate::BufferAddress, count_buffer: &DispatchBuffer, count_buffer_offset: crate::BufferAddress, max_count: u32, ); fn insert_debug_marker(&mut self, label: &str); fn push_debug_group(&mut self, group_label: &str); fn pop_debug_group(&mut self); fn write_timestamp(&mut self, query_set: &DispatchQuerySet, query_index: u32); fn begin_occlusion_query(&mut self, query_index: u32); fn end_occlusion_query(&mut self); fn begin_pipeline_statistics_query(&mut self, query_set: &DispatchQuerySet, query_index: u32); fn end_pipeline_statistics_query(&mut self); fn execute_bundles(&mut self, render_bundles: &mut dyn Iterator); } pub trait RenderBundleEncoderInterface: CommonTraits { fn set_pipeline(&mut self, pipeline: &DispatchRenderPipeline); fn set_bind_group( &mut self, index: u32, bind_group: Option<&DispatchBindGroup>, offsets: &[crate::DynamicOffset], ); fn set_index_buffer( &mut self, buffer: &DispatchBuffer, index_format: crate::IndexFormat, offset: crate::BufferAddress, size: Option, ); fn set_vertex_buffer( &mut self, slot: u32, buffer: &DispatchBuffer, offset: crate::BufferAddress, size: Option, ); fn set_immediates(&mut self, offset: u32, data: &[u8]); fn draw(&mut self, vertices: Range, instances: Range); fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range); fn draw_indirect( &mut self, indirect_buffer: &DispatchBuffer, indirect_offset: crate::BufferAddress, ); fn draw_indexed_indirect( &mut self, indirect_buffer: &DispatchBuffer, indirect_offset: crate::BufferAddress, ); fn finish(self, desc: &crate::RenderBundleDescriptor<'_>) -> DispatchRenderBundle where Self: Sized; } pub trait CommandBufferInterface: CommonTraits {} pub trait RenderBundleInterface: CommonTraits {} pub trait SurfaceInterface: CommonTraits { fn get_capabilities(&self, adapter: &DispatchAdapter) -> crate::SurfaceCapabilities; fn configure(&self, device: &DispatchDevice, config: &crate::SurfaceConfiguration); fn get_current_texture( &self, ) -> ( Option, crate::SurfaceStatus, DispatchSurfaceOutputDetail, ); } pub trait SurfaceOutputDetailInterface: CommonTraits { fn present(&self); fn texture_discard(&self); } pub trait QueueWriteBufferInterface: CommonTraits { fn len(&self) -> usize; /// # Safety /// /// Must only be used on write, not read, mappings. unsafe fn write_slice(&mut self) -> WriteOnly<'_, [u8]>; } pub trait BufferMappedRangeInterface: CommonTraits { fn len(&self) -> usize; /// # Safety /// /// Must only be used on read, not write, mappings. unsafe fn read_slice(&self) -> &[u8]; /// # Safety /// /// Must only be used on write, not read, mappings. unsafe fn write_slice(&mut self) -> WriteOnly<'_, [u8]>; #[cfg(webgpu)] fn as_uint8array(&self) -> &js_sys::Uint8Array; } /// Generates a dispatch type for some `wgpu` API type. /// /// Invocations of this macro take one of the following forms: /// /// ```ignore /// dispatch_types! {mut type D: I = Core, Web, Dyn } /// dispatch_types! {ref type D: I = Core, Web, Dyn } /// ``` /// /// This defines `D` as a type that dereferences to a `dyn I` trait object. Most uses of /// `D` in the rest of this crate just call the methods from the `dyn I` object, not from /// `D` itself. /// /// Internally, `D` is an enum with up to three variants holding values of type `Core`, /// `Web`, and `Dyn`, all of which must implement `I`. `Core`, `Web` and `Dyn` are the /// types from the `wgpu_core`, `webgpu`, and `custom` submodules of `wgpu::backend` that /// correspond to `D`. The macro generates `Deref` and `DerefMut` implementations that /// match on this enum and produce a `dyn I` reference for each variant. /// /// The macro's `mut type` form defines `D` as the unique owner of the backend type, with /// a `DerefMut` implementation, and `as_*_mut` methods that return `&mut` references. /// This `D` does not implement `Clone`. /// /// The macro's `ref type` form defines `D` to hold an `Arc` pointing to the backend type, /// permitting `Clone` and `Deref`, but losing exclusive, mutable access. /// /// For example: /// /// ```ignore /// dispatch_types! {ref type DispatchBuffer: BufferInterface = /// CoreBuffer, WebBuffer, DynBuffer} /// ``` /// /// This defines `DispatchBuffer` as a type that dereferences to `&dyn BufferInterface`, /// which has methods like `map_async` and `destroy`. The enum would be: /// /// ```ignore /// pub enum DispatchBuffer { /// #[cfg(wgpu_core)] /// Core(Arc), /// #[cfg(webgpu)] /// WebGPU(WebBuffer), /// #[cfg(custom)] /// Custom(DynBuffer), /// } /// ``` /// /// This macro also defines `as_*` methods so that the backend implementations can /// dereference other arguments. /// /// ## Devirtualization /// /// The dispatch types generated by this macro are carefully designed to allow the /// compiler to completely devirtualize calls in most circumstances. /// /// Note that every variant of the enum generated by this macro is under a `#[cfg]`. /// Naturally, the `match` expressions in the `Deref` and `DerefMut` implementations have /// matching `#[cfg]` attributes on each match arm. /// /// In practice, when `wgpu`'s `"custom"` feature is not enabled, there is usually only /// one variant in the `enum`, making it effectively a newtype around the sole variant's /// data: it has no discriminant to branch on, and the `match` expressions are removed /// entirely by the compiler. /// /// In this case, when we invoke a method from the interface trait `I` on a dispatch type, /// the `Deref` and `DerefMut` implementations' `match` statements build a `&dyn I` for /// the data, on which we immediately invoke a method. The vtable is a constant, allowing /// the Rust compiler to turn the `dyn` method call into an ordinary method call. This /// creates opportunities for inlining. /// /// Similarly, the `as_*` methods are free when there is only one backend. macro_rules! dispatch_types { ( ref type $name:ident: $interface:ident = $core_type:ident,$webgpu_type:ident,$custom_type:ident ) => { #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] pub enum $name { #[cfg(wgpu_core)] Core(Arc<$core_type>), #[cfg(webgpu)] WebGPU($webgpu_type), #[allow(clippy::allow_attributes, private_interfaces)] #[cfg(custom)] Custom($custom_type), } impl $name { #[cfg(wgpu_core)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_core(&self) -> &$core_type { match self { Self::Core(value) => value, _ => panic!(concat!(stringify!($name), " is not core")), } } #[cfg(wgpu_core)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_core_opt(&self) -> Option<&$core_type> { match self { Self::Core(value) => Some(value), _ => None, } } #[cfg(custom)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_custom(&self) -> Option<&T> { match self { Self::Custom(value) => value.downcast(), _ => None, } } #[cfg(webgpu)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_webgpu(&self) -> &$webgpu_type { match self { Self::WebGPU(value) => value, _ => panic!(concat!(stringify!($name), " is not webgpu")), } } #[cfg(webgpu)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_webgpu_opt(&self) -> Option<&$webgpu_type> { match self { Self::WebGPU(value) => Some(value), _ => None, } } #[cfg(custom)] #[inline] pub fn custom(t: T) -> Self { Self::Custom($custom_type::new(t)) } } #[cfg(wgpu_core)] impl From<$core_type> for $name { #[inline] fn from(value: $core_type) -> Self { Self::Core(Arc::new(value)) } } #[cfg(webgpu)] impl From<$webgpu_type> for $name { #[inline] fn from(value: $webgpu_type) -> Self { Self::WebGPU(value) } } impl core::ops::Deref for $name { type Target = dyn $interface; #[inline] fn deref(&self) -> &Self::Target { match self { #[cfg(wgpu_core)] Self::Core(value) => value.as_ref(), #[cfg(webgpu)] Self::WebGPU(value) => value, #[cfg(custom)] Self::Custom(value) => value.deref(), #[cfg(not(any(wgpu_core, webgpu)))] _ => panic!("No context available. You need to enable one of wgpu's backend feature build flags."), } } } }; ( mut type $name:ident: $interface:ident = $core_type:ident,$webgpu_type:ident,$custom_type:ident ) => { #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum $name { #[cfg(wgpu_core)] Core($core_type), #[cfg(webgpu)] WebGPU($webgpu_type), #[allow(clippy::allow_attributes, private_interfaces)] #[cfg(custom)] Custom($custom_type), } impl $name { #[cfg(wgpu_core)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_core(&self) -> &$core_type { match self { Self::Core(value) => value, _ => panic!(concat!(stringify!($name), " is not core")), } } #[cfg(wgpu_core)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_core_mut(&mut self) -> &mut $core_type { match self { Self::Core(value) => value, _ => panic!(concat!(stringify!($name), " is not core")), } } #[cfg(wgpu_core)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_core_opt(&self) -> Option<&$core_type> { match self { Self::Core(value) => Some(value), _ => None, } } #[cfg(wgpu_core)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_core_mut_opt( &mut self, ) -> Option<&mut $core_type> { match self { Self::Core(value) => Some(value), _ => None, } } #[cfg(custom)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_custom(&self) -> Option<&T> { match self { Self::Custom(value) => value.downcast(), _ => None, } } #[cfg(webgpu)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_webgpu(&self) -> &$webgpu_type { match self { Self::WebGPU(value) => value, _ => panic!(concat!(stringify!($name), " is not webgpu")), } } #[cfg(webgpu)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_webgpu_mut(&mut self) -> &mut $webgpu_type { match self { Self::WebGPU(value) => value, _ => panic!(concat!(stringify!($name), " is not webgpu")), } } #[cfg(webgpu)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_webgpu_opt(&self) -> Option<&$webgpu_type> { match self { Self::WebGPU(value) => Some(value), _ => None, } } #[cfg(webgpu)] #[inline] #[allow(clippy::allow_attributes, unused)] pub fn as_webgpu_mut_opt( &mut self, ) -> Option<&mut $webgpu_type> { match self { Self::WebGPU(value) => Some(value), _ => None, } } #[cfg(custom)] #[inline] pub fn custom(t: T) -> Self { Self::Custom($custom_type::new(t)) } } #[cfg(wgpu_core)] impl From<$core_type> for $name { #[inline] fn from(value: $core_type) -> Self { Self::Core(value) } } #[cfg(webgpu)] impl From<$webgpu_type> for $name { #[inline] fn from(value: $webgpu_type) -> Self { Self::WebGPU(value) } } impl core::ops::Deref for $name { type Target = dyn $interface; #[inline] fn deref(&self) -> &Self::Target { match self { #[cfg(wgpu_core)] Self::Core(value) => value, #[cfg(webgpu)] Self::WebGPU(value) => value, #[cfg(custom)] Self::Custom(value) => value.deref(), #[cfg(not(any(wgpu_core, webgpu)))] _ => panic!("No context available. You need to enable one of wgpu's backend feature build flags."), } } } impl core::ops::DerefMut for $name { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { match self { #[cfg(wgpu_core)] Self::Core(value) => value, #[cfg(webgpu)] Self::WebGPU(value) => value, #[cfg(custom)] Self::Custom(value) => value.deref_mut(), #[cfg(not(any(wgpu_core, webgpu)))] _ => panic!("No context available. You need to enable one of wgpu's backend feature build flags."), } } } }; } dispatch_types! {ref type DispatchInstance: InstanceInterface = ContextWgpuCore, ContextWebGpu, DynContext} dispatch_types! {ref type DispatchAdapter: AdapterInterface = CoreAdapter, WebAdapter, DynAdapter} dispatch_types! {ref type DispatchDevice: DeviceInterface = CoreDevice, WebDevice, DynDevice} dispatch_types! {ref type DispatchQueue: QueueInterface = CoreQueue, WebQueue, DynQueue} dispatch_types! {ref type DispatchShaderModule: ShaderModuleInterface = CoreShaderModule, WebShaderModule, DynShaderModule} dispatch_types! {ref type DispatchBindGroupLayout: BindGroupLayoutInterface = CoreBindGroupLayout, WebBindGroupLayout, DynBindGroupLayout} dispatch_types! {ref type DispatchBindGroup: BindGroupInterface = CoreBindGroup, WebBindGroup, DynBindGroup} dispatch_types! {ref type DispatchTextureView: TextureViewInterface = CoreTextureView, WebTextureView, DynTextureView} dispatch_types! {ref type DispatchSampler: SamplerInterface = CoreSampler, WebSampler, DynSampler} dispatch_types! {ref type DispatchBuffer: BufferInterface = CoreBuffer, WebBuffer, DynBuffer} dispatch_types! {ref type DispatchTexture: TextureInterface = CoreTexture, WebTexture, DynTexture} dispatch_types! {ref type DispatchExternalTexture: ExternalTextureInterface = CoreExternalTexture, WebExternalTexture, DynExternalTexture} dispatch_types! {ref type DispatchBlas: BlasInterface = CoreBlas, WebBlas, DynBlas} dispatch_types! {ref type DispatchTlas: TlasInterface = CoreTlas, WebTlas, DynTlas} dispatch_types! {ref type DispatchQuerySet: QuerySetInterface = CoreQuerySet, WebQuerySet, DynQuerySet} dispatch_types! {ref type DispatchPipelineLayout: PipelineLayoutInterface = CorePipelineLayout, WebPipelineLayout, DynPipelineLayout} dispatch_types! {ref type DispatchRenderPipeline: RenderPipelineInterface = CoreRenderPipeline, WebRenderPipeline, DynRenderPipeline} dispatch_types! {ref type DispatchComputePipeline: ComputePipelineInterface = CoreComputePipeline, WebComputePipeline, DynComputePipeline} dispatch_types! {ref type DispatchPipelineCache: PipelineCacheInterface = CorePipelineCache, WebPipelineCache, DynPipelineCache} dispatch_types! {mut type DispatchCommandEncoder: CommandEncoderInterface = CoreCommandEncoder, WebCommandEncoder, DynCommandEncoder} dispatch_types! {mut type DispatchComputePass: ComputePassInterface = CoreComputePass, WebComputePassEncoder, DynComputePass} dispatch_types! {mut type DispatchRenderPass: RenderPassInterface = CoreRenderPass, WebRenderPassEncoder, DynRenderPass} dispatch_types! {mut type DispatchCommandBuffer: CommandBufferInterface = CoreCommandBuffer, WebCommandBuffer, DynCommandBuffer} dispatch_types! {mut type DispatchRenderBundleEncoder: RenderBundleEncoderInterface = CoreRenderBundleEncoder, WebRenderBundleEncoder, DynRenderBundleEncoder} dispatch_types! {ref type DispatchRenderBundle: RenderBundleInterface = CoreRenderBundle, WebRenderBundle, DynRenderBundle} dispatch_types! {ref type DispatchSurface: SurfaceInterface = CoreSurface, WebSurface, DynSurface} dispatch_types! {ref type DispatchSurfaceOutputDetail: SurfaceOutputDetailInterface = CoreSurfaceOutputDetail, WebSurfaceOutputDetail, DynSurfaceOutputDetail} dispatch_types! {mut type DispatchQueueWriteBuffer: QueueWriteBufferInterface = CoreQueueWriteBuffer, WebQueueWriteBuffer, DynQueueWriteBuffer} dispatch_types! {mut type DispatchBufferMappedRange: BufferMappedRangeInterface = CoreBufferMappedRange, WebBufferMappedRange, DynBufferMappedRange} ================================================ FILE: wgpu/src/lib.rs ================================================ //! `wgpu` is a cross-platform, safe, pure-Rust graphics API. It runs natively on //! Vulkan, Metal, D3D12, and OpenGL; and on top of WebGL2 and WebGPU on wasm. //! //! The API is based on the [WebGPU standard][webgpu], but is a fully native Rust library. //! It serves as the core of the WebGPU integration in Firefox, Servo, and Deno. //! //! [webgpu]: https://gpuweb.github.io/gpuweb/ //! //! ## Getting Started //! //! The main entry point to the API is the [`Instance`] type, from which you can create [`Adapter`], [`Device`], and [`Surface`]. //! //! If you are new to `wgpu` and graphics programming, we recommend starting with [Learn Wgpu]. //! //! //! Additionally, [WebGPU Fundamentals] is a tutorial for WebGPU which is very similar to our API, minus differences between Rust and Javascript. //! //! We have a [wiki](https://github.com/gfx-rs/wgpu/wiki) which has information on useful architecture patterns, debugging tips, and more getting started information. //! //! There are examples for this version [available on GitHub](https://github.com/gfx-rs/wgpu/tree/v29/examples#readme). //! //! The API is refcounted, so all handles are cloneable, and if you create a resource which references another, //! it will automatically keep dependent resources alive. //! //! `wgpu` uses the coordinate systems of D3D and Metal. Depth ranges from [0, 1]. //! //! | Render | Texture | //! | --------------------- | ---------------------- | //! | ![render_coordinates] | ![texture_coordinates] | //! //! `wgpu`'s MSRV is **1.87**. //! //! [Learn Wgpu]: https://sotrh.github.io/learn-wgpu/ //! [WebGPU Fundamentals]: https://webgpufundamentals.org/ //! [render_coordinates]: https://raw.githubusercontent.com/gfx-rs/wgpu/refs/heads/v29/docs/render_coordinates.png //! [texture_coordinates]: https://raw.githubusercontent.com/gfx-rs/wgpu/refs/heads/v29/docs/texture_coordinates.png //! //! ## Extension Specifications //! //! While the core of `wgpu` is based on the WebGPU standard, we also support extensions that allow for features that the standard does not have yet. //! For high-level documentation on how to use these extensions, see documentation on [`Features`] or the relevant specification: //! //! 🧪EXPERIMENTAL🧪 APIs are subject to change and may allow undefined behavior if used incorrectly. //! //! - 🧪EXPERIMENTAL🧪 [Ray Tracing](https://github.com/gfx-rs/wgpu/blob/v29/docs/api-specs/ray_tracing.md). //! - 🧪EXPERIMENTAL🧪 [Mesh Shading](https://github.com/gfx-rs/wgpu/blob/v29/docs/api-specs/mesh_shading.md). //! //! ## Shader Support //! //! `wgpu` can consume shaders in [WGSL](https://gpuweb.github.io/gpuweb/wgsl/), SPIR-V, and GLSL. //! Both [HLSL](https://github.com/Microsoft/DirectXShaderCompiler) and [GLSL](https://github.com/KhronosGroup/glslang) //! have compilers to target SPIR-V. All of these shader languages can be used with any backend as we handle all of the conversions. Additionally, support for these shader inputs is not going away. //! //! While WebGPU does not support any shading language other than WGSL, we will automatically convert your //! non-WGSL shaders if you're running on WebGPU. //! //! WGSL is always supported by default, but GLSL and SPIR-V need features enabled to compile in support. //! //! To enable WGSL shaders, enable the `wgsl` feature of `wgpu` (enabled by default). //! To enable SPIR-V shaders, enable the `spirv` feature of `wgpu`. //! To enable GLSL shaders, enable the `glsl` feature of `wgpu`. //! //! ## Feature flags #![doc = document_features::document_features!()] //! //! ### Feature Aliases //! //! These features aren't actually features on the crate itself, but a convenient shorthand for //! complicated cases. //! //! - **`wgpu_core`** --- Enabled when there is any non-webgpu backend enabled on the platform. //! - **`naga`** --- Enabled when target `glsl` or `spirv` input is enabled, or when `wgpu_core` is enabled. //! #![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc(html_logo_url = "https://raw.githubusercontent.com/gfx-rs/wgpu/trunk/logo.png")] #![warn( clippy::alloc_instead_of_core, clippy::allow_attributes, clippy::std_instead_of_alloc, clippy::std_instead_of_core, missing_docs, rust_2018_idioms, unsafe_op_in_unsafe_fn )] #![allow( // We need to investiagate these. clippy::large_enum_variant, // These degrade readability significantly. clippy::bool_assert_comparison, clippy::bool_comparison, )] // NOTE: Keep this in sync with `wgpu-core`. #![cfg_attr(not(send_sync), allow(clippy::arc_with_non_send_sync))] #![cfg_attr(not(any(wgpu_core, webgpu)), allow(unused))] extern crate alloc; #[cfg(any(std, test))] extern crate std; #[cfg(wgpu_core)] pub extern crate wgpu_core as wgc; #[cfg(wgpu_core)] pub extern crate wgpu_hal as hal; pub extern crate wgpu_types as wgt; // // // Modules // // mod api; mod backend; mod cmp; mod dispatch; mod macros; pub mod util; // // // Public re-exports // // #[cfg(custom)] pub use backend::custom; pub use api::*; pub use wgt::{ AdapterInfo, AddressMode, AllocatorReport, AstcBlock, AstcChannel, Backend, BackendOptions, Backends, BindGroupLayoutEntry, BindingType, BlendComponent, BlendFactor, BlendOperation, BlendState, BufferAddress, BufferBindingType, BufferSize, BufferTextureCopyInfo, BufferTransition, BufferUsages, BufferUses, Color, ColorTargetState, ColorWrites, CommandBufferDescriptor, CompareFunction, CompositeAlphaMode, CooperativeMatrixProperties, CooperativeScalarType, CopyExternalImageDestInfo, CoreCounters, DepthBiasState, DepthStencilState, DeviceLostReason, DeviceType, DownlevelCapabilities, DownlevelFlags, DownlevelLimits, Dx12BackendOptions, Dx12Compiler, Dx12SwapchainKind, Dx12UseFrameLatencyWaitableObject, DxcShaderModel, DynamicOffset, ExperimentalFeatures, Extent3d, ExternalTextureFormat, ExternalTextureTransferFunction, Face, Features, FeaturesWGPU, FeaturesWebGPU, FilterMode, ForceShaderModelToken, FrontFace, GlBackendOptions, GlDebugFns, GlFenceBehavior, Gles3MinorVersion, HalCounters, ImageSubresourceRange, IndexFormat, InstanceDescriptor, InstanceFlags, InternalCounters, Limits, LoadOpDontCare, MemoryBudgetThresholds, MemoryHints, MipmapFilterMode, MultisampleState, NoopBackendOptions, Origin2d, Origin3d, PipelineStatisticsTypes, PollError, PollStatus, PolygonMode, PowerPreference, PredefinedColorSpace, PresentMode, PresentationTimestamp, PrimitiveState, PrimitiveTopology, QueryType, RenderBundleDepthStencil, RequestAdapterError, SamplerBindingType, SamplerBorderColor, ShaderLocation, ShaderModel, ShaderRuntimeChecks, ShaderStages, StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, SurfaceCapabilities, SurfaceStatus, TexelCopyBufferLayout, TextureAspect, TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, TextureSampleType, TextureTransition, TextureUsages, TextureUses, TextureViewDimension, Trace, VertexAttribute, VertexFormat, VertexStepMode, WasmNotSend, WasmNotSendSync, WasmNotSync, WriteOnly, WriteOnlyIter, COPY_BUFFER_ALIGNMENT, COPY_BYTES_PER_ROW_ALIGNMENT, IMMEDIATE_DATA_ALIGNMENT, MAP_ALIGNMENT, MAXIMUM_SUBGROUP_MAX_SIZE, MINIMUM_SUBGROUP_MIN_SIZE, QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, QUERY_SIZE, VERTEX_ALIGNMENT, }; #[expect(deprecated)] pub use wgt::VERTEX_STRIDE_ALIGNMENT; // wasm-only types, we try to keep as many types non-platform // specific, but these need to depend on web-sys. #[cfg(web)] pub use wgt::{CopyExternalImageSourceInfo, ExternalImageSource}; /// Re-export of our `naga` dependency. /// #[cfg(wgpu_core)] #[cfg_attr(docsrs, doc(cfg(any(wgpu_core, naga))))] // We re-export wgpu-core's re-export of naga, as we may not have direct access to it. pub use ::wgc::naga; /// Re-export of our `naga` dependency. /// #[cfg(all(not(wgpu_core), naga))] #[cfg_attr(docsrs, doc(cfg(any(wgpu_core, naga))))] // If that's not available, we re-export our own. pub use naga; /// Re-export of our `raw-window-handle` dependency. /// pub use raw_window_handle as rwh; /// Re-export of our `web-sys` dependency. /// #[cfg(web)] pub use web_sys; #[doc(hidden)] pub use macros::helpers as __macro_helpers; ================================================ FILE: wgpu/src/macros/be-aligned.spv ================================================ #"3D ================================================ FILE: wgpu/src/macros/le-aligned.spv ================================================ #D3" ================================================ FILE: wgpu/src/macros/mod.rs ================================================ //! Convenience macros #[cfg(doc)] use crate::{VertexAttribute, VertexBufferLayout, VertexFormat}; /// Macro to produce an array of [`VertexAttribute`]. /// /// The input is a sequence of pairs of shader locations (expression of type [`u32`]) and /// variant names of [`VertexFormat`]. /// /// The return value has type `[VertexAttribute; N]`, where `N` is the number of inputs. /// /// Offsets are calculated automatically, /// using the assumption that there is no padding or other data between attributes. /// /// # Example /// /// ``` /// // Suppose that this is our vertex format: /// #[repr(C, packed)] /// struct Vertex { /// foo: [f32; 2], /// bar: f32, /// baz: [u16; 4], /// } /// /// // Then these attributes match it: /// let attrs = wgpu::vertex_attr_array![ /// 0 => Float32x2, /// 1 => Float32, /// 2 => Uint16x4, /// ]; /// /// // Here's the full data structure the macro produced: /// use wgpu::{VertexAttribute as A, VertexFormat as F}; /// assert_eq!(attrs, [ /// A { format: F::Float32x2, offset: 0, shader_location: 0, }, /// A { format: F::Float32, offset: 8, shader_location: 1, }, /// A { format: F::Uint16x4, offset: 12, shader_location: 2, }, /// ]); /// ``` /// /// See [`VertexBufferLayout`] for an example building on this one. #[macro_export] macro_rules! vertex_attr_array { ($($location:expr => $format:ident),* $(,)?) => { $crate::_vertex_attr_array_helper!([] ; 0; $($location => $format ,)*) }; } #[doc(hidden)] #[macro_export] macro_rules! _vertex_attr_array_helper { ([$($t:expr,)*] ; $off:expr ;) => { [$($t,)*] }; ([$($t:expr,)*] ; $off:expr ; $location:expr => $format:ident, $($ll:expr => $ii:ident ,)*) => { $crate::_vertex_attr_array_helper!( [$($t,)* $crate::VertexAttribute { format: $crate::VertexFormat :: $format, offset: $off, shader_location: $location, },]; $off + $crate::VertexFormat :: $format.size(); $($ll => $ii ,)* ) }; } #[test] fn test_vertex_attr_array() { let attrs = vertex_attr_array![0 => Float32x2, 3 => Uint16x4]; // VertexAttribute does not support PartialEq, so we cannot test directly assert_eq!(attrs.len(), 2); assert_eq!(attrs[0].offset, 0); assert_eq!(attrs[0].shader_location, 0); assert_eq!(attrs[1].offset, size_of::<(f32, f32)>() as u64); assert_eq!(attrs[1].shader_location, 3); } #[macro_export] #[doc(hidden)] macro_rules! include_spirv_source { ($($token:tt)*) => { { const SPIRV_SOURCE: [ u8; $crate::__macro_helpers::include_bytes!($($token)*).len() ] = *$crate::__macro_helpers::include_bytes!($($token)*); const SPIRV_LEN: usize = SPIRV_SOURCE.len() / 4; const SPIRV_WORDS: [u32; SPIRV_LEN] = $crate::util::make_spirv_const(SPIRV_SOURCE); &SPIRV_WORDS } } } #[test] fn make_spirv_le_pass() { static SPIRV: &[u32] = include_spirv_source!("le-aligned.spv"); assert_eq!(SPIRV, &[0x07230203, 0x11223344]); } #[test] fn make_spirv_be_pass() { static SPIRV: &[u32] = include_spirv_source!("be-aligned.spv"); assert_eq!(SPIRV, &[0x07230203, 0x11223344]); } /// Macro to load a SPIR-V module statically. /// /// It ensures the word alignment as well as the magic number. /// /// Return type: [`crate::ShaderModuleDescriptor`] #[macro_export] #[cfg(feature = "spirv")] macro_rules! include_spirv { ($($token:tt)*) => { { $crate::ShaderModuleDescriptor { label: Some($($token)*), source: $crate::ShaderSource::SpirV( $crate::__macro_helpers::Cow::Borrowed($crate::include_spirv_source!($($token)*)) ), } } }; } #[cfg(all(feature = "spirv", test))] #[expect(dead_code)] static SPIRV: crate::ShaderModuleDescriptor<'_> = include_spirv!("le-aligned.spv"); /// Macro to load raw SPIR-V data statically, for use with [`Features::PASSTHROUGH_SHADERS`]. /// /// It ensures the word alignment as well as the magic number. /// /// [`Features::PASSTHROUGH_SHADERS`]: crate::Features::PASSTHROUGH_SHADERS #[macro_export] macro_rules! include_spirv_raw { ($($token:tt)*) => { { $crate::ShaderModuleDescriptorPassthrough { label: $crate::__macro_helpers::Some($($token)*), spirv: Some($crate::__macro_helpers::Cow::Borrowed($crate::include_spirv_source!($($token)*))), // This is unused for SPIR-V num_workgroups: (0, 0, 0), dxil: None, metallib: None, msl: None, hlsl: None, glsl: None, wgsl: None, } } }; } #[cfg(test)] #[expect(dead_code)] static SPIRV_RAW: crate::ShaderModuleDescriptorPassthrough<'_> = include_spirv_raw!("le-aligned.spv"); /// Load WGSL source code from a file at compile time. /// /// The loaded path is relative to the path of the file containing the macro call, in the same way /// as [`include_str!`] operates. /// /// ```ignore /// fn main() { /// let module: ShaderModuleDescriptor = include_wgsl!("shader.wgsl"); /// } /// ``` #[macro_export] macro_rules! include_wgsl { ($($token:tt)*) => { { $crate::ShaderModuleDescriptor { label: $crate::__macro_helpers::Some($($token)*), source: $crate::ShaderSource::Wgsl($crate::__macro_helpers::Cow::Borrowed($crate::__macro_helpers::include_str!($($token)*))), } } }; } // Macros which help us generate the documentation of which hal types correspond to which backend. // // Because all backends are not compiled into the program, we cannot link to them in all situations, // we need to only link to the types if the backend is compiled in. These are used in #[doc] attributes // so cannot have more than one line, so cannot use internal cfgs. /// Helper macro to generate the documentation for dx12 hal methods, referencing the hal type. #[cfg(dx12)] macro_rules! hal_type_dx12 { ($ty: literal) => { concat!("- [`hal::api::Dx12`] uses [`hal::dx12::", $ty, "`]") }; } /// Helper macro to generate the documentation for dx12 hal methods, referencing the hal type. #[cfg(not(dx12))] macro_rules! hal_type_dx12 { ($ty: literal) => { concat!("- `hal::api::Dx12` uses `hal::dx12::", $ty, "`") }; } pub(crate) use hal_type_dx12; /// Helper macro to generate the documentation for metal hal methods, referencing the hal type. #[cfg(metal)] macro_rules! hal_type_metal { ($ty: literal) => { concat!("- [`hal::api::Metal`] uses [`hal::metal::", $ty, "`]") }; } /// Helper macro to generate the documentation for metal hal methods, referencing the hal type. #[cfg(not(metal))] macro_rules! hal_type_metal { ($ty: literal) => { concat!("- `hal::api::Metal` uses `hal::metal::", $ty, "`") }; } pub(crate) use hal_type_metal; /// Helper macro to generate the documentation for vulkan hal methods, referencing the hal type. #[cfg(vulkan)] macro_rules! hal_type_vulkan { ($ty: literal) => { concat!("- [`hal::api::Vulkan`] uses [`hal::vulkan::", $ty, "`]") }; } /// Helper macro to generate the documentation for vulkan hal methods, referencing the hal type. #[cfg(not(vulkan))] macro_rules! hal_type_vulkan { ($ty: literal) => { concat!("- `hal::api::Vulkan` uses `hal::vulkan::", $ty, "`") }; } pub(crate) use hal_type_vulkan; /// Helper macro to generate the documentation for gles hal methods, referencing the hal type. #[cfg(gles)] macro_rules! hal_type_gles { ($ty: literal) => { concat!("- [`hal::api::Gles`] uses [`hal::gles::", $ty, "`]") }; } /// Helper macro to generate the documentation for gles hal methods, referencing the hal type. #[cfg(not(gles))] macro_rules! hal_type_gles { ($ty: literal) => { concat!("- `hal::api::Gles` uses `hal::gles::", $ty, "`") }; } pub(crate) use hal_type_gles; #[doc(hidden)] pub mod helpers { pub use alloc::{borrow::Cow, string::String}; pub use core::{include_bytes, include_str}; pub use Some; } ================================================ FILE: wgpu/src/util/belt.rs ================================================ use crate::{ util::align_to, Buffer, BufferAddress, BufferDescriptor, BufferSize, BufferSlice, BufferUsages, BufferViewMut, CommandEncoder, Device, MapMode, }; use alloc::vec::Vec; use core::fmt; use std::sync::mpsc; use wgt::Features; use crate::COPY_BUFFER_ALIGNMENT; /// Efficiently performs many buffer writes by sharing and reusing temporary buffers. /// /// Internally it uses a ring-buffer of staging buffers that are sub-allocated. /// Its advantage over [`Queue::write_buffer_with()`] is that the individual allocations /// are cheaper; `StagingBelt` is most useful when you are writing very many small pieces /// of data. It can be understood as a sort of arena allocator. /// /// Using a staging belt is slightly complicated, and generally goes as follows: /// 1. Use [`StagingBelt::write_buffer()`] or [`StagingBelt::allocate()`] to allocate /// buffer slices, then write your data to them. /// 2. Call [`StagingBelt::finish()`]. /// 3. Submit all command encoders that were used in step 1. /// 4. Call [`StagingBelt::recall()`]. /// /// [`Queue::write_buffer_with()`]: crate::Queue::write_buffer_with pub struct StagingBelt { device: Device, chunk_size: BufferAddress, /// User-specified [`BufferUsages`] used to create the chunk buffers are created. /// /// [`new`](Self::new) guarantees that this always contains /// [`MAP_WRITE`](BufferUsages::MAP_WRITE). buffer_usages: BufferUsages, /// Chunks into which we are accumulating data to be transferred. active_chunks: Vec, /// Chunks that have scheduled transfers already; they are unmapped and some /// command encoder has one or more commands with them as source. closed_chunks: Vec, /// Chunks that are back from the GPU and ready to be mapped for write and put /// into `active_chunks`. free_chunks: Vec, /// When closed chunks are mapped again, the map callback sends them here. sender: Exclusive>, /// Free chunks are received here to be put on `self.free_chunks`. receiver: Exclusive>, } impl StagingBelt { /// Create a new staging belt. /// /// The `chunk_size` is the unit of internal buffer allocation; writes will be /// sub-allocated within each chunk. Therefore, for optimal use of memory, the /// chunk size should be: /// /// * larger than the largest single [`StagingBelt::write_buffer()`] operation; /// * 1-4 times less than the total amount of data uploaded per submission /// (per [`StagingBelt::finish()`]); and /// * bigger is better, within these bounds. /// /// The buffers returned by this [`StagingBelt`] will be have the buffer usages /// [`COPY_SRC | MAP_WRITE`](crate::BufferUsages) pub fn new(device: Device, chunk_size: BufferAddress) -> Self { Self::new_with_buffer_usages(device, chunk_size, BufferUsages::COPY_SRC) } /// Create a new staging belt. /// /// The `chunk_size` is the unit of internal buffer allocation; writes will be /// sub-allocated within each chunk. Therefore, for optimal use of memory, the /// chunk size should be: /// /// * larger than the largest single [`StagingBelt::write_buffer()`] operation; /// * 1-4 times less than the total amount of data uploaded per submission /// (per [`StagingBelt::finish()`]); and /// * bigger is better, within these bounds. /// /// `buffer_usages` specifies the [`BufferUsages`] the staging buffers /// will be created with. [`MAP_WRITE`](BufferUsages::MAP_WRITE) will be added /// automatically. The method will panic if the combination of usages is not /// supported. Because [`MAP_WRITE`](BufferUsages::MAP_WRITE) is implied, the allowed usages /// depends on if [`Features::MAPPABLE_PRIMARY_BUFFERS`] is enabled. /// - If enabled: any usage is valid. /// - If disabled: only [`COPY_SRC`](BufferUsages::COPY_SRC) can be used. #[track_caller] pub fn new_with_buffer_usages( device: Device, chunk_size: BufferAddress, mut buffer_usages: BufferUsages, ) -> Self { let (sender, receiver) = mpsc::channel(); // make sure anything other than MAP_WRITE | COPY_SRC is only allowed with MAPPABLE_PRIMARY_BUFFERS. let extra_usages = buffer_usages.difference(BufferUsages::MAP_WRITE | BufferUsages::COPY_SRC); if !extra_usages.is_empty() && !device .features() .contains(Features::MAPPABLE_PRIMARY_BUFFERS) { panic!("Only BufferUsages::COPY_SRC may be used when Features::MAPPABLE_PRIMARY_BUFFERS is not enabled. Specified buffer usages: {buffer_usages:?}"); } // always set MAP_WRITE buffer_usages.insert(BufferUsages::MAP_WRITE); StagingBelt { device, chunk_size, buffer_usages, active_chunks: Vec::new(), closed_chunks: Vec::new(), free_chunks: Vec::new(), sender: Exclusive::new(sender), receiver: Exclusive::new(receiver), } } /// Allocate a staging belt slice of `size` to be copied into the `target` buffer /// at the specified offset. /// /// `offset` and `size` must be multiples of [`COPY_BUFFER_ALIGNMENT`] /// (as is required by the underlying buffer operations). /// /// The upload will be placed into the provided command encoder. This encoder /// must be submitted after [`StagingBelt::finish()`] is called and before /// [`StagingBelt::recall()`] is called. /// /// If the `size` is greater than the size of any free internal buffer, a new buffer /// will be allocated for it. Therefore, the `chunk_size` passed to [`StagingBelt::new()`] /// should ideally be larger than every such size. #[track_caller] pub fn write_buffer( &mut self, encoder: &mut CommandEncoder, target: &Buffer, offset: BufferAddress, size: BufferSize, ) -> BufferViewMut { // Asserting this explicitly gives a usefully more specific, and more prompt, error than // leaving it to regular API validation. // We check only `offset`, not `size`, because `self.allocate()` will check the size. assert!( offset.is_multiple_of(COPY_BUFFER_ALIGNMENT), "StagingBelt::write_buffer() offset {offset} must be a multiple of `COPY_BUFFER_ALIGNMENT`" ); let slice_of_belt = self.allocate( size, const { BufferSize::new(crate::COPY_BUFFER_ALIGNMENT).unwrap() }, ); encoder.copy_buffer_to_buffer( slice_of_belt.buffer(), slice_of_belt.offset(), target, offset, size.get(), ); slice_of_belt.get_mapped_range_mut() } /// Allocate a staging belt slice with the given `size` and `alignment` and return it. /// /// `size` must be a multiple of [`COPY_BUFFER_ALIGNMENT`] /// (as is required by the underlying buffer operations). /// /// To use this slice, call [`BufferSlice::get_mapped_range_mut()`] and write your data into /// that [`BufferViewMut`]. /// (The view must be dropped before [`StagingBelt::finish()`] is called.) /// /// You can then record your own GPU commands to perform with the slice, /// such as copying it to a texture (whereas /// [`StagingBelt::write_buffer()`] can only write to other buffers). /// All commands involving this slice must be submitted after /// [`StagingBelt::finish()`] is called and before [`StagingBelt::recall()`] is called. /// /// If the `size` is greater than the space available in any free internal buffer, a new buffer /// will be allocated for it. Therefore, the `chunk_size` passed to [`StagingBelt::new()`] /// should ideally be larger than every such size. /// /// The chosen slice will be positioned within the buffer at a multiple of `alignment`, /// which may be used to meet alignment requirements for the operation you wish to perform /// with the slice. This does not necessarily affect the alignment of the [`BufferViewMut`]. #[track_caller] pub fn allocate(&mut self, size: BufferSize, alignment: BufferSize) -> BufferSlice<'_> { assert!( size.get().is_multiple_of(COPY_BUFFER_ALIGNMENT), "StagingBelt allocation size {size} must be a multiple of `COPY_BUFFER_ALIGNMENT`" ); assert!( alignment.get().is_power_of_two(), "alignment must be a power of two, not {alignment}" ); // At minimum, we must have alignment sufficient to map the buffer. let alignment = alignment.get().max(crate::MAP_ALIGNMENT); let mut chunk = if let Some(index) = self .active_chunks .iter() .position(|chunk| chunk.can_allocate(size, alignment)) { self.active_chunks.swap_remove(index) } else { self.receive_chunks(); // ensure self.free_chunks is up to date if let Some(index) = self .free_chunks .iter() .position(|chunk| chunk.can_allocate(size, alignment)) { self.free_chunks.swap_remove(index) } else { Chunk { buffer: self.device.create_buffer(&BufferDescriptor { label: Some("(wgpu internal) StagingBelt staging buffer"), size: self.chunk_size.max(size.get()), usage: self.buffer_usages, mapped_at_creation: true, }), offset: 0, } } }; let allocation_offset = chunk.allocate(size, alignment); self.active_chunks.push(chunk); let chunk = self.active_chunks.last().unwrap(); chunk .buffer .slice(allocation_offset..allocation_offset + size.get()) } /// Prepare currently mapped buffers for use in a submission. /// /// This must be called before the command encoder(s) provided to /// [`StagingBelt::write_buffer()`] are submitted. /// /// At this point, all the partially used staging buffers are closed (cannot be used for /// further writes) until after [`StagingBelt::recall()`] is called *and* the GPU is done /// copying the data from them. pub fn finish(&mut self) { for chunk in self.active_chunks.drain(..) { chunk.buffer.unmap(); self.closed_chunks.push(chunk); } } /// Recall all of the closed buffers back to be reused. /// /// This must only be called after the command encoder(s) provided to /// [`StagingBelt::write_buffer()`] are submitted. Additional calls are harmless. /// Not calling this as soon as possible may result in increased buffer memory usage. pub fn recall(&mut self) { self.receive_chunks(); for chunk in self.closed_chunks.drain(..) { let sender = self.sender.get_mut().clone(); chunk .buffer .clone() .slice(..) .map_async(MapMode::Write, move |_| { let _ = sender.send(chunk); }); } } /// Move all chunks that the GPU is done with (and are now mapped again) /// from `self.receiver` to `self.free_chunks`. fn receive_chunks(&mut self) { while let Ok(mut chunk) = self.receiver.get_mut().try_recv() { chunk.offset = 0; self.free_chunks.push(chunk); } } } impl fmt::Debug for StagingBelt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { device, chunk_size, buffer_usages, active_chunks, closed_chunks, free_chunks, sender: _, receiver: _, } = self; f.debug_struct("StagingBelt") .field("device", device) .field("chunk_size", chunk_size) .field("buffer_usages", buffer_usages) .field("active_chunks", &active_chunks.len()) .field("closed_chunks", &closed_chunks.len()) .field("free_chunks", &free_chunks.len()) .finish_non_exhaustive() } } struct Chunk { buffer: Buffer, offset: BufferAddress, } impl Chunk { fn can_allocate(&self, size: BufferSize, alignment: BufferAddress) -> bool { let alloc_start = align_to(self.offset, alignment); let alloc_end = alloc_start + size.get(); alloc_end <= self.buffer.size() } fn allocate(&mut self, size: BufferSize, alignment: BufferAddress) -> BufferAddress { let alloc_start = align_to(self.offset, alignment); let alloc_end = alloc_start + size.get(); assert!(alloc_end <= self.buffer.size()); self.offset = alloc_end; alloc_start } } use exclusive::Exclusive; mod exclusive { /// `Sync` wrapper that works by providing only exclusive access. /// /// See pub(super) struct Exclusive(T); /// Safety: `&Exclusive` has no operations. unsafe impl Sync for Exclusive {} impl Exclusive { pub fn new(value: T) -> Self { Self(value) } pub fn get_mut(&mut self) -> &mut T { &mut self.0 } } } ================================================ FILE: wgpu/src/util/blit.wgsl ================================================ struct VertexOutput { @builtin(position) position: vec4, @location(0) tex_coords: vec2, } @vertex fn vs_main(@builtin(vertex_index) vi: u32) -> VertexOutput { var out: VertexOutput; out.tex_coords = vec2( f32((vi << 1u) & 2u), f32(vi & 2u), ); out.position = vec4(out.tex_coords * 2.0 - 1.0, 0.0, 1.0); // Invert y so the texture is not upside down out.tex_coords.y = 1.0 - out.tex_coords.y; return out; } @group(0) @binding(0) var texture: texture_2d; @group(0) @binding(1) var texture_sampler: sampler; @fragment fn fs_main(vs: VertexOutput) -> @location(0) vec4 { return textureSample(texture, texture_sampler, vs.tex_coords); } ================================================ FILE: wgpu/src/util/device.rs ================================================ use alloc::borrow::ToOwned as _; use wgt::TextureDataOrder; /// Describes a [Buffer](crate::Buffer) when allocating. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct BufferInitDescriptor<'a> { /// Debug label of a buffer. This will show up in graphics debuggers for easy identification. pub label: crate::Label<'a>, /// Contents of a buffer on creation. pub contents: &'a [u8], /// Usages of a buffer. If the buffer is used in any way that isn't specified here, the operation /// will panic. pub usage: wgt::BufferUsages, } /// Utility methods not meant to be in the main API. pub trait DeviceExt { /// Creates a [Buffer](crate::Buffer) with data to initialize it. fn create_buffer_init(&self, desc: &BufferInitDescriptor<'_>) -> crate::Buffer; /// Upload an entire texture and its mipmaps from a source buffer. /// /// Expects all mipmaps to be tightly packed in the data buffer. /// /// See [`TextureDataOrder`] for the order in which the data is laid out in memory. /// /// Implicitly adds the `COPY_DST` usage if it is not present in the descriptor, /// as it is required to be able to upload the data to the gpu. fn create_texture_with_data( &self, queue: &crate::Queue, desc: &crate::TextureDescriptor<'_>, order: TextureDataOrder, data: &[u8], ) -> crate::Texture; } impl DeviceExt for crate::Device { fn create_buffer_init(&self, descriptor: &BufferInitDescriptor<'_>) -> crate::Buffer { // Skip mapping if the buffer is zero sized if descriptor.contents.is_empty() { let wgt_descriptor = crate::BufferDescriptor { label: descriptor.label, size: 0, usage: descriptor.usage, mapped_at_creation: false, }; self.create_buffer(&wgt_descriptor) } else { let unpadded_size = descriptor.contents.len() as crate::BufferAddress; // Valid vulkan usage is // 1. buffer size must be a multiple of COPY_BUFFER_ALIGNMENT. // 2. buffer size must be greater than 0. // Therefore we round the value up to the nearest multiple, and ensure it's at least COPY_BUFFER_ALIGNMENT. let align_mask = crate::COPY_BUFFER_ALIGNMENT - 1; let padded_size = ((unpadded_size + align_mask) & !align_mask).max(crate::COPY_BUFFER_ALIGNMENT); let wgt_descriptor = crate::BufferDescriptor { label: descriptor.label, size: padded_size, usage: descriptor.usage, mapped_at_creation: true, }; let buffer = self.create_buffer(&wgt_descriptor); buffer .get_mapped_range_mut(..) .slice(..unpadded_size as usize) .copy_from_slice(descriptor.contents); buffer.unmap(); buffer } } fn create_texture_with_data( &self, queue: &crate::Queue, desc: &crate::TextureDescriptor<'_>, order: TextureDataOrder, data: &[u8], ) -> crate::Texture { // Implicitly add the COPY_DST usage let mut desc = desc.to_owned(); desc.usage |= crate::TextureUsages::COPY_DST; let texture = self.create_texture(&desc); // Will return None only if it's a combined depth-stencil format // If so, default to 4, validation will fail later anyway since the depth or stencil // aspect needs to be written to individually let block_size = desc.format.block_copy_size(None).unwrap_or(4); let (block_width, block_height) = desc.format.block_dimensions(); let layer_iterations = desc.array_layer_count(); let outer_iteration; let inner_iteration; match order { TextureDataOrder::LayerMajor => { outer_iteration = layer_iterations; inner_iteration = desc.mip_level_count; } TextureDataOrder::MipMajor => { outer_iteration = desc.mip_level_count; inner_iteration = layer_iterations; } } let mut binary_offset = 0; for outer in 0..outer_iteration { for inner in 0..inner_iteration { let (layer, mip) = match order { TextureDataOrder::LayerMajor => (outer, inner), TextureDataOrder::MipMajor => (inner, outer), }; let mut mip_size = desc.mip_level_size(mip).unwrap(); // copying layers separately if desc.dimension != wgt::TextureDimension::D3 { mip_size.depth_or_array_layers = 1; } // When uploading mips of compressed textures and the mip is supposed to be // a size that isn't a multiple of the block size, the mip needs to be uploaded // as its "physical size" which is the size rounded up to the nearest block size. let mip_physical = mip_size.physical_size(desc.format); // All these calculations are performed on the physical size as that's the // data that exists in the buffer. let width_blocks = mip_physical.width / block_width; let height_blocks = mip_physical.height / block_height; let bytes_per_row = width_blocks * block_size; let data_size = bytes_per_row * height_blocks * mip_size.depth_or_array_layers; let end_offset = binary_offset + data_size as usize; queue.write_texture( crate::TexelCopyTextureInfo { texture: &texture, mip_level: mip, origin: crate::Origin3d { x: 0, y: 0, z: layer, }, aspect: wgt::TextureAspect::All, }, &data[binary_offset..end_offset], crate::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(bytes_per_row), rows_per_image: Some(height_blocks), }, mip_physical, ); binary_offset = end_offset; } } texture } } ================================================ FILE: wgpu/src/util/encoder.rs ================================================ use core::ops::Range; use wgt::{BufferAddress, DynamicOffset, IndexFormat}; use crate::{BindGroup, Buffer, BufferSlice, RenderBundleEncoder, RenderPass, RenderPipeline}; /// Methods shared by [`RenderPass`] and [`RenderBundleEncoder`]. pub trait RenderEncoder<'a> { /// Sets the active bind group for a given bind group index. The bind group layout /// in the active pipeline when any `draw()` function is called must match the layout of this bind group. /// /// If the bind group have dynamic offsets, provide them in order of their declaration. fn set_bind_group( &mut self, index: u32, bind_group: Option<&'a BindGroup>, offsets: &[DynamicOffset], ); /// Sets the active render pipeline. /// /// Subsequent draw calls will exhibit the behavior defined by `pipeline`. fn set_pipeline(&mut self, pipeline: &'a RenderPipeline); /// Sets the active index buffer. /// /// Subsequent calls to [`draw_indexed`](RenderEncoder::draw_indexed) on this [`RenderEncoder`] will /// use `buffer` as the source index buffer. fn set_index_buffer(&mut self, buffer_slice: BufferSlice<'a>, index_format: IndexFormat); /// Assign a vertex buffer to a slot. /// /// Subsequent calls to [`draw`] and [`draw_indexed`] on this /// [`RenderEncoder`] will use `buffer` as one of the source vertex buffers. /// /// The `slot` refers to the index of the matching descriptor in /// [`VertexState::buffers`](crate::VertexState::buffers). /// /// [`draw`]: RenderEncoder::draw /// [`draw_indexed`]: RenderEncoder::draw_indexed fn set_vertex_buffer(&mut self, slot: u32, buffer_slice: BufferSlice<'a>); /// Draws primitives from the active vertex buffer(s). /// /// The active vertex buffers can be set with [`RenderEncoder::set_vertex_buffer`]. fn draw(&mut self, vertices: Range, instances: Range); /// Draws indexed primitives using the active index buffer and the active vertex buffers. /// /// The active index buffer can be set with [`RenderEncoder::set_index_buffer`], while the active /// vertex buffers can be set with [`RenderEncoder::set_vertex_buffer`]. fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range); /// Draws primitives from the active vertex buffer(s) based on the contents of the `indirect_buffer`. /// /// The active vertex buffers can be set with [`RenderEncoder::set_vertex_buffer`]. /// /// The structure expected in `indirect_buffer` must conform to [`DrawIndirectArgs`](crate::util::DrawIndirectArgs). fn draw_indirect(&mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress); /// Draws indexed primitives using the active index buffer and the active vertex buffers, /// based on the contents of the `indirect_buffer`. /// /// The active index buffer can be set with [`RenderEncoder::set_index_buffer`], while the active /// vertex buffers can be set with [`RenderEncoder::set_vertex_buffer`]. /// /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirectArgs`](crate::util::DrawIndexedIndirectArgs). fn draw_indexed_indirect( &mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress, ); /// [`wgt::Features::IMMEDIATES`] must be enabled on the device in order to call this function. /// /// Set immediate data for subsequent draw calls. /// /// Write the bytes in `data` at offset `offset` within immediate data /// storage. Both `offset` and the length of `data` must be /// multiples of [`crate::IMMEDIATE_DATA_ALIGNMENT`], which is always 4. /// /// For example, if `offset` is `4` and `data` is eight bytes long, this /// call will write `data` to bytes `4..12` of immediate data storage. fn set_immediates(&mut self, offset: u32, data: &[u8]); } impl<'a> RenderEncoder<'a> for RenderPass<'a> { #[inline(always)] fn set_bind_group( &mut self, index: u32, bind_group: Option<&'a BindGroup>, offsets: &[DynamicOffset], ) { Self::set_bind_group(self, index, bind_group, offsets); } #[inline(always)] fn set_pipeline(&mut self, pipeline: &'a RenderPipeline) { Self::set_pipeline(self, pipeline); } #[inline(always)] fn set_index_buffer(&mut self, buffer_slice: BufferSlice<'a>, index_format: IndexFormat) { Self::set_index_buffer(self, buffer_slice, index_format); } #[inline(always)] fn set_vertex_buffer(&mut self, slot: u32, buffer_slice: BufferSlice<'a>) { Self::set_vertex_buffer(self, slot, buffer_slice); } #[inline(always)] fn draw(&mut self, vertices: Range, instances: Range) { Self::draw(self, vertices, instances); } #[inline(always)] fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range) { Self::draw_indexed(self, indices, base_vertex, instances); } #[inline(always)] fn draw_indirect(&mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress) { Self::draw_indirect(self, indirect_buffer, indirect_offset); } #[inline(always)] fn draw_indexed_indirect( &mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress, ) { Self::draw_indexed_indirect(self, indirect_buffer, indirect_offset); } #[inline(always)] fn set_immediates(&mut self, offset: u32, data: &[u8]) { Self::set_immediates(self, offset, data); } } impl<'a> RenderEncoder<'a> for RenderBundleEncoder<'a> { #[inline(always)] fn set_bind_group( &mut self, index: u32, bind_group: Option<&'a BindGroup>, offsets: &[DynamicOffset], ) { Self::set_bind_group(self, index, bind_group, offsets); } #[inline(always)] fn set_pipeline(&mut self, pipeline: &'a RenderPipeline) { Self::set_pipeline(self, pipeline); } #[inline(always)] fn set_index_buffer(&mut self, buffer_slice: BufferSlice<'a>, index_format: IndexFormat) { Self::set_index_buffer(self, buffer_slice, index_format); } #[inline(always)] fn set_vertex_buffer(&mut self, slot: u32, buffer_slice: BufferSlice<'a>) { Self::set_vertex_buffer(self, slot, buffer_slice); } #[inline(always)] fn draw(&mut self, vertices: Range, instances: Range) { Self::draw(self, vertices, instances); } #[inline(always)] fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range) { Self::draw_indexed(self, indices, base_vertex, instances); } #[inline(always)] fn draw_indirect(&mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress) { Self::draw_indirect(self, indirect_buffer, indirect_offset); } #[inline(always)] fn draw_indexed_indirect( &mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress, ) { Self::draw_indexed_indirect(self, indirect_buffer, indirect_offset); } #[inline(always)] fn set_immediates(&mut self, offset: u32, data: &[u8]) { Self::set_immediates(self, offset, data); } } ================================================ FILE: wgpu/src/util/init.rs ================================================ use crate::{Adapter, Instance, RequestAdapterOptions, Surface}; #[cfg(doc)] use crate::Backends; /// Initialize the adapter obeying the `WGPU_ADAPTER_NAME` environment variable. #[cfg(wgpu_core)] #[cfg_attr(not(std), expect(unused_variables, unreachable_code))] pub async fn initialize_adapter_from_env( instance: &Instance, compatible_surface: Option<&Surface<'_>>, ) -> Result { let desired_adapter_name: alloc::string::String = { cfg_if::cfg_if! { if #[cfg(std)] { std::env::var("WGPU_ADAPTER_NAME") .as_deref() .map(str::to_lowercase) .map_err(|_| wgt::RequestAdapterError::EnvNotSet)? } else { return Err(wgt::RequestAdapterError::EnvNotSet) } } }; let adapters = instance.enumerate_adapters(crate::Backends::all()).await; let mut chosen_adapter = None; for adapter in adapters { let info = adapter.get_info(); if let Some(surface) = compatible_surface { if !adapter.is_surface_supported(surface) { continue; } } if info.name.to_lowercase().contains(&desired_adapter_name) { chosen_adapter = Some(adapter); break; } } Ok(chosen_adapter.expect("WGPU_ADAPTER_NAME set but no matching adapter found!")) } /// Initialize the adapter obeying the `WGPU_ADAPTER_NAME` environment variable. #[cfg(not(wgpu_core))] pub async fn initialize_adapter_from_env( _instance: &Instance, _compatible_surface: Option<&Surface<'_>>, ) -> Result { Err(wgt::RequestAdapterError::EnvNotSet) } /// Initialize the adapter obeying the `WGPU_ADAPTER_NAME` environment variable and if it doesn't exist fall back on a default adapter. pub async fn initialize_adapter_from_env_or_default( instance: &Instance, compatible_surface: Option<&Surface<'_>>, ) -> Result { match initialize_adapter_from_env(instance, compatible_surface).await { Ok(a) => Ok(a), Err(_) => { instance .request_adapter(&RequestAdapterOptions { power_preference: crate::PowerPreference::from_env().unwrap_or_default(), force_fallback_adapter: false, compatible_surface, }) .await } } } /// Determines whether the [`Backends::BROWSER_WEBGPU`] backend is supported. /// /// The result can only be true if this is called from the main thread or a dedicated worker. /// For convenience, this is also supported on non-wasm targets, always returning false there. pub async fn is_browser_webgpu_supported() -> bool { #[cfg(webgpu)] { // In theory it should be enough to check for the presence of the `gpu` property... let gpu = crate::backend::get_browser_gpu_property(); let Ok(Some(gpu)) = gpu else { return false; }; // ...but in practice, we also have to try to create an adapter, since as of writing // Chrome on Linux has the `gpu` property but doesn't support WebGPU. let adapter_promise = gpu.request_adapter(); wasm_bindgen_futures::JsFuture::from(adapter_promise) .await .is_ok_and(|adapter| !adapter.is_undefined() && !adapter.is_null()) } #[cfg(not(webgpu))] { false } } /// Create an new instance of wgpu, but disabling [`Backends::BROWSER_WEBGPU`] if no WebGPU support was detected. /// /// If the instance descriptor enables [`Backends::BROWSER_WEBGPU`], /// this checks via [`is_browser_webgpu_supported`] for WebGPU support before forwarding /// the descriptor with or without [`Backends::BROWSER_WEBGPU`] respectively to [`Instance::new`]. /// /// You should prefer this method over [`Instance::new`] if you want to target WebGPU and automatically /// fall back to WebGL if WebGPU is not available. /// This is because WebGPU support has to be decided upon instance creation and [`Instance::new`] /// (being a `sync` function) can't establish WebGPU support (details see [`is_browser_webgpu_supported`]). /// /// # Panics /// /// If no backend feature for the active target platform is enabled, /// this method will panic, see [`Instance::enabled_backend_features()`]. pub async fn new_instance_with_webgpu_detection( mut instance_desc: wgt::InstanceDescriptor, ) -> crate::Instance { if instance_desc .backends .contains(wgt::Backends::BROWSER_WEBGPU) && !is_browser_webgpu_supported().await { instance_desc.backends.remove(wgt::Backends::BROWSER_WEBGPU); } crate::Instance::new(instance_desc) } ================================================ FILE: wgpu/src/util/mod.rs ================================================ //! Utility structures and functions that are built on top of the main `wgpu` API. //! //! Nothing in this module is a part of the WebGPU API specification; //! they are unique to the `wgpu` library. // TODO: For [`belt::StagingBelt`] to be available in `no_std` its usage of [`std::sync::mpsc`] // must be replaced with an appropriate alternative. #[cfg(std)] mod belt; mod device; mod encoder; mod init; mod mutex; mod panicking; mod spirv; mod texture_blitter; use alloc::{format, string::String}; #[cfg(std)] pub use belt::StagingBelt; pub use device::{BufferInitDescriptor, DeviceExt}; pub use encoder::RenderEncoder; pub use init::*; pub use spirv::*; #[cfg(feature = "wgsl")] pub use texture_blitter::{TextureBlitter, TextureBlitterBuilder}; pub use wgt::{ math::*, DispatchIndirectArgs, DrawIndexedIndirectArgs, DrawIndirectArgs, TextureDataOrder, }; pub(crate) use mutex::Mutex; pub(crate) use panicking::is_panicking; use crate::dispatch; /// CPU accessible buffer used to download data back from the GPU. pub struct DownloadBuffer { _gpu_buffer: super::Buffer, mapped_range: dispatch::DispatchBufferMappedRange, } impl DownloadBuffer { /// Asynchronously read the contents of a buffer. pub fn read_buffer( device: &super::Device, queue: &super::Queue, buffer: &super::BufferSlice<'_>, callback: impl FnOnce(Result) + Send + 'static, ) { let size = buffer.size.into(); let download = device.create_buffer(&super::BufferDescriptor { size, usage: super::BufferUsages::COPY_DST | super::BufferUsages::MAP_READ, mapped_at_creation: false, label: None, }); let mut encoder = device.create_command_encoder(&super::CommandEncoderDescriptor { label: None }); encoder.copy_buffer_to_buffer(buffer.buffer, buffer.offset, &download, 0, size); let command_buffer: super::CommandBuffer = encoder.finish(); queue.submit(Some(command_buffer)); download .clone() .slice(..) .map_async(super::MapMode::Read, move |result| { if let Err(e) = result { callback(Err(e)); return; } let mapped_range = download.inner.get_mapped_range(0..size); callback(Ok(Self { _gpu_buffer: download, mapped_range, })); }); } } impl core::ops::Deref for DownloadBuffer { type Target = [u8]; fn deref(&self) -> &[u8] { // SAFETY: `self.mapped_range` is always a read mapping unsafe { self.mapped_range.read_slice() } } } /// A recommended key for storing [`PipelineCache`]s for the adapter /// associated with the given [`AdapterInfo`](wgt::AdapterInfo) /// This key will define a class of adapters for which the same cache /// might be valid. /// /// If this returns `None`, the adapter doesn't support [`PipelineCache`]. /// This may be because the API doesn't support application managed caches /// (such as browser WebGPU), or that `wgpu` hasn't implemented it for /// that API yet. /// /// This key could be used as a filename, as seen in the example below. /// /// # Examples /// /// ```no_run /// # use std::path::PathBuf; /// use wgpu::PipelineCacheDescriptor; /// # let adapter_info = todo!(); /// # let device: wgpu::Device = todo!(); /// let cache_dir: PathBuf = unimplemented!("Some reasonable platform-specific cache directory for your app."); /// let filename = wgpu::util::pipeline_cache_key(&adapter_info); /// let (pipeline_cache, cache_file) = if let Some(filename) = filename { /// let cache_path = cache_dir.join(&filename); /// // If we failed to read the cache, for whatever reason, treat the data as lost. /// // In a real app, we'd probably avoid caching entirely unless the error was "file not found". /// let cache_data = std::fs::read(&cache_path).ok(); /// let pipeline_cache = unsafe { /// device.create_pipeline_cache(&PipelineCacheDescriptor { /// data: cache_data.as_deref(), /// label: None, /// fallback: true /// }) /// }; /// (Some(pipeline_cache), Some(cache_path)) /// } else { /// (None, None) /// }; /// /// // Run pipeline initialisation, making sure to set the `cache` /// // fields of your `*PipelineDescriptor` to `pipeline_cache` /// /// // And then save the resulting cache (probably off the main thread). /// if let (Some(pipeline_cache), Some(cache_file)) = (pipeline_cache, cache_file) { /// let data = pipeline_cache.get_data(); /// if let Some(data) = data { /// let temp_file = cache_file.with_extension("temp"); /// std::fs::write(&temp_file, &data)?; /// std::fs::rename(&temp_file, &cache_file)?; /// } /// } /// # Ok::<_, std::io::Error>(()) /// ``` /// /// [`PipelineCache`]: super::PipelineCache pub fn pipeline_cache_key(adapter_info: &wgt::AdapterInfo) -> Option { match adapter_info.backend { wgt::Backend::Vulkan => Some(format!( // The vendor/device should uniquely define a driver // We/the driver will also later validate that the vendor/device and driver // version match, which may lead to clearing an outdated // cache for the same device. "wgpu_pipeline_cache_vulkan_{}_{}", adapter_info.vendor, adapter_info.device )), _ => None, } } /// Adds extra conversion functions to `TextureFormat`. pub trait TextureFormatExt { /// Finds the [`TextureFormat`](wgt::TextureFormat) corresponding to the given /// [`StorageFormat`](wgc::naga::StorageFormat). /// /// # Examples /// ``` /// use wgpu::util::TextureFormatExt; /// assert_eq!(wgpu::TextureFormat::from_storage_format(wgpu::naga::StorageFormat::Bgra8Unorm), wgpu::TextureFormat::Bgra8Unorm); /// ``` #[cfg(wgpu_core)] fn from_storage_format(storage_format: crate::naga::StorageFormat) -> Self; /// Finds the [`StorageFormat`](wgc::naga::StorageFormat) corresponding to the given [`TextureFormat`](wgt::TextureFormat). /// Returns `None` if there is no matching storage format, /// which typically indicates this format is not supported /// for storage textures. /// /// # Examples /// ``` /// use wgpu::util::TextureFormatExt; /// assert_eq!(wgpu::TextureFormat::Bgra8Unorm.to_storage_format(), Some(wgpu::naga::StorageFormat::Bgra8Unorm)); /// ``` #[cfg(wgpu_core)] fn to_storage_format(&self) -> Option; } impl TextureFormatExt for wgt::TextureFormat { #[cfg(wgpu_core)] fn from_storage_format(storage_format: crate::naga::StorageFormat) -> Self { wgc::map_storage_format_from_naga(storage_format) } #[cfg(wgpu_core)] fn to_storage_format(&self) -> Option { wgc::map_storage_format_to_naga(*self) } } ================================================ FILE: wgpu/src/util/mutex.rs ================================================ //! Provides a [`Mutex`] for internal use based on what features are available. cfg_if::cfg_if! { if #[cfg(feature = "parking_lot")] { use parking_lot::Mutex as MutexInner; } else if #[cfg(std)] { use std::sync::Mutex as MutexInner; } else { use core::cell::RefCell as MutexInner; } } pub(crate) struct Mutex { inner: MutexInner, } impl core::fmt::Debug for Mutex where MutexInner: core::fmt::Debug, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { as core::fmt::Debug>::fmt(&self.inner, f) } } impl Default for Mutex { fn default() -> Self { Self::new(::default()) } } impl Mutex { pub const fn new(value: T) -> Self { Self { inner: MutexInner::new(value), } } } impl Mutex { pub fn lock(&self) -> impl core::ops::DerefMut + '_ { cfg_if::cfg_if! { if #[cfg(feature = "parking_lot")] { self.inner.lock() } else if #[cfg(std)] { self.inner.lock().unwrap_or_else(std::sync::PoisonError::into_inner) } else { loop { let Ok(lock) = self.inner.try_borrow_mut() else { // Without `std` all we can do is spin until the current lock is released core::hint::spin_loop(); continue; }; break lock; } } } } } ================================================ FILE: wgpu/src/util/panicking.rs ================================================ #[cfg(feature = "std")] pub fn is_panicking() -> bool { std::thread::panicking() } #[cfg(not(feature = "std"))] pub fn is_panicking() -> bool { false } ================================================ FILE: wgpu/src/util/spirv.rs ================================================ //! Utilities for loading SPIR-V module data. use alloc::borrow::Cow; use core::mem; #[cfg_attr(not(any(feature = "spirv", doc)), expect(unused_imports))] use crate::ShaderSource; #[cfg(doc)] use crate::Device; const SPIRV_MAGIC_NUMBER: u32 = 0x0723_0203; /// Treat the given byte slice as a SPIR-V module. /// /// # Panics /// /// This function panics if: /// /// - `data.len()` is not a multiple of 4 /// - `data` does not begin with the SPIR-V magic number /// /// It does not check that the data is a valid SPIR-V module in any other way. #[cfg(feature = "spirv")] // ShaderSource::SpirV only exists in this case pub fn make_spirv(data: &[u8]) -> ShaderSource<'_> { ShaderSource::SpirV(make_spirv_raw(data)) } /// Check whether the byte slice has the SPIR-V magic number (in either byte order) and of an /// appropriate size, and panic with a suitable message when it is not. /// /// Returns whether the endianness is opposite of native endianness (i.e. whether /// [`u32::swap_bytes()`] should be called.) /// /// Note: this function’s checks are relied upon for the soundness of [`make_spirv_const()`]. /// Undefined behavior will result if it does not panic when `bytes.len()` is not a multiple of 4. #[track_caller] const fn assert_has_spirv_magic_number_and_length(bytes: &[u8]) -> bool { // First, check the magic number. // This way we give the best error for wrong formats. // (Plus a special case for the empty slice.) let found_magic_number: Option = match *bytes { [] => panic!("byte slice is empty, not SPIR-V"), // This would be simpler as slice::starts_with(), but that isn't a const fn yet. [b1, b2, b3, b4, ..] => { let prefix = u32::from_ne_bytes([b1, b2, b3, b4]); if prefix == SPIRV_MAGIC_NUMBER { Some(false) } else if prefix == const { SPIRV_MAGIC_NUMBER.swap_bytes() } { // needs swapping Some(true) } else { None } } _ => None, // fallthrough case = between 1 and 3 bytes }; match found_magic_number { Some(needs_byte_swap) => { // Note: this assertion is relied upon for the soundness of `make_spirv_const()`. assert!( bytes.len().is_multiple_of(mem::size_of::()), "SPIR-V data must be a multiple of 4 bytes long" ); needs_byte_swap } None => { panic!( "byte slice does not start with SPIR-V magic number. \ Make sure you are using a binary SPIR-V file." ); } } } #[cfg_attr(not(feature = "spirv"), expect(rustdoc::broken_intra_doc_links))] /// Version of [`make_spirv()`] intended for use with /// [`Device::create_shader_module_passthrough()`]. /// /// Returns a raw slice instead of [`ShaderSource`]. /// /// # Panics /// /// This function panics if: /// /// - `data.len()` is not a multiple of 4 /// - `data` does not begin with the SPIR-V magic number /// /// It does not check that the data is a valid SPIR-V module in any other way. pub fn make_spirv_raw(bytes: &[u8]) -> Cow<'_, [u32]> { let needs_byte_swap = assert_has_spirv_magic_number_and_length(bytes); // If the data happens to be aligned, directly use the byte array, // otherwise copy the byte array in an owned vector and use that instead. let mut words: Cow<'_, [u32]> = match bytemuck::try_cast_slice(bytes) { Ok(words) => Cow::Borrowed(words), // We already checked the length, so if this fails, it fails due to lack of alignment only. Err(_) => Cow::Owned(bytemuck::pod_collect_to_vec(bytes)), }; // If necessary, swap bytes to native endianness. if needs_byte_swap { for word in Cow::to_mut(&mut words) { *word = word.swap_bytes(); } } assert!( words[0] == SPIRV_MAGIC_NUMBER, "can't happen: wrong magic number after swap_bytes" ); words } /// Version of `make_spirv_raw` used for implementing [`include_spirv!`] and [`include_spirv_raw!`] macros. /// /// Not public API. Also, don't even try calling at runtime; you'll get a stack overflow. /// /// [`include_spirv!`]: crate::include_spirv #[doc(hidden)] pub const fn make_spirv_const(bytes: [u8; IN]) -> [u32; OUT] { let needs_byte_swap = assert_has_spirv_magic_number_and_length(&bytes); // NOTE: to get around lack of generic const expressions, the input and output lengths must // be specified separately. // Check that they are consistent with each other. assert!(mem::size_of_val(&bytes) == mem::size_of::<[u32; OUT]>()); // Can't use `bytemuck` in `const fn` (yet), so do it unsafely. // SAFETY: // * The previous assertion checked that the byte sizes of `bytes` and `words` are equal. // * `transmute_copy` doesn't care that the alignment might be wrong. let mut words: [u32; OUT] = unsafe { mem::transmute_copy(&bytes) }; // If necessary, swap bytes to native endianness. if needs_byte_swap { let mut idx = 0; while idx < words.len() { words[idx] = words[idx].swap_bytes(); idx += 1; } } assert!( words[0] == SPIRV_MAGIC_NUMBER, "can't happen: wrong magic number after swap_bytes" ); words } #[cfg(test)] mod tests { use super::*; use alloc::vec; fn test_success_with_misalignments( input: &[u8; IN], expected: [u32; OUT], ) { // We don't know which 3 out of 4 offsets will produce an actually misaligned slice, // but they always will. (Note that it is necessary to reuse the same allocation for all 4 // tests, or we could (in theory) get unlucky and not test any misalignments.) let mut buffer = vec![0; input.len() + 4]; for offset in 0..4 { let misaligned_slice: &mut [u8; IN] = (&mut buffer[offset..][..input.len()]).try_into().unwrap(); misaligned_slice.copy_from_slice(input); assert_eq!(*make_spirv_raw(misaligned_slice), expected); assert_eq!(make_spirv_const(*misaligned_slice), expected); } } #[test] fn success_be() { // magic number followed by dummy data to see the endianness handling let input = b"\x07\x23\x02\x03\xF1\xF2\xF3\xF4"; let expected: [u32; 2] = [SPIRV_MAGIC_NUMBER, 0xF1F2F3F4]; test_success_with_misalignments(input, expected); } #[test] fn success_le() { let input = b"\x03\x02\x23\x07\xF1\xF2\xF3\xF4"; let expected: [u32; 2] = [SPIRV_MAGIC_NUMBER, 0xF4F3F2F1]; test_success_with_misalignments(input, expected); } #[should_panic = "multiple of 4"] #[test] fn nonconst_le_fail() { let _: Cow<'_, [u32]> = make_spirv_raw(&[0x03, 0x02, 0x23, 0x07, 0x44, 0x33]); } #[should_panic = "multiple of 4"] #[test] fn nonconst_be_fail() { let _: Cow<'_, [u32]> = make_spirv_raw(&[0x07, 0x23, 0x02, 0x03, 0x11, 0x22]); } #[should_panic = "multiple of 4"] #[test] fn const_le_fail() { let _: [u32; 1] = make_spirv_const([0x03, 0x02, 0x23, 0x07, 0x44, 0x33]); } #[should_panic = "multiple of 4"] #[test] fn const_be_fail() { let _: [u32; 1] = make_spirv_const([0x07, 0x23, 0x02, 0x03, 0x11, 0x22]); } #[should_panic = "byte slice is empty, not SPIR-V"] #[test] fn make_spirv_empty() { let _: [u32; 0] = make_spirv_const([]); } } ================================================ FILE: wgpu/src/util/texture_blitter.rs ================================================ #![cfg(feature = "wgsl")] use wgt::BlendState; use crate::{ include_wgsl, AddressMode, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, ColorTargetState, ColorWrites, CommandEncoder, Device, FilterMode, FragmentState, FrontFace, LoadOp, MultisampleState, PipelineCompilationOptions, PipelineLayoutDescriptor, PrimitiveState, PrimitiveTopology, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages, StoreOp, TextureFormat, TextureSampleType, TextureView, TextureViewDimension, VertexState, }; /// A builder for the [`TextureBlitter`] utility. /// If you want the default [`TextureBlitter`] use [`TextureBlitter::new`] instead. pub struct TextureBlitterBuilder<'a> { device: &'a Device, format: TextureFormat, sample_type: FilterMode, blend_state: Option, } impl<'a> TextureBlitterBuilder<'a> { /// Returns a new [`TextureBlitterBuilder`] /// /// # Arguments /// - `device` - A [`Device`] /// - `format` - The [`TextureFormat`] of the texture that will be copied to. This has to have the `RENDER_TARGET` usage. pub fn new(device: &'a Device, format: TextureFormat) -> Self { Self { device, format, sample_type: FilterMode::Nearest, blend_state: None, } } /// Sets the [`Sampler`] Filtering Mode pub fn sample_type(mut self, sample_type: FilterMode) -> Self { self.sample_type = sample_type; self } /// Sets the [`BlendState`] that is used. pub fn blend_state(mut self, blend_state: BlendState) -> Self { self.blend_state = Some(blend_state); self } /// Returns a new [`TextureBlitter`] with given settings. pub fn build(self) -> TextureBlitter { let sampler = self.device.create_sampler(&SamplerDescriptor { label: Some("wgpu::util::TextureBlitter::sampler"), address_mode_u: AddressMode::ClampToEdge, address_mode_v: AddressMode::ClampToEdge, address_mode_w: AddressMode::ClampToEdge, mag_filter: self.sample_type, ..Default::default() }); let bind_group_layout = self .device .create_bind_group_layout(&BindGroupLayoutDescriptor { label: Some("wgpu::util::TextureBlitter::bind_group_layout"), entries: &[ BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::FRAGMENT, ty: BindingType::Texture { sample_type: TextureSampleType::Float { filterable: self.sample_type == FilterMode::Linear, }, view_dimension: TextureViewDimension::D2, multisampled: false, }, count: None, }, BindGroupLayoutEntry { binding: 1, visibility: ShaderStages::FRAGMENT, ty: BindingType::Sampler(if self.sample_type == FilterMode::Linear { SamplerBindingType::Filtering } else { SamplerBindingType::NonFiltering }), count: None, }, ], }); let pipeline_layout = self .device .create_pipeline_layout(&PipelineLayoutDescriptor { label: Some("wgpu::util::TextureBlitter::pipeline_layout"), bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); let shader = self.device.create_shader_module(include_wgsl!("blit.wgsl")); let pipeline = self .device .create_render_pipeline(&RenderPipelineDescriptor { label: Some("wgpu::util::TextureBlitter::pipeline"), layout: Some(&pipeline_layout), vertex: VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: PipelineCompilationOptions::default(), buffers: &[], }, primitive: PrimitiveState { topology: PrimitiveTopology::TriangleList, strip_index_format: None, front_face: FrontFace::Ccw, cull_mode: None, unclipped_depth: false, polygon_mode: wgt::PolygonMode::Fill, conservative: false, }, depth_stencil: None, multisample: MultisampleState::default(), fragment: Some(FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: PipelineCompilationOptions::default(), targets: &[Some(ColorTargetState { format: self.format, blend: self.blend_state, write_mask: ColorWrites::ALL, })], }), multiview_mask: None, cache: None, }); TextureBlitter { pipeline, bind_group_layout, sampler, } } } /// Texture Blitting (Copying) Utility /// /// Use this if you want to just render/copy texture A to texture B where [`CommandEncoder::copy_texture_to_texture`] would not work because: /// - Textures are in incompatible formats. /// - Textures are of different sizes. /// - Your copy destination is the surface texture and does not have the `COPY_DST` usage. pub struct TextureBlitter { pipeline: RenderPipeline, bind_group_layout: BindGroupLayout, sampler: Sampler, } impl TextureBlitter { /// Returns a [`TextureBlitter`] with default settings. /// /// # Arguments /// - `device` - A [`Device`] /// - `format` - The [`TextureFormat`] of the texture that will be copied to. This has to have the `RENDER_TARGET` usage. /// /// Properties of the blitting (such as the [`BlendState`]) can be customised by using [`TextureBlitterBuilder`] instead. pub fn new(device: &Device, format: TextureFormat) -> Self { TextureBlitterBuilder::new(device, format).build() } /// Copies the data from the source [`TextureView`] to the target [`TextureView`] /// /// # Arguments /// - `device` - A [`Device`] /// - `encoder` - A [`CommandEncoder`] /// - `source` - A [`TextureView`] that gets copied. The format does not matter. /// - `target` - A [`TextureView`] that gets the data copied from the `source`. It has to be the same format as the format specified in [`TextureBlitter::new`] pub fn copy( &self, device: &Device, encoder: &mut CommandEncoder, source: &TextureView, target: &TextureView, ) { let bind_group = device.create_bind_group(&BindGroupDescriptor { label: Some("wgpu::util::TextureBlitter::bind_group"), layout: &self.bind_group_layout, entries: &[ BindGroupEntry { binding: 0, resource: crate::BindingResource::TextureView(source), }, BindGroupEntry { binding: 1, resource: crate::BindingResource::Sampler(&self.sampler), }, ], }); let mut pass = encoder.begin_render_pass(&RenderPassDescriptor { label: Some("wgpu::util::TextureBlitter::pass"), color_attachments: &[Some(crate::RenderPassColorAttachment { view: target, depth_slice: None, resolve_target: None, ops: wgt::Operations { load: LoadOp::Load, store: StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, &bind_group, &[]); pass.draw(0..3, 0..1); } } ================================================ FILE: wgpu-core/Cargo.toml ================================================ [package] name = "wgpu-core" version.workspace = true authors.workspace = true edition.workspace = true description = "Core implementation logic of wgpu, the cross-platform, safe, pure-rust graphics API" homepage.workspace = true repository.workspace = true keywords.workspace = true license.workspace = true # Override the workspace's `rust-version` key. `wgpu-core` and its dependencies # have a less strict MSRV, to allow firefox more leeway in updating their Rust toolchain. # # See the repo README for more information on MSRV policy. rust-version = "1.87" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] targets = [ "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-pc-windows-msvc", "wasm32-unknown-unknown", ] [package.metadata.cargo-machete] # Cargo machete can't check build.rs dependencies. See https://github.com/bnjbvr/cargo-machete/issues/100 ignored = ["cfg_aliases"] [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wgpu_validate_locks)'] } [lib] [features] #! See documentation for the `wgpu` crate for more in-depth information on these features. # TODO(https://github.com/gfx-rs/wgpu/issues/6826): "std" is a default feature for # compatibility with prior behavior only, and should be removed once we know how # wgpu-core’s dependents want to handle no_std. default = ["std"] #! ### Logging Configuration # -------------------------------------------------------------------- ## Log all API entry points at info instead of trace level. ## Also, promotes certain debug log calls to info. api_log_info = [] ## Log resource lifecycle management at info instead of trace level. resource_log_info = [] #! ### Runtime Checks # -------------------------------------------------------------------- ## Apply run-time checks, even in release builds. These are in addition ## to the validation carried out at public APIs in all builds. strict_asserts = ["wgpu-types/strict_asserts"] #! ### Debugging # -------------------------------------------------------------------- ## Enable lock order observation. observe_locks = ["std", "dep:ron", "serde/serde_derive"] #! ### Serialization # -------------------------------------------------------------------- ## Enables serialization via `serde` on common wgpu types. serde = [ "dep:serde", "wgpu-types/serde", "arrayvec/serde", "hashbrown/serde", "smallvec/serde", "macro_rules_attribute", ] ## Enable API tracing. trace = ["serde", "std", "dep:ron", "naga/serialize", "wgpu-types/trace"] ## Enable API replaying replay = ["serde", "naga/deserialize"] #! ### Shading Language Support # -------------------------------------------------------------------- ## Enable `ShaderModuleSource::Wgsl` wgsl = ["naga/wgsl-in"] ## Enable `ShaderModuleSource::Glsl` glsl = ["naga/glsl-in"] ## Enable `ShaderModuleSource::SpirV` spirv = ["naga/spv-in"] #! ### Other # -------------------------------------------------------------------- ## Internally count resources and events for debugging purposes. If the counters ## feature is disabled, the counting infrastructure is removed from the build and ## the exposed counters always return 0. counters = ["wgpu-types/counters"] ## Implement `Send` and `Sync` on Wasm, but only if atomics are not enabled. fragile-send-sync-non-atomic-wasm = [ "wgpu-hal/fragile-send-sync-non-atomic-wasm", ] ## Enable certain items to be `Send` and `Sync` when they would not otherwise be. ## Also enables backtraces in some error cases when also under cfg(debug_assertions). std = [] #! ### External libraries # -------------------------------------------------------------------- #! The following features facilitate integration with third-party supporting libraries. ## Enable using the `mach-dxcompiler-rs` crate to compile DX12 shaders. static-dxc = ["wgpu-hal/static-dxc"] ## Enable portable atomics on platforms that do not support 64bit atomics. portable-atomic = ["dep:portable-atomic", "wgpu-hal/portable-atomic"] #! ### Target Conditional Features # -------------------------------------------------------------------- # Look to wgpu-hal's Cargo.toml for explaination how these features and the wgpu-core # platform crates collude to provide platform-specific behavior. ## DX12 backend dx12 = ["wgpu-core-deps-windows-linux-android/dx12"] ## Metal backend metal = ["wgpu-core-deps-apple/metal"] ## Vulkan backend, only available on Windows, Linux, Android vulkan = ["wgpu-core-deps-windows-linux-android/vulkan"] ## OpenGL backend, only available on Windows, Linux, Android, and Emscripten gles = [ "wgpu-core-deps-windows-linux-android/gles", "wgpu-core-deps-emscripten/gles", ] ## WebGL backend, only available on Emscripten webgl = ["wgpu-core-deps-wasm/webgl", "wgpu-types/web"] ## OpenGL backend, on macOS only angle = ["wgpu-core-deps-apple/angle"] ## Vulkan portability backend, only available on macOS vulkan-portability = ["wgpu-core-deps-apple/vulkan-portability"] ## Renderdoc integration, only available on Windows, Linux, and Android renderdoc = ["wgpu-core-deps-windows-linux-android/renderdoc"] ## Enable the `noop` backend. # TODO(https://github.com/gfx-rs/wgpu/issues/7120): there should be a hal feature noop = [] # The target limitation here isn't needed, but prevents more than one of these # platform crates from being included in the build at a time, preventing users # from getting confused by seeing them in the list of crates. [target.'cfg(target_vendor = "apple")'.dependencies] wgpu-core-deps-apple = { workspace = true, optional = true } [target.'cfg(target_os = "emscripten")'.dependencies] wgpu-core-deps-emscripten = { workspace = true, optional = true } [target.'cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))'.dependencies] wgpu-core-deps-wasm = { workspace = true, optional = true } [target.'cfg(any(windows, target_os = "linux", target_os = "android", target_os = "freebsd"))'.dependencies] wgpu-core-deps-windows-linux-android = { workspace = true, optional = true } [dependencies] naga.workspace = true wgpu-naga-bridge.workspace = true wgpu-hal.workspace = true wgpu-types.workspace = true arrayvec.workspace = true bit-vec.workspace = true bit-set.workspace = true bitflags.workspace = true bytemuck.workspace = true document-features.workspace = true hashbrown.workspace = true indexmap.workspace = true log.workspace = true macro_rules_attribute = { workspace = true, optional = true } once_cell = { workspace = true, features = ["std"] } parking_lot.workspace = true profiling = { workspace = true, default-features = false } raw-window-handle.workspace = true ron = { workspace = true, optional = true } rustc-hash.workspace = true serde = { workspace = true, features = ["default", "derive"], optional = true } smallvec.workspace = true thiserror.workspace = true [target.'cfg(not(target_has_atomic = "64"))'.dependencies] portable-atomic = { workspace = true, optional = true } [build-dependencies] cfg_aliases.workspace = true ================================================ FILE: wgpu-core/LICENSE.APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: wgpu-core/LICENSE.MIT ================================================ MIT License Copyright (c) 2025 The gfx-rs developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: wgpu-core/build.rs ================================================ fn main() { cfg_aliases::cfg_aliases! { windows_linux_android: { any(windows, target_os = "linux", target_os = "android", target_os = "freebsd") }, send_sync: { all( feature = "std", any( not(target_arch = "wasm32"), all(feature = "fragile-send-sync-non-atomic-wasm", not(target_feature = "atomics")) ) ) }, dx12: { all(target_os = "windows", feature = "dx12") }, webgl: { all(target_arch = "wasm32", not(target_os = "emscripten"), feature = "webgl") }, gles: { any( all(windows_linux_android, feature = "gles"), // Regular GLES all(webgl), // WebGL all(target_os = "emscripten", feature = "gles"), // Emscripten GLES all(target_vendor = "apple", feature = "angle") // ANGLE on Apple ) }, vulkan: { any( all(windows_linux_android, feature = "vulkan"), // Regular Vulkan all(target_vendor = "apple", feature = "vulkan-portability") // Vulkan Portability on Apple ) }, metal: { all(target_vendor = "apple", feature = "metal") }, supports_64bit_atomics: { target_has_atomic = "64" } } } ================================================ FILE: wgpu-core/platform-deps/apple/Cargo.toml ================================================ [package] name = "wgpu-core-deps-apple" version.workspace = true authors.workspace = true edition.workspace = true description = "Feature unification helper crate for Apple platforms" homepage.workspace = true repository.workspace = true keywords.workspace = true license.workspace = true readme = "README.md" # Override the workspace's `rust-version` key. Firefox uses `cargo vendor` to # copy the crates it actually uses out of the workspace, so it's meaningful for # them to have less restrictive MSRVs individually than the workspace as a # whole, if their code permits. See `../README.md` for details. rust-version = "1.76" [features] metal = ["wgpu-hal/metal"] angle = ["wgpu-hal/gles", "wgpu-hal/renderdoc"] vulkan-portability = ["wgpu-hal/vulkan", "wgpu-hal/renderdoc"] # Depend on wgpu-hal conditionally, so that the above features only apply to wgpu-hal on this set of platforms. [target.'cfg(target_vendor = "apple")'.dependencies] wgpu-hal.workspace = true ================================================ FILE: wgpu-core/platform-deps/apple/LICENSE.APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: wgpu-core/platform-deps/apple/LICENSE.MIT ================================================ MIT License Copyright (c) 2025 The gfx-rs developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: wgpu-core/platform-deps/apple/README.md ================================================ This crate exists to allow platform and feature specific features work correctly. The features enabled on this crate are only enabled on `target_vendor = "apple"` platforms. See wgpu-hal's `Cargo.toml` for more information. ================================================ FILE: wgpu-core/platform-deps/apple/src/lib.rs ================================================ //! This crate exists to allow platform and feature specific features work correctly. The features //! enabled on this crate are only enabled on `target_vendor = "apple"` platforms. See wgpu-hal's `Cargo.toml` //! for more information. ================================================ FILE: wgpu-core/platform-deps/emscripten/Cargo.toml ================================================ [package] name = "wgpu-core-deps-emscripten" version.workspace = true authors.workspace = true edition.workspace = true description = "Feature unification helper crate for the Emscripten platform" homepage.workspace = true repository.workspace = true keywords.workspace = true license.workspace = true readme = "README.md" # Override the workspace's `rust-version` key. Firefox uses `cargo vendor` to # copy the crates it actually uses out of the workspace, so it's meaningful for # them to have less restrictive MSRVs individually than the workspace as a # whole, if their code permits. See `../README.md` for details. rust-version = "1.76" [features] gles = ["wgpu-hal/gles"] # Depend on wgpu-hal conditionally, so that the above features only apply to wgpu-hal on this set of platforms. [target.'cfg(target_os = "emscripten")'.dependencies] wgpu-hal.workspace = true ================================================ FILE: wgpu-core/platform-deps/emscripten/LICENSE.APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: wgpu-core/platform-deps/emscripten/LICENSE.MIT ================================================ MIT License Copyright (c) 2025 The gfx-rs developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: wgpu-core/platform-deps/emscripten/README.md ================================================ This crate exists to allow platform and feature specific features work correctly. The features enabled on this crate are only enabled on `target_os = "emscripten"` platforms. See wgpu-hal's `Cargo.toml` for more information. ================================================ FILE: wgpu-core/platform-deps/emscripten/src/lib.rs ================================================ //! This crate exists to allow platform and feature specific features work correctly. The features //! enabled on this crate are only enabled on `target_os = "emscripten"` platforms. //! See wgpu-hal's `Cargo.toml` for more information. ================================================ FILE: wgpu-core/platform-deps/wasm/Cargo.toml ================================================ [package] name = "wgpu-core-deps-wasm" version.workspace = true authors.workspace = true edition.workspace = true description = "Feature unification helper crate for the WebAssembly platform" homepage.workspace = true repository.workspace = true keywords.workspace = true license.workspace = true readme = "README.md" # Override the workspace's `rust-version` key. Firefox uses `cargo vendor` to # copy the crates it actually uses out of the workspace, so it's meaningful for # them to have less restrictive MSRVs individually than the workspace as a # whole, if their code permits. See `../README.md` for details. rust-version = "1.76" [features] webgl = ["wgpu-hal/gles"] # Depend on wgpu-hal conditionally, so that the above features only apply to wgpu-hal on this set of platforms. [target.'cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))'.dependencies] wgpu-hal.workspace = true ================================================ FILE: wgpu-core/platform-deps/wasm/LICENSE.APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: wgpu-core/platform-deps/wasm/LICENSE.MIT ================================================ MIT License Copyright (c) 2025 The gfx-rs developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: wgpu-core/platform-deps/wasm/README.md ================================================ This crate exists to allow platform and feature specific features work correctly. The features enabled on this crate are only enabled on `target_arch = "wasm32"` platforms. See wgpu-hal's `Cargo.toml` for more information. ================================================ FILE: wgpu-core/platform-deps/wasm/src/lib.rs ================================================ //! This crate exists to allow platform and feature specific features work correctly. The features //! enabled on this crate are only enabled on `target_arch = "wasm32"` platforms. See wgpu-hal's `Cargo.toml` //! for more information. ================================================ FILE: wgpu-core/platform-deps/windows-linux-android/Cargo.toml ================================================ [package] name = "wgpu-core-deps-windows-linux-android" version.workspace = true authors.workspace = true edition.workspace = true description = "Feature unification helper crate for the Windows/Linux/Android platforms" homepage.workspace = true repository.workspace = true keywords.workspace = true license.workspace = true readme = "README.md" # Override the workspace's `rust-version` key. Firefox uses `cargo vendor` to # copy the crates it actually uses out of the workspace, so it's meaningful for # them to have less restrictive MSRVs individually than the workspace as a # whole, if their code permits. See `../README.md` for details. rust-version = "1.76" [features] gles = ["wgpu-hal/gles"] vulkan = ["wgpu-hal/vulkan"] dx12 = ["wgpu-hal/dx12"] renderdoc = ["wgpu-hal/renderdoc"] # Depend on wgpu-hal conditionally, so that the above features only apply to wgpu-hal on this set of platforms. [target.'cfg(any(windows, target_os = "linux", target_os = "android", target_os = "freebsd", target_os = "netbsd"))'.dependencies] wgpu-hal.workspace = true ================================================ FILE: wgpu-core/platform-deps/windows-linux-android/LICENSE.APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: wgpu-core/platform-deps/windows-linux-android/LICENSE.MIT ================================================ MIT License Copyright (c) 2025 The gfx-rs developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: wgpu-core/platform-deps/windows-linux-android/README.md ================================================ This crate exists to allow platform and feature specific features work correctly. The features enabled on this crate are only enabled on `windows`, `target_os = "linux"`, and `target_os = "android"` platforms. See wgpu-hal's `Cargo.toml` for more information. ================================================ FILE: wgpu-core/platform-deps/windows-linux-android/src/lib.rs ================================================ //! This crate exists to allow platform and feature specific features work correctly. The features //! enabled on this crate are only enabled on `windows`, `target_os = "linux"`, and `target_os = "android"` //! platforms. See wgpu-hal's `Cargo.toml` for more information. ================================================ FILE: wgpu-core/src/as_hal.rs ================================================ use core::{mem::ManuallyDrop, ops::Deref}; use alloc::sync::Arc; use hal::DynResource; use crate::{ device::Device, global::Global, id::{ AdapterId, BlasId, BufferId, CommandEncoderId, DeviceId, QueueId, SurfaceId, TextureId, TextureViewId, TlasId, }, lock::{RankData, RwLockReadGuard}, resource::RawResourceAccess, snatch::SnatchGuard, }; /// A guard which holds alive a wgpu-core resource and dereferences to the Hal type. struct SimpleResourceGuard { _guard: Resource, ptr: *const HalType, } impl SimpleResourceGuard { /// Creates a new guard from a resource, using a callback to derive the Hal type. pub fn new(guard: Resource, callback: C) -> Option where C: Fn(&Resource) -> Option<&HalType>, { // Derive the hal type from the resource and coerce it to a pointer. let ptr: *const HalType = callback(&guard)?; Some(Self { _guard: guard, ptr }) } } impl Deref for SimpleResourceGuard { type Target = HalType; fn deref(&self) -> &Self::Target { // SAFETY: The pointer is guaranteed to be valid as the original resource is // still alive and this guard cannot be used with snatchable resources. unsafe { &*self.ptr } } } unsafe impl Send for SimpleResourceGuard where Resource: Send, HalType: Send, { } unsafe impl Sync for SimpleResourceGuard where Resource: Sync, HalType: Sync, { } /// A guard which holds alive a snatchable wgpu-core resource and dereferences to the Hal type. struct SnatchableResourceGuard where Resource: RawResourceAccess, { resource: Arc, snatch_lock_rank_data: ManuallyDrop, ptr: *const HalType, } impl SnatchableResourceGuard where Resource: RawResourceAccess, HalType: 'static, { /// Creates a new guard from a snatchable resource. /// /// Returns `None` if: /// - The resource is not of the expected Hal type. /// - The resource has been destroyed. pub fn new(resource: Arc) -> Option { // Grab the snatchable lock. let snatch_guard = resource.device().snatchable_lock.read(); // Get the raw resource and downcast it to the expected Hal type. let underlying = resource .raw(&snatch_guard)? .as_any() .downcast_ref::()?; // Cast the raw resource to a pointer to get rid of the lifetime // connecting us to the snatch guard. let ptr: *const HalType = underlying; // SAFETY: At this point all panicking or divergance has already happened, // so we can safely forget the snatch guard without causing the lock to be left open. let snatch_lock_rank_data = SnatchGuard::forget(snatch_guard); // SAFETY: We only construct this guard while the snatchable lock is held, // as the `drop` implementation of this guard will unsafely release the lock. Some(Self { resource, snatch_lock_rank_data: ManuallyDrop::new(snatch_lock_rank_data), ptr, }) } } impl Deref for SnatchableResourceGuard where Resource: RawResourceAccess, { type Target = HalType; fn deref(&self) -> &Self::Target { // SAFETY: The pointer is guaranteed to be valid as the original resource is // still alive and the snatchable lock is still being held due to the forgotten // snatch guard. unsafe { &*self.ptr } } } impl Drop for SnatchableResourceGuard where Resource: RawResourceAccess, { fn drop(&mut self) { // SAFETY: // - We are not going to access the rank data anymore. let data = unsafe { ManuallyDrop::take(&mut self.snatch_lock_rank_data) }; // SAFETY: // - The pointer is no longer going to be accessed. // - The snatchable lock is being held because this type was not created // until after the snatchable lock was forgotten. unsafe { self.resource .device() .snatchable_lock .force_unlock_read(data) }; } } unsafe impl Send for SnatchableResourceGuard where Resource: RawResourceAccess + Send, HalType: Send, { } unsafe impl Sync for SnatchableResourceGuard where Resource: RawResourceAccess + Sync, HalType: Sync, { } /// A guard which holds alive a device and the device's fence lock, dereferencing to the Hal type. struct FenceGuard { device: Arc, fence_lock_rank_data: ManuallyDrop, ptr: *const Fence, } impl FenceGuard where Fence: 'static, { /// Creates a new guard over a device's fence. /// /// Returns `None` if: /// - The device's fence is not of the expected Hal type. pub fn new(device: Arc) -> Option { // Grab the fence lock. let fence_guard = device.fence.read(); // Get the raw fence and downcast it to the expected Hal type, coercing it to a pointer // to get rid of the lifetime connecting us to the fence guard. let ptr: *const Fence = fence_guard.as_any().downcast_ref::()?; // SAFETY: At this point all panicking or divergance has already happened, // so we can safely forget the fence guard without causing the lock to be left open. let fence_lock_rank_data = RwLockReadGuard::forget(fence_guard); // SAFETY: We only construct this guard while the fence lock is held, // as the `drop` implementation of this guard will unsafely release the lock. Some(Self { device, fence_lock_rank_data: ManuallyDrop::new(fence_lock_rank_data), ptr, }) } } impl Deref for FenceGuard { type Target = Fence; fn deref(&self) -> &Self::Target { // SAFETY: The pointer is guaranteed to be valid as the original device's fence // is still alive and the fence lock is still being held due to the forgotten // fence guard. unsafe { &*self.ptr } } } impl Drop for FenceGuard { fn drop(&mut self) { // SAFETY: // - We are not going to access the rank data anymore. let data = unsafe { ManuallyDrop::take(&mut self.fence_lock_rank_data) }; // SAFETY: // - The pointer is no longer going to be accessed. // - The fence lock is being held because this type was not created // until after the fence lock was forgotten. unsafe { self.device.fence.force_unlock_read(data); }; } } unsafe impl Send for FenceGuard where Fence: Send {} unsafe impl Sync for FenceGuard where Fence: Sync {} impl Global { /// # Safety /// /// - The raw buffer handle must not be manually destroyed pub unsafe fn buffer_as_hal( &self, id: BufferId, ) -> Option> { profiling::scope!("Buffer::as_hal"); let hub = &self.hub; let buffer = hub.buffers.get(id).get().ok()?; SnatchableResourceGuard::new(buffer) } /// # Safety /// /// - The raw texture handle must not be manually destroyed pub unsafe fn texture_as_hal( &self, id: TextureId, ) -> Option> { profiling::scope!("Texture::as_hal"); let hub = &self.hub; let texture = hub.textures.get(id).get().ok()?; SnatchableResourceGuard::new(texture) } /// # Safety /// /// - The raw texture view handle must not be manually destroyed pub unsafe fn texture_view_as_hal( &self, id: TextureViewId, ) -> Option> { profiling::scope!("TextureView::as_hal"); let hub = &self.hub; let view = hub.texture_views.get(id).get().ok()?; SnatchableResourceGuard::new(view) } /// # Safety /// /// - The raw adapter handle must not be manually destroyed pub unsafe fn adapter_as_hal( &self, id: AdapterId, ) -> Option> { profiling::scope!("Adapter::as_hal"); let hub = &self.hub; let adapter = hub.adapters.get(id); SimpleResourceGuard::new(adapter, move |adapter| { adapter.raw.adapter.as_any().downcast_ref() }) } /// # Safety /// /// - The raw device handle must not be manually destroyed pub unsafe fn device_as_hal( &self, id: DeviceId, ) -> Option> { profiling::scope!("Device::as_hal"); let device = self.hub.devices.get(id); SimpleResourceGuard::new(device, move |device| device.raw().as_any().downcast_ref()) } /// # Safety /// /// - The raw fence handle must not be manually destroyed pub unsafe fn device_fence_as_hal( &self, id: DeviceId, ) -> Option> { profiling::scope!("Device::fence_as_hal"); let device = self.hub.devices.get(id); FenceGuard::new(device) } /// # Safety /// - The raw surface handle must not be manually destroyed pub unsafe fn surface_as_hal( &self, id: SurfaceId, ) -> Option> { profiling::scope!("Surface::as_hal"); let surface = self.surfaces.get(id); SimpleResourceGuard::new(surface, move |surface| { surface.raw(A::VARIANT)?.as_any().downcast_ref() }) } /// Encode commands using the raw HAL command encoder. /// /// # Panics /// /// If the command encoder has already been used with the wgpu encoding API. /// /// # Safety /// /// - The raw command encoder handle must not be manually destroyed pub unsafe fn command_encoder_as_hal_mut< A: hal::Api, F: FnOnce(Option<&mut A::CommandEncoder>) -> R, R, >( &self, id: CommandEncoderId, hal_command_encoder_callback: F, ) -> R { profiling::scope!("CommandEncoder::as_hal"); let hub = &self.hub; let cmd_enc = hub.command_encoders.get(id); let mut cmd_buf_data = cmd_enc.data.lock(); cmd_buf_data.record_as_hal_mut(|opt_cmd_buf| -> R { hal_command_encoder_callback(opt_cmd_buf.and_then(|cmd_buf| { cmd_buf .encoder .open() .ok() .and_then(|encoder| encoder.as_any_mut().downcast_mut()) })) }) } /// # Safety /// /// - The raw queue handle must not be manually destroyed pub unsafe fn queue_as_hal( &self, id: QueueId, ) -> Option> { profiling::scope!("Queue::as_hal"); let queue = self.hub.queues.get(id); SimpleResourceGuard::new(queue, move |queue| queue.raw().as_any().downcast_ref()) } /// # Safety /// /// - The raw blas handle must not be manually destroyed pub unsafe fn blas_as_hal( &self, id: BlasId, ) -> Option> { profiling::scope!("Blas::as_hal"); let hub = &self.hub; let blas = hub.blas_s.get(id).get().ok()?; SnatchableResourceGuard::new(blas) } /// # Safety /// /// - The raw tlas handle must not be manually destroyed pub unsafe fn tlas_as_hal( &self, id: TlasId, ) -> Option> { profiling::scope!("Tlas::as_hal"); let hub = &self.hub; let tlas = hub.tlas_s.get(id).get().ok()?; SnatchableResourceGuard::new(tlas) } } ================================================ FILE: wgpu-core/src/binding_model.rs ================================================ use alloc::{ borrow::{Cow, ToOwned}, boxed::Box, string::String, sync::{Arc, Weak}, vec::Vec, }; use core::{fmt, mem::ManuallyDrop, ops::Range}; use arrayvec::ArrayVec; use thiserror::Error; #[cfg(feature = "serde")] use serde::Deserialize; #[cfg(feature = "serde")] use serde::Serialize; use wgt::error::{ErrorType, WebGpuError}; use crate::{ device::{bgl, Device, DeviceError, MissingDownlevelFlags, MissingFeatures}, id::{BindGroupLayoutId, BufferId, ExternalTextureId, SamplerId, TextureViewId, TlasId}, init_tracker::{BufferInitTrackerAction, TextureInitTrackerAction}, pipeline::{ComputePipeline, RenderPipeline}, resource::{ Buffer, DestroyedResourceError, ExternalTexture, InvalidResourceError, Labeled, MissingBufferUsageError, MissingTextureUsageError, RawResourceAccess, ResourceErrorIdent, Sampler, TextureView, Tlas, TrackingData, }, resource_log, snatch::{SnatchGuard, Snatchable}, track::{BindGroupStates, ResourceUsageCompatibilityError}, Label, }; #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum BindGroupLayoutEntryError { #[error("Cube dimension is not expected for texture storage")] StorageTextureCube, #[error("Atomic storage textures are not allowed by baseline webgpu, they require the native only feature TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES")] StorageTextureAtomic, #[error("Arrays of bindings unsupported for this type of binding")] ArrayUnsupported, #[error("Multisampled binding with sample type `TextureSampleType::Float` must have filterable set to false.")] SampleTypeFloatFilterableBindingMultisampled, #[error("Multisampled texture binding view dimension must be 2d, got {0:?}")] Non2DMultisampled(wgt::TextureViewDimension), #[error(transparent)] MissingFeatures(#[from] MissingFeatures), #[error(transparent)] MissingDownlevelFlags(#[from] MissingDownlevelFlags), } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateBindGroupLayoutError { #[error(transparent)] Device(#[from] DeviceError), #[error("Conflicting binding at index {0}")] ConflictBinding(u32), #[error("Binding {binding} entry is invalid")] Entry { binding: u32, #[source] error: BindGroupLayoutEntryError, }, #[error(transparent)] TooManyBindings(BindingTypeMaxCountError), #[error("Bind groups may not contain both a binding array and a dynamically offset buffer")] ContainsBothBindingArrayAndDynamicOffsetArray, #[error("Bind groups may not contain both a binding array and a uniform buffer")] ContainsBothBindingArrayAndUniformBuffer, #[error("Binding index {binding} is greater than the maximum number {maximum}")] InvalidBindingIndex { binding: u32, maximum: u32 }, #[error("Invalid visibility {0:?}")] InvalidVisibility(wgt::ShaderStages), #[error("Binding index {binding}: {access:?} access to storage textures with format {format:?} is not supported")] UnsupportedStorageTextureAccess { binding: u32, access: wgt::StorageTextureAccess, format: wgt::TextureFormat, }, } impl WebGpuError for CreateBindGroupLayoutError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::ConflictBinding(_) | Self::Entry { .. } | Self::TooManyBindings(_) | Self::InvalidBindingIndex { .. } | Self::InvalidVisibility(_) | Self::ContainsBothBindingArrayAndDynamicOffsetArray | Self::ContainsBothBindingArrayAndUniformBuffer | Self::UnsupportedStorageTextureAccess { .. } => ErrorType::Validation, } } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum BindingError { #[error(transparent)] DestroyedResource(#[from] DestroyedResourceError), #[error("Buffer {buffer}: Binding with size {binding_size} at offset {offset} would overflow buffer size of {buffer_size}")] BindingRangeTooLarge { buffer: ResourceErrorIdent, offset: wgt::BufferAddress, binding_size: u64, buffer_size: u64, }, #[error("Buffer {buffer}: Binding offset {offset} is greater than buffer size {buffer_size}")] BindingOffsetTooLarge { buffer: ResourceErrorIdent, offset: wgt::BufferAddress, buffer_size: u64, }, } impl WebGpuError for BindingError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::DestroyedResource(e) => e.webgpu_error_type(), Self::BindingRangeTooLarge { .. } | Self::BindingOffsetTooLarge { .. } => { ErrorType::Validation } } } } // TODO: there may be additional variants here that can be extracted into // `BindingError`. #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateBindGroupError { #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] DestroyedResource(#[from] DestroyedResourceError), #[error(transparent)] BindingError(#[from] BindingError), #[error( "Binding count declared with at most {expected} items, but {actual} items were provided" )] BindingArrayPartialLengthMismatch { actual: usize, expected: usize }, #[error( "Binding count declared with exactly {expected} items, but {actual} items were provided" )] BindingArrayLengthMismatch { actual: usize, expected: usize }, #[error("Array binding provided zero elements")] BindingArrayZeroLength, #[error("Binding size {actual} of {buffer} is less than minimum {min}")] BindingSizeTooSmall { buffer: ResourceErrorIdent, actual: u64, min: u64, }, #[error("{0} binding size is zero")] BindingZeroSize(ResourceErrorIdent), #[error("Number of bindings in bind group descriptor ({actual}) does not match the number of bindings defined in the bind group layout ({expected})")] BindingsNumMismatch { actual: usize, expected: usize }, #[error("Binding {0} is used at least twice in the descriptor")] DuplicateBinding(u32), #[error("Unable to find a corresponding declaration for the given binding {0}")] MissingBindingDeclaration(u32), #[error(transparent)] MissingBufferUsage(#[from] MissingBufferUsageError), #[error(transparent)] MissingTextureUsage(#[from] MissingTextureUsageError), #[error("Binding declared as a single item, but bind group is using it as an array")] SingleBindingExpected, #[error("Effective buffer binding size {size} for storage buffers is expected to align to {alignment}, but size is {size}")] UnalignedEffectiveBufferBindingSizeForStorage { alignment: u32, size: u64 }, #[error("Buffer offset {0} does not respect device's requested `{1}` limit {2}")] UnalignedBufferOffset(wgt::BufferAddress, &'static str, u32), #[error( "Buffer binding {binding} range {given} exceeds `max_*_buffer_binding_size` limit {limit}" )] BufferRangeTooLarge { binding: u32, given: u64, limit: u64, }, #[error("Binding {binding} has a different type ({actual:?}) than the one in the layout ({expected:?})")] WrongBindingType { // Index of the binding binding: u32, // The type given to the function actual: wgt::BindingType, // Human-readable description of expected types expected: &'static str, }, #[error("Texture binding {binding} expects multisampled = {layout_multisampled}, but given a view with samples = {view_samples}")] InvalidTextureMultisample { binding: u32, layout_multisampled: bool, view_samples: u32, }, #[error( "Texture binding {} expects sample type {:?}, but was given a view with format {:?} (sample type {:?})", binding, layout_sample_type, view_format, view_sample_type )] InvalidTextureSampleType { binding: u32, layout_sample_type: wgt::TextureSampleType, view_format: wgt::TextureFormat, view_sample_type: wgt::TextureSampleType, }, #[error("Texture binding {binding} expects dimension = {layout_dimension:?}, but given a view with dimension = {view_dimension:?}")] InvalidTextureDimension { binding: u32, layout_dimension: wgt::TextureViewDimension, view_dimension: wgt::TextureViewDimension, }, #[error("Storage texture binding {binding} expects format = {layout_format:?}, but given a view with format = {view_format:?}")] InvalidStorageTextureFormat { binding: u32, layout_format: wgt::TextureFormat, view_format: wgt::TextureFormat, }, #[error("Storage texture bindings must have a single mip level, but given a view with mip_level_count = {mip_level_count:?} at binding {binding}")] InvalidStorageTextureMipLevelCount { binding: u32, mip_level_count: u32 }, #[error("External texture bindings must have a single mip level, but given a view with mip_level_count = {mip_level_count:?} at binding {binding}")] InvalidExternalTextureMipLevelCount { binding: u32, mip_level_count: u32 }, #[error("External texture bindings must have a format of `rgba8unorm`, `bgra8unorm`, or `rgba16float, but given a view with format = {format:?} at binding {binding}")] InvalidExternalTextureFormat { binding: u32, format: wgt::TextureFormat, }, #[error("Sampler binding {binding} expects comparison = {layout_cmp}, but given a sampler with comparison = {sampler_cmp}")] WrongSamplerComparison { binding: u32, layout_cmp: bool, sampler_cmp: bool, }, #[error("Sampler binding {binding} expects filtering = {layout_flt}, but given a sampler with filtering = {sampler_flt}")] WrongSamplerFiltering { binding: u32, layout_flt: bool, sampler_flt: bool, }, #[error("TLAS binding {binding} is required to support vertex returns but is missing flag AccelerationStructureFlags::ALLOW_RAY_HIT_VERTEX_RETURN")] MissingTLASVertexReturn { binding: u32 }, #[error("Bound texture views can not have both depth and stencil aspects enabled")] DepthStencilAspect, #[error(transparent)] ResourceUsageCompatibility(#[from] ResourceUsageCompatibilityError), #[error(transparent)] InvalidResource(#[from] InvalidResourceError), } impl WebGpuError for CreateBindGroupError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::DestroyedResource(e) => e.webgpu_error_type(), Self::BindingError(e) => e.webgpu_error_type(), Self::MissingBufferUsage(e) => e.webgpu_error_type(), Self::MissingTextureUsage(e) => e.webgpu_error_type(), Self::ResourceUsageCompatibility(e) => e.webgpu_error_type(), Self::InvalidResource(e) => e.webgpu_error_type(), Self::BindingArrayPartialLengthMismatch { .. } | Self::BindingArrayLengthMismatch { .. } | Self::BindingArrayZeroLength | Self::BindingSizeTooSmall { .. } | Self::BindingsNumMismatch { .. } | Self::BindingZeroSize(_) | Self::DuplicateBinding(_) | Self::MissingBindingDeclaration(_) | Self::SingleBindingExpected | Self::UnalignedEffectiveBufferBindingSizeForStorage { .. } | Self::UnalignedBufferOffset(_, _, _) | Self::BufferRangeTooLarge { .. } | Self::WrongBindingType { .. } | Self::InvalidTextureMultisample { .. } | Self::InvalidTextureSampleType { .. } | Self::InvalidTextureDimension { .. } | Self::InvalidStorageTextureFormat { .. } | Self::InvalidStorageTextureMipLevelCount { .. } | Self::WrongSamplerComparison { .. } | Self::WrongSamplerFiltering { .. } | Self::DepthStencilAspect | Self::MissingTLASVertexReturn { .. } | Self::InvalidExternalTextureMipLevelCount { .. } | Self::InvalidExternalTextureFormat { .. } => ErrorType::Validation, } } } #[derive(Clone, Debug, Error)] pub enum BindingZone { #[error("Stage {0:?}")] Stage(wgt::ShaderStages), #[error("Whole pipeline")] Pipeline, } #[derive(Clone, Debug, Error)] #[error("Too many bindings of type {kind:?} in {zone}, limit is {limit}, count was {count}. Check the limit `{}` passed to `Adapter::request_device`", .kind.to_config_str())] pub struct BindingTypeMaxCountError { pub kind: BindingTypeMaxCountErrorKind, pub zone: BindingZone, pub limit: u32, pub count: u32, } impl WebGpuError for BindingTypeMaxCountError { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } } #[derive(Clone, Debug)] pub enum BindingTypeMaxCountErrorKind { DynamicUniformBuffers, DynamicStorageBuffers, SampledTextures, Samplers, StorageBuffers, StorageTextures, UniformBuffers, BindingArrayElements, BindingArraySamplerElements, BindingArrayAccelerationStructureElements, AccelerationStructures, } impl BindingTypeMaxCountErrorKind { fn to_config_str(&self) -> &'static str { match self { BindingTypeMaxCountErrorKind::DynamicUniformBuffers => { "max_dynamic_uniform_buffers_per_pipeline_layout" } BindingTypeMaxCountErrorKind::DynamicStorageBuffers => { "max_dynamic_storage_buffers_per_pipeline_layout" } BindingTypeMaxCountErrorKind::SampledTextures => { "max_sampled_textures_per_shader_stage" } BindingTypeMaxCountErrorKind::Samplers => "max_samplers_per_shader_stage", BindingTypeMaxCountErrorKind::StorageBuffers => "max_storage_buffers_per_shader_stage", BindingTypeMaxCountErrorKind::StorageTextures => { "max_storage_textures_per_shader_stage" } BindingTypeMaxCountErrorKind::UniformBuffers => "max_uniform_buffers_per_shader_stage", BindingTypeMaxCountErrorKind::BindingArrayElements => { "max_binding_array_elements_per_shader_stage" } BindingTypeMaxCountErrorKind::BindingArraySamplerElements => { "max_binding_array_sampler_elements_per_shader_stage" } BindingTypeMaxCountErrorKind::BindingArrayAccelerationStructureElements => { "max_binding_array_acceleration_structure_elements_per_shader_stage" } BindingTypeMaxCountErrorKind::AccelerationStructures => { "max_acceleration_structures_per_shader_stage" } } } } #[derive(Debug, Default)] pub(crate) struct PerStageBindingTypeCounter { vertex: u32, fragment: u32, compute: u32, } impl PerStageBindingTypeCounter { pub(crate) fn add(&mut self, stage: wgt::ShaderStages, count: u32) { if stage.contains(wgt::ShaderStages::VERTEX) { self.vertex += count; } if stage.contains(wgt::ShaderStages::FRAGMENT) { self.fragment += count; } if stage.contains(wgt::ShaderStages::COMPUTE) { self.compute += count; } } pub(crate) fn max(&self) -> (BindingZone, u32) { let max_value = self.vertex.max(self.fragment.max(self.compute)); let mut stage = wgt::ShaderStages::NONE; if max_value == self.vertex { stage |= wgt::ShaderStages::VERTEX } if max_value == self.fragment { stage |= wgt::ShaderStages::FRAGMENT } if max_value == self.compute { stage |= wgt::ShaderStages::COMPUTE } (BindingZone::Stage(stage), max_value) } pub(crate) fn merge(&mut self, other: &Self) { self.vertex += other.vertex; self.fragment += other.fragment; self.compute += other.compute; } pub(crate) fn validate( &self, limit: u32, kind: BindingTypeMaxCountErrorKind, ) -> Result<(), BindingTypeMaxCountError> { let (zone, count) = self.max(); if limit < count { Err(BindingTypeMaxCountError { kind, zone, limit, count, }) } else { Ok(()) } } } #[derive(Debug, Default)] pub(crate) struct BindingTypeMaxCountValidator { dynamic_uniform_buffers: u32, dynamic_storage_buffers: u32, sampled_textures: PerStageBindingTypeCounter, samplers: PerStageBindingTypeCounter, storage_buffers: PerStageBindingTypeCounter, storage_textures: PerStageBindingTypeCounter, uniform_buffers: PerStageBindingTypeCounter, acceleration_structures: PerStageBindingTypeCounter, binding_array_elements: PerStageBindingTypeCounter, binding_array_sampler_elements: PerStageBindingTypeCounter, binding_array_acceleration_structure_elements: PerStageBindingTypeCounter, has_bindless_array: bool, } impl BindingTypeMaxCountValidator { pub(crate) fn add_binding(&mut self, binding: &wgt::BindGroupLayoutEntry) { let count = binding.count.map_or(1, |count| count.get()); if binding.count.is_some() { self.binding_array_elements.add(binding.visibility, count); self.has_bindless_array = true; match binding.ty { wgt::BindingType::Sampler(_) => { self.binding_array_sampler_elements .add(binding.visibility, count); } wgt::BindingType::AccelerationStructure { .. } => { self.binding_array_acceleration_structure_elements .add(binding.visibility, count); } _ => {} } } else { match binding.ty { wgt::BindingType::Buffer { ty: wgt::BufferBindingType::Uniform, has_dynamic_offset, .. } => { self.uniform_buffers.add(binding.visibility, count); if has_dynamic_offset { self.dynamic_uniform_buffers += count; } } wgt::BindingType::Buffer { ty: wgt::BufferBindingType::Storage { .. }, has_dynamic_offset, .. } => { self.storage_buffers.add(binding.visibility, count); if has_dynamic_offset { self.dynamic_storage_buffers += count; } } wgt::BindingType::Sampler { .. } => { self.samplers.add(binding.visibility, count); } wgt::BindingType::Texture { .. } => { self.sampled_textures.add(binding.visibility, count); } wgt::BindingType::StorageTexture { .. } => { self.storage_textures.add(binding.visibility, count); } wgt::BindingType::AccelerationStructure { .. } => { self.acceleration_structures.add(binding.visibility, count); } wgt::BindingType::ExternalTexture => { // https://www.w3.org/TR/webgpu/#gpuexternaltexture // In order to account for many possible representations, // the binding conservatively uses the following, for each // external texture: // * Three sampled textures for up to 3 planes // * One additional sampled texture for a 3D LUT // * One sampler to sample the LUT // * One uniform buffer for metadata self.sampled_textures.add(binding.visibility, count * 4); self.samplers.add(binding.visibility, count); self.uniform_buffers.add(binding.visibility, count); } } } } pub(crate) fn merge(&mut self, other: &Self) { self.dynamic_uniform_buffers += other.dynamic_uniform_buffers; self.dynamic_storage_buffers += other.dynamic_storage_buffers; self.sampled_textures.merge(&other.sampled_textures); self.samplers.merge(&other.samplers); self.storage_buffers.merge(&other.storage_buffers); self.storage_textures.merge(&other.storage_textures); self.uniform_buffers.merge(&other.uniform_buffers); self.acceleration_structures .merge(&other.acceleration_structures); self.binding_array_elements .merge(&other.binding_array_elements); self.binding_array_sampler_elements .merge(&other.binding_array_sampler_elements); self.binding_array_acceleration_structure_elements .merge(&other.binding_array_acceleration_structure_elements); } pub(crate) fn validate(&self, limits: &wgt::Limits) -> Result<(), BindingTypeMaxCountError> { if limits.max_dynamic_uniform_buffers_per_pipeline_layout < self.dynamic_uniform_buffers { return Err(BindingTypeMaxCountError { kind: BindingTypeMaxCountErrorKind::DynamicUniformBuffers, zone: BindingZone::Pipeline, limit: limits.max_dynamic_uniform_buffers_per_pipeline_layout, count: self.dynamic_uniform_buffers, }); } if limits.max_dynamic_storage_buffers_per_pipeline_layout < self.dynamic_storage_buffers { return Err(BindingTypeMaxCountError { kind: BindingTypeMaxCountErrorKind::DynamicStorageBuffers, zone: BindingZone::Pipeline, limit: limits.max_dynamic_storage_buffers_per_pipeline_layout, count: self.dynamic_storage_buffers, }); } self.sampled_textures.validate( limits.max_sampled_textures_per_shader_stage, BindingTypeMaxCountErrorKind::SampledTextures, )?; self.samplers.validate( limits.max_samplers_per_shader_stage, BindingTypeMaxCountErrorKind::Samplers, )?; self.storage_buffers.validate( limits.max_storage_buffers_per_shader_stage, BindingTypeMaxCountErrorKind::StorageBuffers, )?; self.storage_textures.validate( limits.max_storage_textures_per_shader_stage, BindingTypeMaxCountErrorKind::StorageTextures, )?; self.uniform_buffers.validate( limits.max_uniform_buffers_per_shader_stage, BindingTypeMaxCountErrorKind::UniformBuffers, )?; self.binding_array_elements.validate( limits.max_binding_array_elements_per_shader_stage, BindingTypeMaxCountErrorKind::BindingArrayElements, )?; self.binding_array_sampler_elements.validate( limits.max_binding_array_sampler_elements_per_shader_stage, BindingTypeMaxCountErrorKind::BindingArraySamplerElements, )?; self.binding_array_acceleration_structure_elements .validate( limits.max_binding_array_acceleration_structure_elements_per_shader_stage, BindingTypeMaxCountErrorKind::BindingArrayAccelerationStructureElements, )?; self.acceleration_structures.validate( limits.max_acceleration_structures_per_shader_stage, BindingTypeMaxCountErrorKind::AccelerationStructures, )?; Ok(()) } /// Validate that the bind group layout does not contain both a binding array and a dynamic offset array. /// /// This allows us to use `UPDATE_AFTER_BIND` on vulkan for bindless arrays. Vulkan does not allow /// `UPDATE_AFTER_BIND` on dynamic offset arrays. See pub(crate) fn validate_binding_arrays(&self) -> Result<(), CreateBindGroupLayoutError> { let has_dynamic_offset_array = self.dynamic_uniform_buffers > 0 || self.dynamic_storage_buffers > 0; let has_uniform_buffer = self.uniform_buffers.max().1 > 0; if self.has_bindless_array && has_dynamic_offset_array { return Err(CreateBindGroupLayoutError::ContainsBothBindingArrayAndDynamicOffsetArray); } if self.has_bindless_array && has_uniform_buffer { return Err(CreateBindGroupLayoutError::ContainsBothBindingArrayAndUniformBuffer); } Ok(()) } } /// Bindable resource and the slot to bind it to. /// cbindgen:ignore #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct BindGroupEntry< 'a, B = BufferId, S = SamplerId, TV = TextureViewId, TLAS = TlasId, ET = ExternalTextureId, > where [BufferBinding]: ToOwned, [S]: ToOwned, [TV]: ToOwned, [TLAS]: ToOwned, <[BufferBinding] as ToOwned>::Owned: fmt::Debug, <[S] as ToOwned>::Owned: fmt::Debug, <[TV] as ToOwned>::Owned: fmt::Debug, <[TLAS] as ToOwned>::Owned: fmt::Debug, { /// Slot for which binding provides resource. Corresponds to an entry of the same /// binding index in the [`BindGroupLayoutDescriptor`]. pub binding: u32, #[cfg_attr( feature = "serde", serde(bound(deserialize = "BindingResource<'a, B, S, TV, TLAS, ET>: Deserialize<'de>")) )] /// Resource to attach to the binding pub resource: BindingResource<'a, B, S, TV, TLAS, ET>, } /// cbindgen:ignore pub type ResolvedBindGroupEntry<'a> = BindGroupEntry< 'a, Arc, Arc, Arc, Arc, Arc, >; /// Describes a group of bindings and the resources to be bound. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct BindGroupDescriptor< 'a, BGL = BindGroupLayoutId, B = BufferId, S = SamplerId, TV = TextureViewId, TLAS = TlasId, ET = ExternalTextureId, > where [BufferBinding]: ToOwned, [S]: ToOwned, [TV]: ToOwned, [TLAS]: ToOwned, <[BufferBinding] as ToOwned>::Owned: fmt::Debug, <[S] as ToOwned>::Owned: fmt::Debug, <[TV] as ToOwned>::Owned: fmt::Debug, <[TLAS] as ToOwned>::Owned: fmt::Debug, [BindGroupEntry<'a, B, S, TV, TLAS, ET>]: ToOwned, <[BindGroupEntry<'a, B, S, TV, TLAS, ET>] as ToOwned>::Owned: fmt::Debug, { /// Debug label of the bind group. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// The [`BindGroupLayout`] that corresponds to this bind group. pub layout: BGL, #[cfg_attr( feature = "serde", serde(bound( deserialize = "<[BindGroupEntry<'a, B, S, TV, TLAS, ET>] as ToOwned>::Owned: Deserialize<'de>" )) )] /// The resources to bind to this bind group. #[allow(clippy::type_complexity)] pub entries: Cow<'a, [BindGroupEntry<'a, B, S, TV, TLAS, ET>]>, } /// cbindgen:ignore pub type ResolvedBindGroupDescriptor<'a> = BindGroupDescriptor< 'a, Arc, Arc, Arc, Arc, Arc, Arc, >; /// Describes a [`BindGroupLayout`]. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BindGroupLayoutDescriptor<'a> { /// Debug label of the bind group layout. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// Array of entries in this BindGroupLayout pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>, } /// Used by [`BindGroupLayout`]. It indicates whether the BGL must be /// used with a specific pipeline. This constraint only happens when /// the BGLs have been derived from a pipeline without a layout. #[derive(Clone, Debug)] pub(crate) enum ExclusivePipeline { None, Render(Weak), Compute(Weak), } impl fmt::Display for ExclusivePipeline { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ExclusivePipeline::None => f.write_str("None"), ExclusivePipeline::Render(p) => { if let Some(p) = p.upgrade() { p.error_ident().fmt(f) } else { f.write_str("RenderPipeline") } } ExclusivePipeline::Compute(p) => { if let Some(p) = p.upgrade() { p.error_ident().fmt(f) } else { f.write_str("ComputePipeline") } } } } } #[derive(Debug)] pub enum RawBindGroupLayout { Owning(ManuallyDrop>), /// The empty BGL was created by the device and will be destroyed by the device. RefDeviceEmptyBGL, } /// Bind group layout. #[derive(Debug)] pub struct BindGroupLayout { pub(crate) raw: RawBindGroupLayout, pub(crate) device: Arc, pub(crate) entries: bgl::EntryMap, /// It is very important that we know if the bind group comes from the BGL pool. /// /// If it does, then we need to remove it from the pool when we drop it. /// /// We cannot unconditionally remove from the pool, as BGLs that don't come from the pool /// (derived BGLs) must not be removed. pub(crate) origin: bgl::Origin, pub(crate) exclusive_pipeline: crate::OnceCellOrLock, pub(crate) binding_count_validator: BindingTypeMaxCountValidator, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, } impl Drop for BindGroupLayout { fn drop(&mut self) { resource_log!("Destroy raw {}", self.error_ident()); if matches!(self.origin, bgl::Origin::Pool) { self.device.bgl_pool.remove(&self.entries); } match self.raw { RawBindGroupLayout::Owning(ref mut raw) => { // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(raw) }; unsafe { self.device.raw().destroy_bind_group_layout(raw); } } RawBindGroupLayout::RefDeviceEmptyBGL => {} } } } crate::impl_resource_type!(BindGroupLayout); crate::impl_labeled!(BindGroupLayout); crate::impl_parent_device!(BindGroupLayout); crate::impl_storage_item!(BindGroupLayout); impl BindGroupLayout { pub(crate) fn raw(&self) -> &dyn hal::DynBindGroupLayout { match &self.raw { RawBindGroupLayout::Owning(raw) => raw.as_ref(), RawBindGroupLayout::RefDeviceEmptyBGL => self.device.empty_bgl.as_ref(), } } fn empty(device: &Arc) -> Arc { let exclusive_pipeline = crate::OnceCellOrLock::new(); exclusive_pipeline.set(ExclusivePipeline::None).unwrap(); Arc::new(Self { raw: RawBindGroupLayout::RefDeviceEmptyBGL, device: device.clone(), entries: bgl::EntryMap::default(), origin: bgl::Origin::Derived, exclusive_pipeline, binding_count_validator: BindingTypeMaxCountValidator::default(), label: String::new(), }) } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreatePipelineLayoutError { #[error(transparent)] Device(#[from] DeviceError), #[error( "Immediate data has range bound {size} which is not aligned to IMMEDIATE_DATA_ALIGNMENT ({})", wgt::IMMEDIATE_DATA_ALIGNMENT )] MisalignedImmediateSize { size: u32 }, #[error(transparent)] MissingFeatures(#[from] MissingFeatures), #[error( "Immediate data has size {size} which exceeds device immediate data size limit 0..{max}" )] ImmediateRangeTooLarge { size: u32, max: u32 }, #[error(transparent)] TooManyBindings(BindingTypeMaxCountError), #[error("Bind group layout count {actual} exceeds device bind group limit {max}")] TooManyGroups { actual: usize, max: usize }, #[error(transparent)] InvalidResource(#[from] InvalidResourceError), #[error("Bind group layout at index {index} has an exclusive pipeline: {pipeline}")] BglHasExclusivePipeline { index: usize, pipeline: String }, } impl WebGpuError for CreatePipelineLayoutError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::MissingFeatures(e) => e.webgpu_error_type(), Self::InvalidResource(e) => e.webgpu_error_type(), Self::TooManyBindings(e) => e.webgpu_error_type(), Self::MisalignedImmediateSize { .. } | Self::ImmediateRangeTooLarge { .. } | Self::TooManyGroups { .. } | Self::BglHasExclusivePipeline { .. } => ErrorType::Validation, } } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum ImmediateUploadError { #[error( "Start offset {start_offset} overruns the immediate data range with a size of {immediate_size}" )] StartOffsetOverrun { start_offset: u32, immediate_size: u32, }, #[error( "Provided immediate data start offset {0} does not respect \ `IMMEDIATE_DATA_ALIGNMENT` ({ida})", ida = wgt::IMMEDIATE_DATA_ALIGNMENT )] StartOffsetUnaligned(u32), #[error( "Provided immediate data byte size {0} does not respect \ `IMMEDIATE_DATA_ALIGNMENT` ({ida})", ida = wgt::IMMEDIATE_DATA_ALIGNMENT )] SizeUnaligned(u32), #[error( "Provided immediate data start offset {} + size {} overruns the immediate data range \ with a size of {}", start_offset, size, immediate_size )] EndOffsetOverrun { start_offset: u32, size: u32, immediate_size: u32, }, #[error("Start index {start_index} overruns the value data range with {data_size} element(s)")] ValueStartIndexOverrun { start_index: u32, data_size: usize }, #[error( "Start index {} + count of {} overruns the value data range \ with {} element(s)", start_index, count, data_size )] ValueEndIndexOverrun { start_index: u32, count: u32, data_size: usize, }, } impl WebGpuError for ImmediateUploadError { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } } /// Describes a pipeline layout. /// /// A `PipelineLayoutDescriptor` can be used to create a pipeline layout. #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "BGL: Serialize"))] pub struct PipelineLayoutDescriptor<'a, BGL = BindGroupLayoutId> where [Option]: ToOwned, <[Option] as ToOwned>::Owned: fmt::Debug, { /// Debug label of the pipeline layout. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// Bind groups that this pipeline uses. The first entry will provide all the bindings for /// "set = 0", second entry will provide all the bindings for "set = 1" etc. #[cfg_attr( feature = "serde", serde(bound(deserialize = "<[Option] as ToOwned>::Owned: Deserialize<'de>")) )] pub bind_group_layouts: Cow<'a, [Option]>, /// The number of bytes of immediate data that are allocated for use /// in the shader. The `var`s in the shader attached to /// this pipeline must be equal or smaller than this size. /// /// If this value is non-zero, [`wgt::Features::IMMEDIATES`] must be enabled. pub immediate_size: u32, } /// cbindgen:ignore pub type ResolvedPipelineLayoutDescriptor<'a, BGL = Arc> = PipelineLayoutDescriptor<'a, BGL>; #[derive(Debug)] pub struct PipelineLayout { pub(crate) raw: ManuallyDrop>, pub(crate) device: Arc, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) bind_group_layouts: ArrayVec>, { hal::MAX_BIND_GROUPS }>, pub(crate) immediate_size: u32, } impl Drop for PipelineLayout { fn drop(&mut self) { resource_log!("Destroy raw {}", self.error_ident()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; unsafe { self.device.raw().destroy_pipeline_layout(raw); } } } impl PipelineLayout { pub(crate) fn raw(&self) -> &dyn hal::DynPipelineLayout { self.raw.as_ref() } pub fn get_bind_group_layout( self: &Arc, index: u32, ) -> Result, GetBindGroupLayoutError> { let max_bind_groups = self.device.limits.max_bind_groups; if index >= max_bind_groups { return Err(GetBindGroupLayoutError::IndexOutOfRange { index, max: max_bind_groups, }); } Ok(self .bind_group_layouts .get(index as usize) .cloned() .flatten() .unwrap_or_else(|| BindGroupLayout::empty(&self.device))) } pub(crate) fn get_bgl_entry( &self, group: u32, binding: u32, ) -> Option<&wgt::BindGroupLayoutEntry> { let bgl = self.bind_group_layouts.get(group as usize)?; let bgl = bgl.as_ref()?; bgl.entries.get(binding) } /// Validate immediates match up with expected ranges. pub(crate) fn validate_immediates_ranges( &self, offset: u32, size_bytes: u32, ) -> Result<(), ImmediateUploadError> { // Don't need to validate size against the immediate data size limit here, // as immediate data ranges are already validated to be within bounds, // and we validate that they are within the ranges. if !offset.is_multiple_of(wgt::IMMEDIATE_DATA_ALIGNMENT) { return Err(ImmediateUploadError::StartOffsetUnaligned(offset)); } if !size_bytes.is_multiple_of(wgt::IMMEDIATE_DATA_ALIGNMENT) { return Err(ImmediateUploadError::SizeUnaligned(offset)); } if offset > self.immediate_size { return Err(ImmediateUploadError::StartOffsetOverrun { start_offset: offset, immediate_size: self.immediate_size, }); } if size_bytes > self.immediate_size - offset { return Err(ImmediateUploadError::EndOffsetOverrun { start_offset: offset, size: size_bytes, immediate_size: self.immediate_size, }); } Ok(()) } } crate::impl_resource_type!(PipelineLayout); crate::impl_labeled!(PipelineLayout); crate::impl_parent_device!(PipelineLayout); crate::impl_storage_item!(PipelineLayout); #[repr(C)] #[derive(Clone, Debug, Hash, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct BufferBinding { pub buffer: B, pub offset: wgt::BufferAddress, /// Size of the binding. If `None`, the binding spans from `offset` to the /// end of the buffer. /// /// We use `BufferAddress` to allow a size of zero on this `wgpu_core` type, /// because JavaScript bindings cannot readily express `Option`. /// The `wgpu` API uses `Option` (i.e. `NonZeroU64`) for this /// field. pub size: Option, } pub type ResolvedBufferBinding = BufferBinding>; // Note: Duplicated in `wgpu-rs` as `BindingResource` // They're different enough that it doesn't make sense to share a common type #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum BindingResource< 'a, B = BufferId, S = SamplerId, TV = TextureViewId, TLAS = TlasId, ET = ExternalTextureId, > where [BufferBinding]: ToOwned, [S]: ToOwned, [TV]: ToOwned, [TLAS]: ToOwned, <[BufferBinding] as ToOwned>::Owned: fmt::Debug, <[S] as ToOwned>::Owned: fmt::Debug, <[TV] as ToOwned>::Owned: fmt::Debug, <[TLAS] as ToOwned>::Owned: fmt::Debug, { Buffer(BufferBinding), #[cfg_attr( feature = "serde", serde(bound(deserialize = "<[BufferBinding] as ToOwned>::Owned: Deserialize<'de>")) )] BufferArray(Cow<'a, [BufferBinding]>), Sampler(S), #[cfg_attr( feature = "serde", serde(bound(deserialize = "<[S] as ToOwned>::Owned: Deserialize<'de>")) )] SamplerArray(Cow<'a, [S]>), TextureView(TV), #[cfg_attr( feature = "serde", serde(bound(deserialize = "<[TV] as ToOwned>::Owned: Deserialize<'de>")) )] TextureViewArray(Cow<'a, [TV]>), AccelerationStructure(TLAS), #[cfg_attr( feature = "serde", serde(bound(deserialize = "<[TLAS] as ToOwned>::Owned: Deserialize<'de>")) )] AccelerationStructureArray(Cow<'a, [TLAS]>), ExternalTexture(ET), } pub type ResolvedBindingResource<'a> = BindingResource< 'a, Arc, Arc, Arc, Arc, Arc, >; #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum BindError { #[error( "Dynamic offsets not expected with null bind group at index {group}. However {actual} dynamic offset{s1} were provided.", s1 = if *.actual >= 2 { "s" } else { "" }, )] DynamicOffsetCountNotZero { group: u32, actual: usize }, #[error( "{bind_group} {group} expects {expected} dynamic offset{s0}. However {actual} dynamic offset{s1} were provided.", s0 = if *.expected >= 2 { "s" } else { "" }, s1 = if *.actual >= 2 { "s" } else { "" }, )] MismatchedDynamicOffsetCount { bind_group: ResourceErrorIdent, group: u32, actual: usize, expected: usize, }, #[error( "Dynamic binding index {idx} (targeting {bind_group} {group}, binding {binding}) with value {offset}, does not respect device's requested `{limit_name}` limit: {alignment}" )] UnalignedDynamicBinding { bind_group: ResourceErrorIdent, idx: usize, group: u32, binding: u32, offset: u32, alignment: u32, limit_name: &'static str, }, #[error( "Dynamic binding offset index {idx} with offset {offset} would overrun the buffer bound to {bind_group} {group} -> binding {binding}. \ Buffer size is {buffer_size} bytes, the binding binds bytes {binding_range:?}, meaning the maximum the binding can be offset is {maximum_dynamic_offset} bytes", )] DynamicBindingOutOfBounds { bind_group: ResourceErrorIdent, idx: usize, group: u32, binding: u32, offset: u32, buffer_size: wgt::BufferAddress, binding_range: Range, maximum_dynamic_offset: wgt::BufferAddress, }, } impl WebGpuError for BindError { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } } #[derive(Debug)] pub struct BindGroupDynamicBindingData { /// The index of the binding. /// /// Used for more descriptive errors. pub(crate) binding_idx: u32, /// The size of the buffer. /// /// Used for more descriptive errors. pub(crate) buffer_size: wgt::BufferAddress, /// The range that the binding covers. /// /// Used for more descriptive errors. pub(crate) binding_range: Range, /// The maximum value the dynamic offset can have before running off the end of the buffer. pub(crate) maximum_dynamic_offset: wgt::BufferAddress, /// The binding type. pub(crate) binding_type: wgt::BufferBindingType, } pub(crate) fn buffer_binding_type_alignment( limits: &wgt::Limits, binding_type: wgt::BufferBindingType, ) -> (u32, &'static str) { match binding_type { wgt::BufferBindingType::Uniform => ( limits.min_uniform_buffer_offset_alignment, "min_uniform_buffer_offset_alignment", ), wgt::BufferBindingType::Storage { .. } => ( limits.min_storage_buffer_offset_alignment, "min_storage_buffer_offset_alignment", ), } } pub(crate) fn buffer_binding_type_bounds_check_alignment( alignments: &hal::Alignments, binding_type: wgt::BufferBindingType, ) -> wgt::BufferAddress { match binding_type { wgt::BufferBindingType::Uniform => alignments.uniform_bounds_check_alignment.get(), wgt::BufferBindingType::Storage { .. } => wgt::COPY_BUFFER_ALIGNMENT, } } #[derive(Debug)] pub(crate) struct BindGroupLateBufferBindingInfo { /// The normal binding index in the bind group. pub binding_index: u32, /// The size that exists at bind time. pub size: wgt::BufferSize, } #[derive(Debug)] pub struct BindGroup { pub(crate) raw: Snatchable>, pub(crate) device: Arc, pub(crate) layout: Arc, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, pub(crate) used: BindGroupStates, pub(crate) used_buffer_ranges: Vec, pub(crate) used_texture_ranges: Vec, pub(crate) dynamic_binding_info: Vec, /// Actual binding sizes for buffers that don't have `min_binding_size` /// specified in BGL. Listed in the order of iteration of `BGL.entries`. pub(crate) late_buffer_binding_infos: Vec, } impl Drop for BindGroup { fn drop(&mut self) { if let Some(raw) = self.raw.take() { resource_log!("Destroy raw {}", self.error_ident()); unsafe { self.device.raw().destroy_bind_group(raw); } } } } impl BindGroup { pub(crate) fn try_raw<'a>( &'a self, guard: &'a SnatchGuard, ) -> Result<&'a dyn hal::DynBindGroup, DestroyedResourceError> { // Clippy insist on writing it this way. The idea is to return None // if any of the raw buffer is not valid anymore. for buffer in &self.used_buffer_ranges { buffer.buffer.try_raw(guard)?; } for texture in &self.used_texture_ranges { texture.texture.try_raw(guard)?; } self.raw .get(guard) .map(|raw| raw.as_ref()) .ok_or_else(|| DestroyedResourceError(self.error_ident())) } pub(crate) fn validate_dynamic_bindings( &self, bind_group_index: u32, offsets: &[wgt::DynamicOffset], ) -> Result<(), BindError> { if self.dynamic_binding_info.len() != offsets.len() { return Err(BindError::MismatchedDynamicOffsetCount { bind_group: self.error_ident(), group: bind_group_index, expected: self.dynamic_binding_info.len(), actual: offsets.len(), }); } for (idx, (info, &offset)) in self .dynamic_binding_info .iter() .zip(offsets.iter()) .enumerate() { let (alignment, limit_name) = buffer_binding_type_alignment(&self.device.limits, info.binding_type); if !(offset as wgt::BufferAddress).is_multiple_of(alignment as u64) { return Err(BindError::UnalignedDynamicBinding { bind_group: self.error_ident(), group: bind_group_index, binding: info.binding_idx, idx, offset, alignment, limit_name, }); } if offset as wgt::BufferAddress > info.maximum_dynamic_offset { return Err(BindError::DynamicBindingOutOfBounds { bind_group: self.error_ident(), group: bind_group_index, binding: info.binding_idx, idx, offset, buffer_size: info.buffer_size, binding_range: info.binding_range.clone(), maximum_dynamic_offset: info.maximum_dynamic_offset, }); } } Ok(()) } } crate::impl_resource_type!(BindGroup); crate::impl_labeled!(BindGroup); crate::impl_parent_device!(BindGroup); crate::impl_storage_item!(BindGroup); crate::impl_trackable!(BindGroup); #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum GetBindGroupLayoutError { #[error("Bind group layout index {index} is greater than the device's configured `max_bind_groups` limit {max}")] IndexOutOfRange { index: u32, max: u32 }, #[error(transparent)] InvalidResource(#[from] InvalidResourceError), } impl WebGpuError for GetBindGroupLayoutError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::IndexOutOfRange { .. } => ErrorType::Validation, Self::InvalidResource(e) => e.webgpu_error_type(), } } } #[derive(Clone, Debug, Error, Eq, PartialEq)] #[error( "In bind group index {group_index}, the buffer bound at binding index {binding_index} \ is bound with size {bound_size} where the shader expects {shader_size}." )] pub struct LateMinBufferBindingSizeMismatch { pub group_index: u32, pub binding_index: u32, pub shader_size: wgt::BufferAddress, pub bound_size: wgt::BufferAddress, } ================================================ FILE: wgpu-core/src/command/allocator.rs ================================================ use alloc::{boxed::Box, vec::Vec}; use crate::lock::{rank, Mutex}; /// A pool of free [`wgpu_hal::CommandEncoder`]s, owned by a `Device`. /// /// Each encoder in this list is in the "closed" state. /// /// Since a raw [`CommandEncoder`][ce] is itself a pool for allocating /// raw [`CommandBuffer`][cb]s, this is a pool of pools. /// /// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder /// [ce]: hal::CommandEncoder /// [cb]: hal::Api::CommandBuffer pub(crate) struct CommandAllocator { free_encoders: Mutex>>, } impl CommandAllocator { pub(crate) fn new() -> Self { Self { free_encoders: Mutex::new(rank::COMMAND_ALLOCATOR_FREE_ENCODERS, Vec::new()), } } /// Return a fresh [`wgpu_hal::CommandEncoder`] in the "closed" state. /// /// If we have free encoders in the pool, take one of those. Otherwise, /// create a new one on `device`. /// /// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder pub(crate) fn acquire_encoder( &self, device: &dyn hal::DynDevice, queue: &dyn hal::DynQueue, ) -> Result, hal::DeviceError> { let mut free_encoders = self.free_encoders.lock(); match free_encoders.pop() { Some(encoder) => Ok(encoder), None => unsafe { let hal_desc = hal::CommandEncoderDescriptor { label: None, queue }; device.create_command_encoder(&hal_desc) }, } } /// Add `encoder` back to the free pool. pub(crate) fn release_encoder(&self, encoder: Box) { let mut free_encoders = self.free_encoders.lock(); free_encoders.push(encoder); } } ================================================ FILE: wgpu-core/src/command/bind.rs ================================================ use alloc::{boxed::Box, sync::Arc, vec::Vec}; use thiserror::Error; use crate::{ binding_model::{BindGroup, LateMinBufferBindingSizeMismatch, PipelineLayout}, pipeline::LateSizedBufferGroup, resource::{Labeled, ParentDevice, ResourceErrorIdent}, }; mod compat { use alloc::{ string::{String, ToString as _}, sync::{Arc, Weak}, vec::Vec, }; use core::num::NonZeroU32; use thiserror::Error; use wgt::{BindingType, ShaderStages}; use crate::{ binding_model::BindGroupLayout, error::MultiError, resource::{Labeled, ParentDevice, ResourceErrorIdent}, }; pub(crate) enum Error { Incompatible { expected_bgl: ResourceErrorIdent, assigned_bgl: ResourceErrorIdent, inner: MultiError, }, Missing, } #[derive(Debug, Clone)] struct Entry { assigned: Option>, expected: Option>, } impl Entry { const fn empty() -> Self { Self { assigned: None, expected: None, } } fn is_active(&self) -> bool { self.assigned.is_some() && self.expected.is_some() } fn is_valid(&self) -> bool { if let Some(expected_bgl) = self.expected.as_ref() { if let Some(assigned_bgl) = self.assigned.as_ref() { expected_bgl.is_equal(assigned_bgl) } else { false } } else { false } } fn check(&self) -> Result<(), Error> { if let Some(expected_bgl) = self.expected.as_ref() { if let Some(assigned_bgl) = self.assigned.as_ref() { if expected_bgl.is_equal(assigned_bgl) { Ok(()) } else { #[derive(Clone, Debug, Error)] #[error( "Exclusive pipelines don't match: expected {expected}, got {assigned}" )] struct IncompatibleExclusivePipelines { expected: String, assigned: String, } use crate::binding_model::ExclusivePipeline; match ( expected_bgl.exclusive_pipeline.get().unwrap(), assigned_bgl.exclusive_pipeline.get().unwrap(), ) { (ExclusivePipeline::None, ExclusivePipeline::None) => {} ( ExclusivePipeline::Render(e_pipeline), ExclusivePipeline::Render(a_pipeline), ) if Weak::ptr_eq(e_pipeline, a_pipeline) => {} ( ExclusivePipeline::Compute(e_pipeline), ExclusivePipeline::Compute(a_pipeline), ) if Weak::ptr_eq(e_pipeline, a_pipeline) => {} (expected, assigned) => { return Err(Error::Incompatible { expected_bgl: expected_bgl.error_ident(), assigned_bgl: assigned_bgl.error_ident(), inner: MultiError::new(core::iter::once( IncompatibleExclusivePipelines { expected: expected.to_string(), assigned: assigned.to_string(), }, )) .unwrap(), }); } } #[derive(Clone, Debug, Error)] enum EntryError { #[error("Entries with binding {binding} differ in visibility: expected {expected:?}, got {assigned:?}")] Visibility { binding: u32, expected: ShaderStages, assigned: ShaderStages, }, #[error("Entries with binding {binding} differ in type: expected {expected:?}, got {assigned:?}")] Type { binding: u32, expected: BindingType, assigned: BindingType, }, #[error("Entries with binding {binding} differ in count: expected {expected:?}, got {assigned:?}")] Count { binding: u32, expected: Option, assigned: Option, }, #[error("Expected entry with binding {binding} not found in assigned bind group layout")] ExtraExpected { binding: u32 }, #[error("Assigned entry with binding {binding} not found in expected bind group layout")] ExtraAssigned { binding: u32 }, } let mut errors = Vec::new(); for (&binding, expected_entry) in expected_bgl.entries.iter() { if let Some(assigned_entry) = assigned_bgl.entries.get(binding) { if assigned_entry.visibility != expected_entry.visibility { errors.push(EntryError::Visibility { binding, expected: expected_entry.visibility, assigned: assigned_entry.visibility, }); } if assigned_entry.ty != expected_entry.ty { errors.push(EntryError::Type { binding, expected: expected_entry.ty, assigned: assigned_entry.ty, }); } if assigned_entry.count != expected_entry.count { errors.push(EntryError::Count { binding, expected: expected_entry.count, assigned: assigned_entry.count, }); } } else { errors.push(EntryError::ExtraExpected { binding }); } } for (&binding, _) in assigned_bgl.entries.iter() { if !expected_bgl.entries.contains_key(binding) { errors.push(EntryError::ExtraAssigned { binding }); } } Err(Error::Incompatible { expected_bgl: expected_bgl.error_ident(), assigned_bgl: assigned_bgl.error_ident(), inner: MultiError::new(errors.drain(..)).unwrap(), }) } } else { Err(Error::Missing) } } else { Ok(()) } } } #[derive(Debug)] pub(super) struct BoundBindGroupLayouts { entries: [Entry; hal::MAX_BIND_GROUPS], rebind_start: usize, } impl BoundBindGroupLayouts { pub fn new() -> Self { Self { entries: [const { Entry::empty() }; hal::MAX_BIND_GROUPS], rebind_start: 0, } } /// Takes the start index of the bind group range to be rebound, and clears it. pub fn take_rebind_start_index(&mut self) -> usize { let start = self.rebind_start; self.rebind_start = self.entries.len(); start } pub fn update_rebind_start_index(&mut self, start_index: usize) { self.rebind_start = self.rebind_start.min(start_index); } pub fn update_expectations(&mut self, expectations: &[Option>]) { let mut rebind_start_index = None; for (i, (e, new_expected_bgl)) in self .entries .iter_mut() .zip(expectations.iter().chain(core::iter::repeat(&None))) .enumerate() { let (must_set, must_rebind) = match (&mut e.expected, new_expected_bgl) { (None, None) => (false, false), (None, Some(_)) => (true, true), (Some(_), None) => (true, false), (Some(old_expected_bgl), Some(new_expected_bgl)) => { let is_different = !old_expected_bgl.is_equal(new_expected_bgl); (is_different, is_different) } }; if must_set { e.expected = new_expected_bgl.clone(); } if must_rebind && rebind_start_index.is_none() { rebind_start_index = Some(i); } } if let Some(rebind_start_index) = rebind_start_index { self.update_rebind_start_index(rebind_start_index); } } pub fn assign(&mut self, index: usize, value: Arc) { self.entries[index].assigned = Some(value); self.update_rebind_start_index(index); } pub fn clear(&mut self, index: usize) { self.entries[index].assigned = None; } pub fn list_active(&self) -> impl Iterator + '_ { self.entries .iter() .enumerate() .filter_map(|(i, e)| if e.is_active() { Some(i) } else { None }) } pub fn list_valid(&self) -> impl Iterator + '_ { self.entries .iter() .enumerate() .filter_map(|(i, e)| if e.is_valid() { Some(i) } else { None }) } #[allow(clippy::result_large_err)] pub fn get_invalid(&self) -> Result<(), (usize, Error)> { for (index, entry) in self.entries.iter().enumerate() { entry.check().map_err(|e| (index, e))?; } Ok(()) } } } #[derive(Clone, Debug, Error)] pub enum BinderError { #[error("The current set {pipeline} expects a BindGroup to be set at index {index}")] MissingBindGroup { index: usize, pipeline: ResourceErrorIdent, }, #[error("The {assigned_bgl} of current set {assigned_bg} at index {index} is not compatible with the corresponding {expected_bgl} of {pipeline}")] IncompatibleBindGroup { expected_bgl: ResourceErrorIdent, assigned_bgl: ResourceErrorIdent, assigned_bg: ResourceErrorIdent, index: usize, pipeline: ResourceErrorIdent, #[source] inner: crate::error::MultiError, }, } #[derive(Debug)] struct LateBufferBinding { binding_index: u32, shader_expect_size: wgt::BufferAddress, bound_size: wgt::BufferAddress, } #[derive(Debug, Default)] struct EntryPayload { group: Option>, dynamic_offsets: Vec, late_buffer_bindings: Vec, /// Since `LateBufferBinding` may contain information about the bindings /// not used by the pipeline, we need to know when to stop validating. late_bindings_effective_count: usize, } impl EntryPayload { fn reset(&mut self) { self.group = None; self.dynamic_offsets.clear(); self.late_buffer_bindings.clear(); self.late_bindings_effective_count = 0; } } #[derive(Debug)] pub(super) struct Binder { pub(super) pipeline_layout: Option>, manager: compat::BoundBindGroupLayouts, payloads: [EntryPayload; hal::MAX_BIND_GROUPS], } impl Binder { pub(super) fn new() -> Self { Self { pipeline_layout: None, manager: compat::BoundBindGroupLayouts::new(), payloads: Default::default(), } } pub(super) fn reset(&mut self) { self.pipeline_layout = None; self.manager = compat::BoundBindGroupLayouts::new(); for payload in self.payloads.iter_mut() { payload.reset(); } } /// Returns `true` if the pipeline layout has been changed, i.e. if the /// new PL was not the same as the old PL. pub(super) fn change_pipeline_layout<'a>( &'a mut self, new: &Arc, late_sized_buffer_groups: &[LateSizedBufferGroup], ) -> bool { if let Some(old) = self.pipeline_layout.as_ref() { if old.is_equal(new) { return false; } } let old = self.pipeline_layout.replace(new.clone()); self.manager.update_expectations(&new.bind_group_layouts); // Update the buffer binding sizes that are required by shaders. for (payload, late_group) in self.payloads.iter_mut().zip(late_sized_buffer_groups) { payload.late_bindings_effective_count = late_group.shader_sizes.len(); // Update entries that already exist as the bind group was bound before the pipeline // was bound. for (late_binding, &shader_expect_size) in payload .late_buffer_bindings .iter_mut() .zip(late_group.shader_sizes.iter()) { late_binding.shader_expect_size = shader_expect_size; } // Add new entries for the bindings that were not known when the bind group was bound. if late_group.shader_sizes.len() > payload.late_buffer_bindings.len() { for &shader_expect_size in late_group.shader_sizes[payload.late_buffer_bindings.len()..].iter() { payload.late_buffer_bindings.push(LateBufferBinding { binding_index: 0, shader_expect_size, bound_size: 0, }); } } } if let Some(old) = old { // root constants are the base compatibility property if old.immediate_size != new.immediate_size { self.manager.update_rebind_start_index(0); } } true } pub(super) fn assign_group<'a>( &'a mut self, index: usize, bind_group: &Arc, offsets: &[wgt::DynamicOffset], ) { let payload = &mut self.payloads[index]; payload.group = Some(bind_group.clone()); payload.dynamic_offsets.clear(); payload.dynamic_offsets.extend_from_slice(offsets); // Fill out the actual binding sizes for buffers, // whose layout doesn't specify `min_binding_size`. // Update entries that already exist as the pipeline was bound before the group // was bound. for (late_binding, late_info) in payload .late_buffer_bindings .iter_mut() .zip(bind_group.late_buffer_binding_infos.iter()) { late_binding.binding_index = late_info.binding_index; late_binding.bound_size = late_info.size.get(); } // Add new entries for the bindings that were not known when the pipeline was bound. if bind_group.late_buffer_binding_infos.len() > payload.late_buffer_bindings.len() { for late_info in bind_group.late_buffer_binding_infos[payload.late_buffer_bindings.len()..].iter() { payload.late_buffer_bindings.push(LateBufferBinding { binding_index: late_info.binding_index, shader_expect_size: 0, bound_size: late_info.size.get(), }); } } self.manager.assign(index, bind_group.layout.clone()); } pub(super) fn clear_group(&mut self, index: usize) { self.payloads[index].reset(); self.manager.clear(index); } /// Takes the start index of the bind group range to be rebound, and clears it. pub(super) fn take_rebind_start_index(&mut self) -> usize { self.manager.take_rebind_start_index() } pub(super) fn list_valid_with_start( &self, start: usize, ) -> impl Iterator, &[wgt::DynamicOffset])> + '_ { let payloads = &self.payloads; self.manager .list_valid() .filter(move |i| *i >= start) .map(move |index| { ( index, payloads[index].group.as_ref().unwrap(), payloads[index].dynamic_offsets.as_slice(), ) }) } pub(super) fn list_active(&self) -> impl Iterator> + '_ { let payloads = &self.payloads; self.manager .list_active() .map(move |index| payloads[index].group.as_ref().unwrap()) } pub(super) fn list_valid( &self, ) -> impl Iterator, &[wgt::DynamicOffset])> + '_ { self.list_valid_with_start(0) } pub(super) fn check_compatibility( &self, pipeline: &T, ) -> Result<(), Box> { self.manager.get_invalid().map_err(|(index, error)| { Box::new(match error { compat::Error::Incompatible { expected_bgl, assigned_bgl, inner, } => BinderError::IncompatibleBindGroup { expected_bgl, assigned_bgl, assigned_bg: self.payloads[index].group.as_ref().unwrap().error_ident(), index, pipeline: pipeline.error_ident(), inner, }, compat::Error::Missing => BinderError::MissingBindGroup { index, pipeline: pipeline.error_ident(), }, }) }) } /// Scan active buffer bindings corresponding to layouts without `min_binding_size` specified. pub(super) fn check_late_buffer_bindings( &self, ) -> Result<(), LateMinBufferBindingSizeMismatch> { for group_index in self.manager.list_active() { let payload = &self.payloads[group_index]; for late_binding in &payload.late_buffer_bindings[..payload.late_bindings_effective_count] { if late_binding.bound_size < late_binding.shader_expect_size { return Err(LateMinBufferBindingSizeMismatch { group_index: group_index as u32, binding_index: late_binding.binding_index, shader_size: late_binding.shader_expect_size, bound_size: late_binding.bound_size, }); } } } Ok(()) } } ================================================ FILE: wgpu-core/src/command/bundle.rs ================================================ /*! Render Bundles A render bundle is a prerecorded sequence of commands that can be replayed on a command encoder with a single call. A single bundle can replayed any number of times, on different encoders. Constructing a render bundle lets `wgpu` validate and analyze its commands up front, so that replaying a bundle can be more efficient than simply re-recording its commands each time. Not all commands are available in bundles; for example, a render bundle may not contain a [`RenderCommand::SetViewport`] command. Most of `wgpu`'s backend graphics APIs have something like bundles. For example, Vulkan calls them "secondary command buffers", and Metal calls them "indirect command buffers". Although we plan to take advantage of these platform features at some point in the future, for now `wgpu`'s implementation of render bundles does not use them: at the hal level, `wgpu` render bundles just replay the commands. ## Render Bundle Isolation One important property of render bundles is that the draw calls in a render bundle depend solely on the pipeline and state established within the render bundle itself. A draw call in a bundle will never use a vertex buffer, say, that was set in the `RenderPass` before executing the bundle. We call this property 'isolation', in that a render bundle is somewhat isolated from the passes that use it. Render passes are also isolated from the effects of bundles. After executing a render bundle, a render pass's pipeline, bind groups, and vertex and index buffers are are unset, so the bundle cannot affect later draw calls in the pass. A render pass is not fully isolated from a bundle's effects on immediate data values. Draw calls following a bundle's execution will see whatever values the bundle writes to immediate data storage. Setting a pipeline initializes any push constant storage it could access to zero, and this initialization may also be visible after bundle execution. ## Render Bundle Lifecycle To create a render bundle: 1) Create a [`RenderBundleEncoder`] by calling [`Global::device_create_render_bundle_encoder`][Gdcrbe]. 2) Record commands in the `RenderBundleEncoder` using functions from the [`bundle_ffi`] module. 3) Call [`Global::render_bundle_encoder_finish`][Grbef], which analyzes and cleans up the command stream and returns a `RenderBundleId`. 4) Then, any number of times, call [`render_pass_execute_bundles`][wrpeb] to execute the bundle as part of some render pass. ## Implementation The most complex part of render bundles is the "finish" step, mostly implemented in [`RenderBundleEncoder::finish`]. This consumes the commands stored in the encoder's [`BasePass`], while validating everything, tracking the state, dropping redundant or unnecessary commands, and presenting the results as a new [`RenderBundle`]. It doesn't actually execute any commands. This step also enforces the 'isolation' property mentioned above: every draw call is checked to ensure that the resources it uses on were established since the last time the pipeline was set. This means the bundle can be executed verbatim without any state tracking. ### Execution When the bundle is used in an actual render pass, `RenderBundle::execute` is called. It goes through the commands and issues them into the native command buffer. Thanks to isolation, it doesn't track any bind group invalidations or index format changes. [Gdcrbe]: crate::global::Global::device_create_render_bundle_encoder [Grbef]: crate::global::Global::render_bundle_encoder_finish [wrpeb]: crate::global::Global::render_pass_execute_bundles !*/ #![allow(clippy::reversed_empty_ranges)] use alloc::{ borrow::{Cow, ToOwned as _}, string::String, sync::Arc, vec::Vec, }; use core::{ convert::Infallible, num::{NonZeroU32, NonZeroU64}, ops::Range, }; use arrayvec::ArrayVec; use thiserror::Error; use wgpu_hal::ShouldBeNonZeroExt; use wgt::error::{ErrorType, WebGpuError}; #[cfg(feature = "trace")] use crate::command::ArcReferences; use crate::{ binding_model::{BindError, BindGroup, PipelineLayout}, command::{ bind::Binder, BasePass, BindGroupStateChange, ColorAttachmentError, DrawError, IdReferences, MapPassErr, PassErrorScope, RenderCommand, RenderCommandError, StateChange, }, device::{AttachmentData, Device, DeviceError, MissingDownlevelFlags, RenderPassContext}, hub::Hub, id, init_tracker::{BufferInitTrackerAction, MemoryInitKind, TextureInitTrackerAction}, pipeline::{PipelineFlags, RenderPipeline, VertexStep}, resource::{ Buffer, DestroyedResourceError, Fallible, InvalidResourceError, Labeled, ParentDevice, RawResourceAccess, TrackingData, }, resource_log, snatch::SnatchGuard, track::RenderBundleScope, Label, LabelHelpers, }; use super::{pass, render_command::ArcRenderCommand, DrawCommandFamily, DrawKind}; /// Describes a [`RenderBundleEncoder`]. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct RenderBundleEncoderDescriptor<'a> { /// Debug label of the render bundle encoder. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// The formats of the color attachments that this render bundle is capable /// to rendering to. /// /// This must match the formats of the color attachments in the /// renderpass this render bundle is executed in. pub color_formats: Cow<'a, [Option]>, /// Information about the depth attachment that this render bundle is /// capable to rendering to. /// /// The format must match the format of the depth attachments in the /// renderpass this render bundle is executed in. pub depth_stencil: Option, /// Sample count this render bundle is capable of rendering to. /// /// This must match the pipelines and the renderpasses it is used in. pub sample_count: u32, /// If this render bundle will rendering to multiple array layers in the /// attachments at the same time. pub multiview: Option, } #[derive(Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct RenderBundleEncoder { base: BasePass, Infallible>, parent_id: id::DeviceId, pub(crate) context: RenderPassContext, pub(crate) is_depth_read_only: bool, pub(crate) is_stencil_read_only: bool, // Resource binding dedupe state. #[cfg_attr(feature = "serde", serde(skip))] current_bind_groups: BindGroupStateChange, #[cfg_attr(feature = "serde", serde(skip))] current_pipeline: StateChange, } impl RenderBundleEncoder { pub fn new( desc: &RenderBundleEncoderDescriptor, parent_id: id::DeviceId, ) -> Result { let (is_depth_read_only, is_stencil_read_only) = match desc.depth_stencil { Some(ds) => { let aspects = hal::FormatAspects::from(ds.format); ( !aspects.contains(hal::FormatAspects::DEPTH) || ds.depth_read_only, !aspects.contains(hal::FormatAspects::STENCIL) || ds.stencil_read_only, ) } // There's no depth/stencil attachment, so these values just don't // matter. Choose the most accommodating value, to simplify // validation. None => (true, true), }; // TODO: should be device.limits.max_color_attachments let max_color_attachments = hal::MAX_COLOR_ATTACHMENTS; //TODO: validate that attachment formats are renderable, // have expected aspects, support multisampling. Ok(Self { base: BasePass::new(&desc.label), parent_id, context: RenderPassContext { attachments: AttachmentData { colors: if desc.color_formats.len() > max_color_attachments { return Err(CreateRenderBundleError::ColorAttachment( ColorAttachmentError::TooMany { given: desc.color_formats.len(), limit: max_color_attachments, }, )); } else { desc.color_formats.iter().cloned().collect() }, resolves: ArrayVec::new(), depth_stencil: desc.depth_stencil.map(|ds| ds.format), }, sample_count: { let sc = desc.sample_count; if sc == 0 || sc > 32 || !sc.is_power_of_two() { return Err(CreateRenderBundleError::InvalidSampleCount(sc)); } sc }, multiview_mask: desc.multiview, }, is_depth_read_only, is_stencil_read_only, current_bind_groups: BindGroupStateChange::new(), current_pipeline: StateChange::new(), }) } pub fn dummy(parent_id: id::DeviceId) -> Self { Self { base: BasePass::new(&None), parent_id, context: RenderPassContext { attachments: AttachmentData { colors: ArrayVec::new(), resolves: ArrayVec::new(), depth_stencil: None, }, sample_count: 0, multiview_mask: None, }, is_depth_read_only: false, is_stencil_read_only: false, current_bind_groups: BindGroupStateChange::new(), current_pipeline: StateChange::new(), } } pub fn parent(&self) -> id::DeviceId { self.parent_id } /// Convert this encoder's commands into a [`RenderBundle`]. /// /// We want executing a [`RenderBundle`] to be quick, so we take /// this opportunity to clean up the [`RenderBundleEncoder`]'s /// command stream and gather metadata about it that will help /// keep [`ExecuteBundle`] simple and fast. We remove redundant /// commands (along with their side data), note resource usage, /// and accumulate buffer and texture initialization actions. /// /// [`ExecuteBundle`]: RenderCommand::ExecuteBundle pub(crate) fn finish( self, desc: &RenderBundleDescriptor, device: &Arc, hub: &Hub, ) -> Result, RenderBundleError> { let scope = PassErrorScope::Bundle; device.check_is_valid().map_pass_err(scope)?; let bind_group_guard = hub.bind_groups.read(); let pipeline_guard = hub.render_pipelines.read(); let buffer_guard = hub.buffers.read(); let mut state = State { trackers: RenderBundleScope::new(), pipeline: None, vertex: Default::default(), index: None, flat_dynamic_offsets: Vec::new(), device: device.clone(), commands: Vec::new(), buffer_memory_init_actions: Vec::new(), texture_memory_init_actions: Vec::new(), next_dynamic_offset: 0, binder: Binder::new(), }; let indices = &state.device.tracker_indices; state.trackers.buffers.set_size(indices.buffers.size()); state.trackers.textures.set_size(indices.textures.size()); let base = &self.base; for command in &base.commands { match command { &RenderCommand::SetBindGroup { index, num_dynamic_offsets, bind_group, } => { let scope = PassErrorScope::SetBindGroup; set_bind_group( &mut state, &bind_group_guard, &base.dynamic_offsets, index, num_dynamic_offsets, bind_group, ) .map_pass_err(scope)?; } &RenderCommand::SetPipeline(pipeline) => { let scope = PassErrorScope::SetPipelineRender; set_pipeline( &mut state, &pipeline_guard, &self.context, self.is_depth_read_only, self.is_stencil_read_only, pipeline, ) .map_pass_err(scope)?; } &RenderCommand::SetIndexBuffer { buffer, index_format, offset, size, } => { let scope = PassErrorScope::SetIndexBuffer; set_index_buffer( &mut state, &buffer_guard, buffer, index_format, offset, size, ) .map_pass_err(scope)?; } &RenderCommand::SetVertexBuffer { slot, buffer, offset, size, } => { let scope = PassErrorScope::SetVertexBuffer; set_vertex_buffer(&mut state, &buffer_guard, slot, buffer, offset, size) .map_pass_err(scope)?; } &RenderCommand::SetImmediate { offset, size_bytes, values_offset, } => { let scope = PassErrorScope::SetImmediate; set_immediates(&mut state, offset, size_bytes, values_offset) .map_pass_err(scope)?; } &RenderCommand::Draw { vertex_count, instance_count, first_vertex, first_instance, } => { let scope = PassErrorScope::Draw { kind: DrawKind::Draw, family: DrawCommandFamily::Draw, }; draw( &mut state, vertex_count, instance_count, first_vertex, first_instance, ) .map_pass_err(scope)?; } &RenderCommand::DrawIndexed { index_count, instance_count, first_index, base_vertex, first_instance, } => { let scope = PassErrorScope::Draw { kind: DrawKind::Draw, family: DrawCommandFamily::DrawIndexed, }; draw_indexed( &mut state, index_count, instance_count, first_index, base_vertex, first_instance, ) .map_pass_err(scope)?; } &RenderCommand::DrawMeshTasks { group_count_x, group_count_y, group_count_z, } => { let scope = PassErrorScope::Draw { kind: DrawKind::Draw, family: DrawCommandFamily::DrawMeshTasks, }; draw_mesh_tasks(&mut state, group_count_x, group_count_y, group_count_z) .map_pass_err(scope)?; } &RenderCommand::DrawIndirect { buffer, offset, count: 1, family, vertex_or_index_limit: None, instance_limit: None, } => { let scope = PassErrorScope::Draw { kind: DrawKind::DrawIndirect, family, }; multi_draw_indirect(&mut state, &buffer_guard, buffer, offset, family) .map_pass_err(scope)?; } &RenderCommand::DrawIndirect { count, vertex_or_index_limit, instance_limit, .. } => { unreachable!("unexpected (multi-)draw indirect with count {count}, vertex_or_index_limits {vertex_or_index_limit:?}, instance_limit {instance_limit:?} found in a render bundle"); } &RenderCommand::MultiDrawIndirectCount { .. } | &RenderCommand::PushDebugGroup { color: _, len: _ } | &RenderCommand::InsertDebugMarker { color: _, len: _ } | &RenderCommand::PopDebugGroup => { unimplemented!("not supported by a render bundle") } // Must check the TIMESTAMP_QUERY_INSIDE_PASSES feature &RenderCommand::WriteTimestamp { .. } | &RenderCommand::BeginOcclusionQuery { .. } | &RenderCommand::EndOcclusionQuery | &RenderCommand::BeginPipelineStatisticsQuery { .. } | &RenderCommand::EndPipelineStatisticsQuery => { unimplemented!("not supported by a render bundle") } &RenderCommand::ExecuteBundle(_) | &RenderCommand::SetBlendConstant(_) | &RenderCommand::SetStencilReference(_) | &RenderCommand::SetViewport { .. } | &RenderCommand::SetScissor(_) => unreachable!("not supported by a render bundle"), } } let State { trackers, flat_dynamic_offsets, device, commands, buffer_memory_init_actions, texture_memory_init_actions, .. } = state; let tracker_indices = device.tracker_indices.bundles.clone(); let discard_hal_labels = device .instance_flags .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS); let render_bundle = RenderBundle { base: BasePass { label: desc.label.as_deref().map(str::to_owned), error: None, commands, dynamic_offsets: flat_dynamic_offsets, string_data: self.base.string_data, immediates_data: self.base.immediates_data, }, is_depth_read_only: self.is_depth_read_only, is_stencil_read_only: self.is_stencil_read_only, device: device.clone(), used: trackers, buffer_memory_init_actions, texture_memory_init_actions, context: self.context, label: desc.label.to_string(), tracking_data: TrackingData::new(tracker_indices), discard_hal_labels, }; let render_bundle = Arc::new(render_bundle); Ok(render_bundle) } pub fn set_index_buffer( &mut self, buffer: id::BufferId, index_format: wgt::IndexFormat, offset: wgt::BufferAddress, size: Option, ) { self.base.commands.push(RenderCommand::SetIndexBuffer { buffer, index_format, offset, size, }); } } fn set_bind_group( state: &mut State, bind_group_guard: &crate::storage::Storage>, dynamic_offsets: &[u32], index: u32, num_dynamic_offsets: usize, bind_group_id: Option>, ) -> Result<(), RenderBundleErrorInner> { let max_bind_groups = state.device.limits.max_bind_groups; if index >= max_bind_groups { return Err( RenderCommandError::BindGroupIndexOutOfRange(pass::BindGroupIndexOutOfRange { index, max: max_bind_groups, }) .into(), ); } // Identify the next `num_dynamic_offsets` entries from `dynamic_offsets`. let offsets_range = state.next_dynamic_offset..state.next_dynamic_offset + num_dynamic_offsets; state.next_dynamic_offset = offsets_range.end; let offsets = &dynamic_offsets[offsets_range.clone()]; let bind_group = bind_group_id.map(|id| bind_group_guard.get(id)); if let Some(bind_group) = bind_group { let bind_group = bind_group.get()?; bind_group.same_device(&state.device)?; bind_group.validate_dynamic_bindings(index, offsets)?; unsafe { state.trackers.merge_bind_group(&bind_group.used)? }; let bind_group = state.trackers.bind_groups.insert_single(bind_group); state .binder .assign_group(index as usize, bind_group, offsets); } else { if !offsets.is_empty() { return Err(RenderBundleErrorInner::Bind( BindError::DynamicOffsetCountNotZero { group: index, actual: offsets.len(), }, )); } state.binder.clear_group(index as usize); } Ok(()) } fn set_pipeline( state: &mut State, pipeline_guard: &crate::storage::Storage>, context: &RenderPassContext, is_depth_read_only: bool, is_stencil_read_only: bool, pipeline_id: id::Id, ) -> Result<(), RenderBundleErrorInner> { let pipeline = pipeline_guard.get(pipeline_id).get()?; pipeline.same_device(&state.device)?; context .check_compatible(&pipeline.pass_context, pipeline.as_ref()) .map_err(RenderCommandError::IncompatiblePipelineTargets)?; if pipeline.flags.contains(PipelineFlags::WRITES_DEPTH) && is_depth_read_only { return Err(RenderCommandError::IncompatibleDepthAccess(pipeline.error_ident()).into()); } if pipeline.flags.contains(PipelineFlags::WRITES_STENCIL) && is_stencil_read_only { return Err(RenderCommandError::IncompatibleStencilAccess(pipeline.error_ident()).into()); } let pipeline_state = PipelineState::new(&pipeline); state .commands .push(ArcRenderCommand::SetPipeline(pipeline.clone())); // If this pipeline uses immediates, zero out their values. if let Some(cmd) = pipeline_state.zero_immediates() { state.commands.push(cmd); } state.pipeline = Some(pipeline_state); state .binder .change_pipeline_layout(&pipeline.layout, &pipeline.late_sized_buffer_groups); state.trackers.render_pipelines.insert_single(pipeline); Ok(()) } // This function is duplicative of `render::set_index_buffer`. fn set_index_buffer( state: &mut State, buffer_guard: &crate::storage::Storage>, buffer_id: id::Id, index_format: wgt::IndexFormat, offset: u64, size: Option, ) -> Result<(), RenderBundleErrorInner> { let buffer = buffer_guard.get(buffer_id).get()?; state .trackers .buffers .merge_single(&buffer, wgt::BufferUses::INDEX)?; buffer.same_device(&state.device)?; buffer.check_usage(wgt::BufferUsages::INDEX)?; if !offset.is_multiple_of(u64::try_from(index_format.byte_size()).unwrap()) { return Err(RenderCommandError::UnalignedIndexBuffer { offset, alignment: index_format.byte_size(), } .into()); } let end = offset + buffer.resolve_binding_size(offset, size)?; state .buffer_memory_init_actions .extend(buffer.initialization_status.read().create_action( &buffer, offset..end.get(), MemoryInitKind::NeedsInitializedMemory, )); state.set_index_buffer(buffer, index_format, offset..end.get()); Ok(()) } // This function is duplicative of `render::set_vertex_buffer`. fn set_vertex_buffer( state: &mut State, buffer_guard: &crate::storage::Storage>, slot: u32, buffer_id: id::Id, offset: u64, size: Option, ) -> Result<(), RenderBundleErrorInner> { let max_vertex_buffers = state.device.limits.max_vertex_buffers; if slot >= max_vertex_buffers { return Err(RenderCommandError::VertexBufferIndexOutOfRange { index: slot, max: max_vertex_buffers, } .into()); } let buffer = buffer_guard.get(buffer_id).get()?; state .trackers .buffers .merge_single(&buffer, wgt::BufferUses::VERTEX)?; buffer.same_device(&state.device)?; buffer.check_usage(wgt::BufferUsages::VERTEX)?; if !offset.is_multiple_of(wgt::VERTEX_ALIGNMENT) { return Err(RenderCommandError::UnalignedVertexBuffer { slot, offset }.into()); } let end = offset + buffer.resolve_binding_size(offset, size)?; state .buffer_memory_init_actions .extend(buffer.initialization_status.read().create_action( &buffer, offset..end.get(), MemoryInitKind::NeedsInitializedMemory, )); state.vertex[slot as usize] = Some(VertexState::new(buffer, offset..end.get())); Ok(()) } fn set_immediates( state: &mut State, offset: u32, size_bytes: u32, values_offset: Option, ) -> Result<(), RenderBundleErrorInner> { let pipeline_state = state.pipeline()?; pipeline_state .pipeline .layout .validate_immediates_ranges(offset, size_bytes)?; state.commands.push(ArcRenderCommand::SetImmediate { offset, size_bytes, values_offset, }); Ok(()) } fn draw( state: &mut State, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32, ) -> Result<(), RenderBundleErrorInner> { state.is_ready(DrawCommandFamily::Draw)?; let pipeline = state.pipeline()?; let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps); vertex_limits.validate_vertex_limit(first_vertex, vertex_count)?; vertex_limits.validate_instance_limit(first_instance, instance_count)?; if instance_count > 0 && vertex_count > 0 { state.flush_vertices(); state.flush_bindings(); state.commands.push(ArcRenderCommand::Draw { vertex_count, instance_count, first_vertex, first_instance, }); } Ok(()) } fn draw_indexed( state: &mut State, index_count: u32, instance_count: u32, first_index: u32, base_vertex: i32, first_instance: u32, ) -> Result<(), RenderBundleErrorInner> { state.is_ready(DrawCommandFamily::DrawIndexed)?; let pipeline = state.pipeline()?; let index = state.index.as_ref().unwrap(); let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps); let last_index = first_index as u64 + index_count as u64; let index_limit = index.limit(); if last_index > index_limit { return Err(DrawError::IndexBeyondLimit { last_index, index_limit, } .into()); } vertex_limits.validate_instance_limit(first_instance, instance_count)?; if instance_count > 0 && index_count > 0 { state.flush_index(); state.flush_vertices(); state.flush_bindings(); state.commands.push(ArcRenderCommand::DrawIndexed { index_count, instance_count, first_index, base_vertex, first_instance, }); } Ok(()) } fn draw_mesh_tasks( state: &mut State, group_count_x: u32, group_count_y: u32, group_count_z: u32, ) -> Result<(), RenderBundleErrorInner> { state.is_ready(DrawCommandFamily::DrawMeshTasks)?; let groups_size_limit = state.device.limits.max_task_mesh_workgroups_per_dimension; let max_groups = state.device.limits.max_task_mesh_workgroup_total_count; if group_count_x > groups_size_limit || group_count_y > groups_size_limit || group_count_z > groups_size_limit || group_count_x * group_count_y * group_count_z > max_groups { return Err(RenderBundleErrorInner::Draw(DrawError::InvalidGroupSize { current: [group_count_x, group_count_y, group_count_z], limit: groups_size_limit, max_total: max_groups, })); } if group_count_x > 0 && group_count_y > 0 && group_count_z > 0 { state.flush_bindings(); state.commands.push(ArcRenderCommand::DrawMeshTasks { group_count_x, group_count_y, group_count_z, }); } Ok(()) } fn multi_draw_indirect( state: &mut State, buffer_guard: &crate::storage::Storage>, buffer_id: id::Id, offset: u64, family: DrawCommandFamily, ) -> Result<(), RenderBundleErrorInner> { state.is_ready(family)?; state .device .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?; let pipeline = state.pipeline()?; let buffer = buffer_guard.get(buffer_id).get()?; buffer.same_device(&state.device)?; buffer.check_usage(wgt::BufferUsages::INDIRECT)?; let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps); let stride = super::get_stride_of_indirect_args(family); state .buffer_memory_init_actions .extend(buffer.initialization_status.read().create_action( &buffer, offset..(offset + stride), MemoryInitKind::NeedsInitializedMemory, )); let vertex_or_index_limit = if family == DrawCommandFamily::DrawIndexed { let index = state.index.as_mut().unwrap(); state.commands.extend(index.flush()); index.limit() } else { vertex_limits.vertex_limit }; let instance_limit = vertex_limits.instance_limit; let buffer_uses = if state.device.indirect_validation.is_some() { wgt::BufferUses::STORAGE_READ_ONLY } else { wgt::BufferUses::INDIRECT }; state.trackers.buffers.merge_single(&buffer, buffer_uses)?; state.flush_vertices(); state.flush_bindings(); state.commands.push(ArcRenderCommand::DrawIndirect { buffer, offset, count: 1, family, vertex_or_index_limit: Some(vertex_or_index_limit), instance_limit: Some(instance_limit), }); Ok(()) } /// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid. #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateRenderBundleError { #[error(transparent)] ColorAttachment(#[from] ColorAttachmentError), #[error("Invalid number of samples {0}")] InvalidSampleCount(u32), } impl WebGpuError for CreateRenderBundleError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::ColorAttachment(e) => e.webgpu_error_type(), Self::InvalidSampleCount(_) => ErrorType::Validation, } } } /// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid. #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum ExecutionError { #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] DestroyedResource(#[from] DestroyedResourceError), #[error("Using {0} in a render bundle is not implemented")] Unimplemented(&'static str), } pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor>; //Note: here, `RenderBundle` is just wrapping a raw stream of render commands. // The plan is to back it by an actual Vulkan secondary buffer, D3D12 Bundle, // or Metal indirect command buffer. /// cbindgen:ignore #[derive(Debug)] pub struct RenderBundle { // Normalized command stream. It can be executed verbatim, // without re-binding anything on the pipeline change. base: BasePass, pub(super) is_depth_read_only: bool, pub(super) is_stencil_read_only: bool, pub(crate) device: Arc, pub(crate) used: RenderBundleScope, pub(super) buffer_memory_init_actions: Vec, pub(super) texture_memory_init_actions: Vec, pub(super) context: RenderPassContext, /// The `label` from the descriptor used to create the resource. label: String, pub(crate) tracking_data: TrackingData, discard_hal_labels: bool, } impl Drop for RenderBundle { fn drop(&mut self) { resource_log!("Drop {}", self.error_ident()); } } #[cfg(send_sync)] unsafe impl Send for RenderBundle {} #[cfg(send_sync)] unsafe impl Sync for RenderBundle {} impl RenderBundle { #[cfg(feature = "trace")] pub(crate) fn to_base_pass(&self) -> BasePass, Infallible> { self.base.clone() } /// Actually encode the contents into a native command buffer. /// /// This is partially duplicating the logic of `render_pass_end`. /// However the point of this function is to be lighter, since we already had /// a chance to go through the commands in `render_bundle_encoder_finish`. /// /// Note that the function isn't expected to fail, generally. /// All the validation has already been done by this point. /// The only failure condition is if some of the used buffers are destroyed. pub(super) unsafe fn execute( &self, raw: &mut dyn hal::DynCommandEncoder, indirect_draw_validation_resources: &mut crate::indirect_validation::DrawResources, indirect_draw_validation_batcher: &mut crate::indirect_validation::DrawBatcher, snatch_guard: &SnatchGuard, ) -> Result<(), ExecutionError> { let mut offsets = self.base.dynamic_offsets.as_slice(); let mut pipeline_layout = None::>; if !self.discard_hal_labels { if let Some(ref label) = self.base.label { unsafe { raw.begin_debug_marker(label) }; } } use ArcRenderCommand as Cmd; for command in self.base.commands.iter() { match command { Cmd::SetBindGroup { index, num_dynamic_offsets, bind_group, } => { let raw_bg = bind_group.as_ref().unwrap().try_raw(snatch_guard)?; unsafe { raw.set_bind_group( pipeline_layout.as_ref().unwrap().raw(), *index, raw_bg, &offsets[..*num_dynamic_offsets], ) }; offsets = &offsets[*num_dynamic_offsets..]; } Cmd::SetPipeline(pipeline) => { unsafe { raw.set_render_pipeline(pipeline.raw()) }; pipeline_layout = Some(pipeline.layout.clone()); } Cmd::SetIndexBuffer { buffer, index_format, offset, size, } => { let buffer = buffer.try_raw(snatch_guard)?; // SAFETY: The binding size was checked against the buffer size // in `set_index_buffer` and again in `IndexState::flush`. let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size); unsafe { raw.set_index_buffer(bb, *index_format) }; } Cmd::SetVertexBuffer { slot, buffer, offset, size, } => { let buffer = buffer.try_raw(snatch_guard)?; // SAFETY: The binding size was checked against the buffer size // in `set_vertex_buffer` and again in `VertexState::flush`. let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size); unsafe { raw.set_vertex_buffer(*slot, bb) }; } Cmd::SetImmediate { offset, size_bytes, values_offset, } => { let pipeline_layout = pipeline_layout.as_ref().unwrap(); if let Some(values_offset) = *values_offset { let values_end_offset = (values_offset + size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize; let data_slice = &self.base.immediates_data[(values_offset as usize)..values_end_offset]; unsafe { raw.set_immediates(pipeline_layout.raw(), *offset, data_slice) } } else { super::immediates_clear( *offset, *size_bytes, |clear_offset, clear_data| { unsafe { raw.set_immediates( pipeline_layout.raw(), clear_offset, clear_data, ) }; }, ); } } Cmd::Draw { vertex_count, instance_count, first_vertex, first_instance, } => { unsafe { raw.draw( *first_vertex, *vertex_count, *first_instance, *instance_count, ) }; } Cmd::DrawIndexed { index_count, instance_count, first_index, base_vertex, first_instance, } => { unsafe { raw.draw_indexed( *first_index, *index_count, *base_vertex, *first_instance, *instance_count, ) }; } Cmd::DrawMeshTasks { group_count_x, group_count_y, group_count_z, } => unsafe { raw.draw_mesh_tasks(*group_count_x, *group_count_y, *group_count_z); }, Cmd::DrawIndirect { buffer, offset, count: 1, family, vertex_or_index_limit, instance_limit, } => { let (buffer, offset) = if self.device.indirect_validation.is_some() { let (dst_resource_index, offset) = indirect_draw_validation_batcher.add( indirect_draw_validation_resources, &self.device, buffer, *offset, *family, vertex_or_index_limit .expect("finalized render bundle missing vertex_or_index_limit"), instance_limit.expect("finalized render bundle missing instance_limit"), )?; let dst_buffer = indirect_draw_validation_resources.get_dst_buffer(dst_resource_index); (dst_buffer, offset) } else { (buffer.try_raw(snatch_guard)?, *offset) }; match family { DrawCommandFamily::Draw => unsafe { raw.draw_indirect(buffer, offset, 1) }, DrawCommandFamily::DrawIndexed => unsafe { raw.draw_indexed_indirect(buffer, offset, 1) }, DrawCommandFamily::DrawMeshTasks => unsafe { raw.draw_mesh_tasks_indirect(buffer, offset, 1); }, } } Cmd::DrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => { return Err(ExecutionError::Unimplemented("multi-draw-indirect")) } Cmd::PushDebugGroup { .. } | Cmd::InsertDebugMarker { .. } | Cmd::PopDebugGroup => { return Err(ExecutionError::Unimplemented("debug-markers")) } Cmd::WriteTimestamp { .. } | Cmd::BeginOcclusionQuery { .. } | Cmd::EndOcclusionQuery | Cmd::BeginPipelineStatisticsQuery { .. } | Cmd::EndPipelineStatisticsQuery => { return Err(ExecutionError::Unimplemented("queries")) } Cmd::ExecuteBundle(_) | Cmd::SetBlendConstant(_) | Cmd::SetStencilReference(_) | Cmd::SetViewport { .. } | Cmd::SetScissor(_) => unreachable!(), } } if !self.discard_hal_labels { if let Some(_) = self.base.label { unsafe { raw.end_debug_marker() }; } } Ok(()) } } crate::impl_resource_type!(RenderBundle); crate::impl_labeled!(RenderBundle); crate::impl_parent_device!(RenderBundle); crate::impl_storage_item!(RenderBundle); crate::impl_trackable!(RenderBundle); /// A render bundle's current index buffer state. /// /// [`RenderBundleEncoder::finish`] records the currently set index buffer here, /// and calls [`State::flush_index`] before any indexed draw command to produce /// a `SetIndexBuffer` command if one is necessary. /// /// Binding ranges must be validated against the size of the buffer before /// being stored in `IndexState`. #[derive(Debug)] struct IndexState { buffer: Arc, format: wgt::IndexFormat, range: Range, is_dirty: bool, } impl IndexState { /// Return the number of entries in the current index buffer. /// /// Panic if no index buffer has been set. fn limit(&self) -> u64 { let bytes_per_index = self.format.byte_size() as u64; (self.range.end - self.range.start) / bytes_per_index } /// Generate a `SetIndexBuffer` command to prepare for an indexed draw /// command, if needed. fn flush(&mut self) -> Option { // This was all checked before, but let's check again just in case. let binding_size = self .range .end .checked_sub(self.range.start) .filter(|_| self.range.end <= self.buffer.size) .expect("index range must be contained in buffer"); if self.is_dirty { self.is_dirty = false; Some(ArcRenderCommand::SetIndexBuffer { buffer: self.buffer.clone(), index_format: self.format, offset: self.range.start, size: NonZeroU64::new(binding_size), }) } else { None } } } /// The state of a single vertex buffer slot during render bundle encoding. /// /// [`RenderBundleEncoder::finish`] uses this to drop redundant /// `SetVertexBuffer` commands from the final [`RenderBundle`]. It /// records one vertex buffer slot's state changes here, and then /// calls this type's [`flush`] method just before any draw command to /// produce a `SetVertexBuffer` commands if one is necessary. /// /// Binding ranges must be validated against the size of the buffer before /// being stored in `VertexState`. /// /// [`flush`]: IndexState::flush #[derive(Debug)] struct VertexState { buffer: Arc, range: Range, is_dirty: bool, } impl VertexState { /// Create a new `VertexState`. /// /// The `range` must be contained within `buffer`. fn new(buffer: Arc, range: Range) -> Self { Self { buffer, range, is_dirty: true, } } /// Generate a `SetVertexBuffer` command for this slot, if necessary. /// /// `slot` is the index of the vertex buffer slot that `self` tracks. fn flush(&mut self, slot: u32) -> Option { let binding_size = self .range .end .checked_sub(self.range.start) .filter(|_| self.range.end <= self.buffer.size) .expect("vertex range must be contained in buffer"); if self.is_dirty { self.is_dirty = false; Some(ArcRenderCommand::SetVertexBuffer { slot, buffer: self.buffer.clone(), offset: self.range.start, size: NonZeroU64::new(binding_size), }) } else { None } } } /// The bundle's current pipeline, and some cached information needed for validation. struct PipelineState { /// The pipeline pipeline: Arc, /// How this pipeline's vertex shader traverses each vertex buffer, indexed /// by vertex buffer slot number. steps: Vec, /// Size of the immediate data ranges this pipeline uses. Copied from the pipeline layout. immediate_size: u32, } impl PipelineState { fn new(pipeline: &Arc) -> Self { Self { pipeline: pipeline.clone(), steps: pipeline.vertex_steps.to_vec(), immediate_size: pipeline.layout.immediate_size, } } /// Return a sequence of commands to zero the immediate data ranges this /// pipeline uses. If no initialization is necessary, return `None`. fn zero_immediates(&self) -> Option { if self.immediate_size == 0 { return None; } Some(ArcRenderCommand::SetImmediate { offset: 0, size_bytes: self.immediate_size, values_offset: None, }) } } /// State for analyzing and cleaning up bundle command streams. /// /// To minimize state updates, [`RenderBundleEncoder::finish`] /// actually just applies commands like [`SetBindGroup`] and /// [`SetIndexBuffer`] to the simulated state stored here, and then /// calls the `flush_foo` methods before draw calls to produce the /// update commands we actually need. /// /// [`SetBindGroup`]: RenderCommand::SetBindGroup /// [`SetIndexBuffer`]: RenderCommand::SetIndexBuffer struct State { /// Resources used by this bundle. This will become [`RenderBundle::used`]. trackers: RenderBundleScope, /// The currently set pipeline, if any. pipeline: Option, /// The state of each vertex buffer slot. vertex: [Option; hal::MAX_VERTEX_BUFFERS], /// The current index buffer, if one has been set. We flush this state /// before indexed draw commands. index: Option, /// Dynamic offset values used by the cleaned-up command sequence. /// /// This becomes the final [`RenderBundle`]'s [`BasePass`]'s /// [`dynamic_offsets`] list. /// /// [`dynamic_offsets`]: BasePass::dynamic_offsets flat_dynamic_offsets: Vec, device: Arc, commands: Vec, buffer_memory_init_actions: Vec, texture_memory_init_actions: Vec, next_dynamic_offset: usize, binder: Binder, } impl State { /// Return the current pipeline state. Return an error if none is set. fn pipeline(&self) -> Result<&PipelineState, RenderBundleErrorInner> { self.pipeline .as_ref() .ok_or(DrawError::MissingPipeline(pass::MissingPipeline).into()) } /// Set the bundle's current index buffer and its associated parameters. fn set_index_buffer( &mut self, buffer: Arc, format: wgt::IndexFormat, range: Range, ) { match self.index { Some(ref current) if current.buffer.is_equal(&buffer) && current.format == format && current.range == range => { return } _ => (), } self.index = Some(IndexState { buffer, format, range, is_dirty: true, }); } /// Generate a `SetIndexBuffer` command to prepare for an indexed draw /// command, if needed. fn flush_index(&mut self) { let commands = self.index.as_mut().and_then(|index| index.flush()); self.commands.extend(commands); } fn flush_vertices(&mut self) { let commands = self .vertex .iter_mut() .enumerate() .flat_map(|(i, vs)| vs.as_mut().and_then(|vs| vs.flush(i as u32))); self.commands.extend(commands); } /// Validation for a draw command. /// /// This should be further deduplicated with similar validation on render/compute passes. fn is_ready(&mut self, family: DrawCommandFamily) -> Result<(), DrawError> { if let Some(pipeline) = self.pipeline.as_ref() { self.binder .check_compatibility(pipeline.pipeline.as_ref())?; self.binder.check_late_buffer_bindings()?; if family == DrawCommandFamily::DrawIndexed { let pipeline = &pipeline.pipeline; let index_format = match &self.index { Some(index) => index.format, None => return Err(DrawError::MissingIndexBuffer), }; if pipeline.topology.is_strip() && pipeline.strip_index_format != Some(index_format) { return Err(DrawError::UnmatchedStripIndexFormat { pipeline: pipeline.error_ident(), strip_index_format: pipeline.strip_index_format, buffer_format: index_format, }); } } Ok(()) } else { Err(DrawError::MissingPipeline(pass::MissingPipeline)) } } /// Generate `SetBindGroup` commands for any bind groups that need to be updated. /// /// This should be further deduplicated with similar code on render/compute passes. fn flush_bindings(&mut self) { let start = self.binder.take_rebind_start_index(); let entries = self.binder.list_valid_with_start(start); self.commands .extend(entries.map(|(i, bind_group, dynamic_offsets)| { self.buffer_memory_init_actions .extend_from_slice(&bind_group.used_buffer_ranges); self.texture_memory_init_actions .extend_from_slice(&bind_group.used_texture_ranges); self.flat_dynamic_offsets.extend_from_slice(dynamic_offsets); ArcRenderCommand::SetBindGroup { index: i.try_into().unwrap(), bind_group: Some(bind_group.clone()), num_dynamic_offsets: dynamic_offsets.len(), } })); } fn vertex_buffer_sizes(&self) -> impl Iterator> + '_ { self.vertex .iter() .map(|vbs| vbs.as_ref().map(|vbs| vbs.range.end - vbs.range.start)) } } /// Error encountered when finishing recording a render bundle. #[derive(Clone, Debug, Error)] pub enum RenderBundleErrorInner { #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] RenderCommand(RenderCommandError), #[error(transparent)] Draw(#[from] DrawError), #[error(transparent)] MissingDownlevelFlags(#[from] MissingDownlevelFlags), #[error(transparent)] Bind(#[from] BindError), #[error(transparent)] InvalidResource(#[from] InvalidResourceError), } impl From for RenderBundleErrorInner where T: Into, { fn from(t: T) -> Self { Self::RenderCommand(t.into()) } } /// Error encountered when finishing recording a render bundle. #[derive(Clone, Debug, Error)] #[error("{scope}")] pub struct RenderBundleError { pub scope: PassErrorScope, #[source] inner: RenderBundleErrorInner, } impl WebGpuError for RenderBundleError { fn webgpu_error_type(&self) -> ErrorType { let Self { scope: _, inner } = self; match inner { RenderBundleErrorInner::Device(e) => e.webgpu_error_type(), RenderBundleErrorInner::RenderCommand(e) => e.webgpu_error_type(), RenderBundleErrorInner::Draw(e) => e.webgpu_error_type(), RenderBundleErrorInner::MissingDownlevelFlags(e) => e.webgpu_error_type(), RenderBundleErrorInner::Bind(e) => e.webgpu_error_type(), RenderBundleErrorInner::InvalidResource(e) => e.webgpu_error_type(), } } } impl RenderBundleError { pub fn from_device_error(e: DeviceError) -> Self { Self { scope: PassErrorScope::Bundle, inner: e.into(), } } } impl MapPassErr for E where E: Into, { fn map_pass_err(self, scope: PassErrorScope) -> RenderBundleError { RenderBundleError { scope, inner: self.into(), } } } pub mod bundle_ffi { use super::{RenderBundleEncoder, RenderCommand}; use crate::{command::DrawCommandFamily, id, RawString}; use core::{convert::TryInto, slice}; use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat}; /// # Safety /// /// This function is unsafe as there is no guarantee that the given pointer is /// valid for `offset_length` elements. pub unsafe fn wgpu_render_bundle_set_bind_group( bundle: &mut RenderBundleEncoder, index: u32, bind_group_id: Option, offsets: *const DynamicOffset, offset_length: usize, ) { let offsets = unsafe { slice::from_raw_parts(offsets, offset_length) }; let redundant = bundle.current_bind_groups.set_and_check_redundant( bind_group_id, index, &mut bundle.base.dynamic_offsets, offsets, ); if redundant { return; } bundle.base.commands.push(RenderCommand::SetBindGroup { index, num_dynamic_offsets: offset_length, bind_group: bind_group_id, }); } pub fn wgpu_render_bundle_set_pipeline( bundle: &mut RenderBundleEncoder, pipeline_id: id::RenderPipelineId, ) { if bundle.current_pipeline.set_and_check_redundant(pipeline_id) { return; } bundle .base .commands .push(RenderCommand::SetPipeline(pipeline_id)); } pub fn wgpu_render_bundle_set_vertex_buffer( bundle: &mut RenderBundleEncoder, slot: u32, buffer_id: id::BufferId, offset: BufferAddress, size: Option, ) { bundle.base.commands.push(RenderCommand::SetVertexBuffer { slot, buffer: buffer_id, offset, size, }); } pub fn wgpu_render_bundle_set_index_buffer( encoder: &mut RenderBundleEncoder, buffer: id::BufferId, index_format: IndexFormat, offset: BufferAddress, size: Option, ) { encoder.set_index_buffer(buffer, index_format, offset, size); } /// # Safety /// /// This function is unsafe as there is no guarantee that the given pointer is /// valid for `data` elements. pub unsafe fn wgpu_render_bundle_set_immediates( pass: &mut RenderBundleEncoder, offset: u32, size_bytes: u32, data: *const u8, ) { assert_eq!( offset & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1), 0, "Immediate data offset must be aligned to 4 bytes." ); assert_eq!( size_bytes & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1), 0, "Immediate data size must be aligned to 4 bytes." ); let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) }; let value_offset = pass.base.immediates_data.len().try_into().expect( "Ran out of immediate data space. Don't set 4gb of immediates per RenderBundle.", ); pass.base.immediates_data.extend( data_slice .chunks_exact(wgt::IMMEDIATE_DATA_ALIGNMENT as usize) .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])), ); pass.base.commands.push(RenderCommand::SetImmediate { offset, size_bytes, values_offset: Some(value_offset), }); } pub fn wgpu_render_bundle_draw( bundle: &mut RenderBundleEncoder, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32, ) { bundle.base.commands.push(RenderCommand::Draw { vertex_count, instance_count, first_vertex, first_instance, }); } pub fn wgpu_render_bundle_draw_indexed( bundle: &mut RenderBundleEncoder, index_count: u32, instance_count: u32, first_index: u32, base_vertex: i32, first_instance: u32, ) { bundle.base.commands.push(RenderCommand::DrawIndexed { index_count, instance_count, first_index, base_vertex, first_instance, }); } pub fn wgpu_render_bundle_draw_indirect( bundle: &mut RenderBundleEncoder, buffer_id: id::BufferId, offset: BufferAddress, ) { bundle.base.commands.push(RenderCommand::DrawIndirect { buffer: buffer_id, offset, count: 1, family: DrawCommandFamily::Draw, vertex_or_index_limit: None, instance_limit: None, }); } pub fn wgpu_render_bundle_draw_indexed_indirect( bundle: &mut RenderBundleEncoder, buffer_id: id::BufferId, offset: BufferAddress, ) { bundle.base.commands.push(RenderCommand::DrawIndirect { buffer: buffer_id, offset, count: 1, family: DrawCommandFamily::DrawIndexed, vertex_or_index_limit: None, instance_limit: None, }); } /// # Safety /// /// This function is unsafe as there is no guarantee that the given `label` /// is a valid null-terminated string. pub unsafe fn wgpu_render_bundle_push_debug_group( _bundle: &mut RenderBundleEncoder, _label: RawString, ) { //TODO } pub fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) { //TODO } /// # Safety /// /// This function is unsafe as there is no guarantee that the given `label` /// is a valid null-terminated string. pub unsafe fn wgpu_render_bundle_insert_debug_marker( _bundle: &mut RenderBundleEncoder, _label: RawString, ) { //TODO } } ================================================ FILE: wgpu-core/src/command/clear.rs ================================================ use alloc::{sync::Arc, vec::Vec}; use core::ops::Range; use crate::{ api_log, command::{encoder::EncodingState, ArcCommand, EncoderStateError}, device::{DeviceError, MissingFeatures}, get_lowest_common_denom, global::Global, hal_label, id::{BufferId, CommandEncoderId, TextureId}, init_tracker::{MemoryInitKind, TextureInitRange}, resource::{ Buffer, DestroyedResourceError, InvalidResourceError, Labeled, MissingBufferUsageError, ParentDevice, RawResourceAccess, ResourceErrorIdent, Texture, TextureClearMode, }, snatch::SnatchGuard, track::TextureTrackerSetSingle, }; use thiserror::Error; use wgt::{ error::{ErrorType, WebGpuError}, math::align_to, BufferAddress, BufferUsages, ImageSubresourceRange, TextureAspect, TextureSelector, }; /// Error encountered while attempting a clear. #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum ClearError { #[error(transparent)] DestroyedResource(#[from] DestroyedResourceError), #[error(transparent)] MissingFeatures(#[from] MissingFeatures), #[error("{0} can not be cleared")] NoValidTextureClearMode(ResourceErrorIdent), #[error("Buffer clear size {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")] UnalignedFillSize(BufferAddress), #[error("Buffer offset {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")] UnalignedBufferOffset(BufferAddress), #[error("Clear starts at offset {start_offset} with size of {requested_size}, but these added together exceed `u64::MAX`")] OffsetPlusSizeExceeds64BitBounds { start_offset: BufferAddress, requested_size: BufferAddress, }, #[error("Clear of {start_offset}..{end_offset} would end up overrunning the bounds of the buffer of size {buffer_size}")] BufferOverrun { start_offset: BufferAddress, end_offset: BufferAddress, buffer_size: BufferAddress, }, #[error(transparent)] MissingBufferUsage(#[from] MissingBufferUsageError), #[error("Texture lacks the aspects that were specified in the image subresource range. Texture with format {texture_format:?}, specified was {subresource_range_aspects:?}")] MissingTextureAspect { texture_format: wgt::TextureFormat, subresource_range_aspects: TextureAspect, }, #[error("Image subresource level range is outside of the texture's level range. texture range is {texture_level_range:?}, \ whereas subesource range specified start {subresource_base_mip_level} and count {subresource_mip_level_count:?}")] InvalidTextureLevelRange { texture_level_range: Range, subresource_base_mip_level: u32, subresource_mip_level_count: Option, }, #[error("Image subresource layer range is outside of the texture's layer range. texture range is {texture_layer_range:?}, \ whereas subesource range specified start {subresource_base_array_layer} and count {subresource_array_layer_count:?}")] InvalidTextureLayerRange { texture_layer_range: Range, subresource_base_array_layer: u32, subresource_array_layer_count: Option, }, #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] EncoderState(#[from] EncoderStateError), #[error(transparent)] InvalidResource(#[from] InvalidResourceError), } impl WebGpuError for ClearError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::DestroyedResource(e) => e.webgpu_error_type(), Self::MissingFeatures(e) => e.webgpu_error_type(), Self::MissingBufferUsage(e) => e.webgpu_error_type(), Self::Device(e) => e.webgpu_error_type(), Self::EncoderState(e) => e.webgpu_error_type(), Self::InvalidResource(e) => e.webgpu_error_type(), Self::NoValidTextureClearMode(..) | Self::UnalignedFillSize(..) | Self::UnalignedBufferOffset(..) | Self::OffsetPlusSizeExceeds64BitBounds { .. } | Self::BufferOverrun { .. } | Self::MissingTextureAspect { .. } | Self::InvalidTextureLevelRange { .. } | Self::InvalidTextureLayerRange { .. } => ErrorType::Validation, } } } impl Global { pub fn command_encoder_clear_buffer( &self, command_encoder_id: CommandEncoderId, dst: BufferId, offset: BufferAddress, size: Option, ) -> Result<(), EncoderStateError> { profiling::scope!("CommandEncoder::clear_buffer"); api_log!("CommandEncoder::clear_buffer {dst:?}"); let hub = &self.hub; let cmd_enc = hub.command_encoders.get(command_encoder_id); let mut cmd_buf_data = cmd_enc.data.lock(); cmd_buf_data.push_with(|| -> Result<_, ClearError> { Ok(ArcCommand::ClearBuffer { dst: self.resolve_buffer_id(dst)?, offset, size, }) }) } pub fn command_encoder_clear_texture( &self, command_encoder_id: CommandEncoderId, dst: TextureId, subresource_range: &ImageSubresourceRange, ) -> Result<(), EncoderStateError> { profiling::scope!("CommandEncoder::clear_texture"); api_log!("CommandEncoder::clear_texture {dst:?}"); let hub = &self.hub; let cmd_enc = hub.command_encoders.get(command_encoder_id); let mut cmd_buf_data = cmd_enc.data.lock(); cmd_buf_data.push_with(|| -> Result<_, ClearError> { Ok(ArcCommand::ClearTexture { dst: self.resolve_texture_id(dst)?, subresource_range: *subresource_range, }) }) } } pub(super) fn clear_buffer( state: &mut EncodingState, dst_buffer: Arc, offset: BufferAddress, size: Option, ) -> Result<(), ClearError> { dst_buffer.same_device(state.device)?; let dst_pending = state .tracker .buffers .set_single(&dst_buffer, wgt::BufferUses::COPY_DST); let dst_raw = dst_buffer.try_raw(state.snatch_guard)?; dst_buffer.check_usage(BufferUsages::COPY_DST)?; // Check if offset & size are valid. if !offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) { return Err(ClearError::UnalignedBufferOffset(offset)); } let size = size.unwrap_or(dst_buffer.size.saturating_sub(offset)); if !size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) { return Err(ClearError::UnalignedFillSize(size)); } let end_offset = offset .checked_add(size) .ok_or(ClearError::OffsetPlusSizeExceeds64BitBounds { start_offset: offset, requested_size: size, })?; if end_offset > dst_buffer.size { return Err(ClearError::BufferOverrun { start_offset: offset, end_offset, buffer_size: dst_buffer.size, }); } // This must happen after parameter validation (so that errors are reported // as required by the spec), but before any side effects. if offset == end_offset { log::trace!("Ignoring fill_buffer of size 0"); return Ok(()); } // Mark dest as initialized. state .buffer_memory_init_actions .extend(dst_buffer.initialization_status.read().create_action( &dst_buffer, offset..end_offset, MemoryInitKind::ImplicitlyInitialized, )); // actual hal barrier & operation let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, state.snatch_guard)); unsafe { state.raw_encoder.transition_buffers(dst_barrier.as_slice()); state.raw_encoder.clear_buffer(dst_raw, offset..end_offset); } Ok(()) } /// Validate and encode a "Clear Texture" command. /// /// This function implements `CommandEncoder::clear_texture` when invoked via /// the command encoder APIs or trace playback. It has the suffix `_cmd` to /// distinguish it from [`clear_texture`]. [`clear_texture`], used internally by /// this function, is a lower-level function that encodes a texture clear /// operation without validating it. pub(super) fn clear_texture_cmd( state: &mut EncodingState, dst_texture: Arc, subresource_range: &ImageSubresourceRange, ) -> Result<(), ClearError> { dst_texture.same_device(state.device)?; state .device .require_features(wgt::Features::CLEAR_TEXTURE)?; // Check if subresource aspects are valid. let clear_aspects = hal::FormatAspects::new(dst_texture.desc.format, subresource_range.aspect); if clear_aspects.is_empty() { return Err(ClearError::MissingTextureAspect { texture_format: dst_texture.desc.format, subresource_range_aspects: subresource_range.aspect, }); }; // Check if subresource level range is valid let subresource_mip_range = subresource_range.mip_range(dst_texture.full_range.mips.end); if dst_texture.full_range.mips.start > subresource_mip_range.start || dst_texture.full_range.mips.end < subresource_mip_range.end { return Err(ClearError::InvalidTextureLevelRange { texture_level_range: dst_texture.full_range.mips.clone(), subresource_base_mip_level: subresource_range.base_mip_level, subresource_mip_level_count: subresource_range.mip_level_count, }); } // Check if subresource layer range is valid let subresource_layer_range = subresource_range.layer_range(dst_texture.full_range.layers.end); if dst_texture.full_range.layers.start > subresource_layer_range.start || dst_texture.full_range.layers.end < subresource_layer_range.end { return Err(ClearError::InvalidTextureLayerRange { texture_layer_range: dst_texture.full_range.layers.clone(), subresource_base_array_layer: subresource_range.base_array_layer, subresource_array_layer_count: subresource_range.array_layer_count, }); } clear_texture( &dst_texture, TextureInitRange { mip_range: subresource_mip_range, layer_range: subresource_layer_range, }, state.raw_encoder, &mut state.tracker.textures, &state.device.alignments, state.device.zero_buffer.as_ref(), state.snatch_guard, state.device.instance_flags, )?; Ok(()) } /// Encode a texture clear operation. /// /// This function encodes a texture clear operation without validating it. /// Texture clears requested via the API call this function via /// [`clear_texture_cmd`], which does the validation. This function is also /// called directly from various places within wgpu that need to clear a /// texture. pub(crate) fn clear_texture( dst_texture: &Arc, range: TextureInitRange, encoder: &mut dyn hal::DynCommandEncoder, texture_tracker: &mut T, alignments: &hal::Alignments, zero_buffer: &dyn hal::DynBuffer, snatch_guard: &SnatchGuard<'_>, instance_flags: wgt::InstanceFlags, ) -> Result<(), ClearError> { let dst_raw = dst_texture.try_raw(snatch_guard)?; // Issue the right barrier. let clear_usage = match *dst_texture.clear_mode.read() { TextureClearMode::BufferCopy => wgt::TextureUses::COPY_DST, TextureClearMode::RenderPass { is_color: false, .. } => wgt::TextureUses::DEPTH_STENCIL_WRITE, TextureClearMode::Surface { .. } | TextureClearMode::RenderPass { is_color: true, .. } => { wgt::TextureUses::COLOR_TARGET } TextureClearMode::None => { return Err(ClearError::NoValidTextureClearMode( dst_texture.error_ident(), )); } }; let selector = TextureSelector { mips: range.mip_range.clone(), layers: range.layer_range.clone(), }; // If we're in a texture-init usecase, we know that the texture is already // tracked since whatever caused the init requirement, will have caused the // usage tracker to be aware of the texture. Meaning, that it is safe to // call call change_replace_tracked if the life_guard is already gone (i.e. // the user no longer holds on to this texture). // // On the other hand, when coming via command_encoder_clear_texture, the // life_guard is still there since in order to call it a texture object is // needed. // // We could in theory distinguish these two scenarios in the internal // clear_texture api in order to remove this check and call the cheaper // change_replace_tracked whenever possible. let dst_barrier = texture_tracker .set_single(dst_texture, selector, clear_usage) .map(|pending| pending.into_hal(dst_raw)) .collect::>(); unsafe { encoder.transition_textures(&dst_barrier); } // Record actual clearing let clear_mode = dst_texture.clear_mode.read(); match *clear_mode { TextureClearMode::BufferCopy => clear_texture_via_buffer_copies( &dst_texture.desc, alignments, zero_buffer, range, encoder, dst_raw, ), TextureClearMode::Surface { .. } => { drop(clear_mode); clear_texture_via_render_passes(dst_texture, range, true, encoder, instance_flags)? } TextureClearMode::RenderPass { is_color, .. } => { drop(clear_mode); clear_texture_via_render_passes(dst_texture, range, is_color, encoder, instance_flags)? } TextureClearMode::None => { return Err(ClearError::NoValidTextureClearMode( dst_texture.error_ident(), )); } } Ok(()) } fn clear_texture_via_buffer_copies( texture_desc: &wgt::TextureDescriptor<(), Vec>, alignments: &hal::Alignments, zero_buffer: &dyn hal::DynBuffer, // Buffer of size device::ZERO_BUFFER_SIZE range: TextureInitRange, encoder: &mut dyn hal::DynCommandEncoder, dst_raw: &dyn hal::DynTexture, ) { assert!(!texture_desc.format.is_depth_stencil_format()); if texture_desc.format == wgt::TextureFormat::NV12 || texture_desc.format == wgt::TextureFormat::P010 { // TODO: Currently COPY_DST for NV12 and P010 textures is unsupported. return; } // Gather list of zero_buffer copies and issue a single command then to perform them let mut zero_buffer_copy_regions = Vec::new(); let buffer_copy_pitch = alignments.buffer_copy_pitch.get() as u32; let (block_width, block_height) = texture_desc.format.block_dimensions(); let block_size = texture_desc.format.block_copy_size(None).unwrap(); let bytes_per_row_alignment = get_lowest_common_denom(buffer_copy_pitch, block_size); for mip_level in range.mip_range { let mut mip_size = texture_desc.mip_level_size(mip_level).unwrap(); // Round to multiple of block size mip_size.width = align_to(mip_size.width, block_width); mip_size.height = align_to(mip_size.height, block_height); let bytes_per_row = align_to( mip_size.width / block_width * block_size, bytes_per_row_alignment, ); let max_rows_per_copy = crate::device::ZERO_BUFFER_SIZE as u32 / bytes_per_row; // round down to a multiple of rows needed by the texture format let max_rows_per_copy = max_rows_per_copy / block_height * block_height; assert!( max_rows_per_copy > 0, "Zero buffer size is too small to fill a single row \ of a texture with format {:?} and desc {:?}", texture_desc.format, texture_desc.size ); let z_range = 0..(if texture_desc.dimension == wgt::TextureDimension::D3 { mip_size.depth_or_array_layers } else { 1 }); for array_layer in range.layer_range.clone() { // TODO: Only doing one layer at a time for volume textures right now. for z in z_range.clone() { // May need multiple copies for each subresource! However, we // assume that we never need to split a row. let mut num_rows_left = mip_size.height; while num_rows_left > 0 { let num_rows = num_rows_left.min(max_rows_per_copy); zero_buffer_copy_regions.push(hal::BufferTextureCopy { buffer_layout: wgt::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(bytes_per_row), rows_per_image: None, }, texture_base: hal::TextureCopyBase { mip_level, array_layer, origin: wgt::Origin3d { x: 0, // Always full rows y: mip_size.height - num_rows_left, z, }, aspect: hal::FormatAspects::COLOR, }, size: hal::CopyExtent { width: mip_size.width, // full row height: num_rows, depth: 1, // Only single slice of volume texture at a time right now }, }); num_rows_left -= num_rows; } } } } unsafe { encoder.copy_buffer_to_texture(zero_buffer, dst_raw, &zero_buffer_copy_regions); } } fn clear_texture_via_render_passes( dst_texture: &Texture, range: TextureInitRange, is_color: bool, encoder: &mut dyn hal::DynCommandEncoder, instance_flags: wgt::InstanceFlags, ) -> Result<(), ClearError> { assert_eq!(dst_texture.desc.dimension, wgt::TextureDimension::D2); let extent_base = wgt::Extent3d { width: dst_texture.desc.size.width, height: dst_texture.desc.size.height, depth_or_array_layers: 1, // Only one layer is cleared at a time. }; let clear_mode = dst_texture.clear_mode.read(); for mip_level in range.mip_range { let extent = extent_base.mip_level_size(mip_level, dst_texture.desc.dimension); for depth_or_layer in range.layer_range.clone() { let color_attachments_tmp; let (color_attachments, depth_stencil_attachment) = if is_color { color_attachments_tmp = [Some(hal::ColorAttachment { target: hal::Attachment { view: Texture::get_clear_view( &clear_mode, &dst_texture.desc, mip_level, depth_or_layer, ), usage: wgt::TextureUses::COLOR_TARGET, }, depth_slice: None, resolve_target: None, ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR, clear_value: wgt::Color::TRANSPARENT, })]; (&color_attachments_tmp[..], None) } else { ( &[][..], Some(hal::DepthStencilAttachment { target: hal::Attachment { view: Texture::get_clear_view( &clear_mode, &dst_texture.desc, mip_level, depth_or_layer, ), usage: wgt::TextureUses::DEPTH_STENCIL_WRITE, }, depth_ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR, stencil_ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR, clear_value: (0.0, 0), }), ) }; unsafe { encoder .begin_render_pass(&hal::RenderPassDescriptor { label: hal_label( Some("(wgpu internal) clear_texture clear pass"), instance_flags, ), extent, sample_count: dst_texture.desc.sample_count, color_attachments, depth_stencil_attachment, multiview_mask: None, timestamp_writes: None, occlusion_query_set: None, }) .map_err(|e| dst_texture.device.handle_hal_error(e))?; encoder.end_render_pass(); } } } Ok(()) } ================================================ FILE: wgpu-core/src/command/compute.rs ================================================ use thiserror::Error; use wgt::{ error::{ErrorType, WebGpuError}, BufferAddress, DynamicOffset, }; use alloc::{borrow::Cow, boxed::Box, sync::Arc, vec::Vec}; use core::{convert::Infallible, fmt, str}; use crate::{ api_log, binding_model::{BindError, ImmediateUploadError, LateMinBufferBindingSizeMismatch}, command::{ bind::{Binder, BinderError}, compute_command::ArcComputeCommand, encoder::EncodingState, memory_init::{fixup_discarded_surfaces, SurfacesInDiscardState}, pass::{self, flush_bindings_helper}, pass_base, pass_try, query::{end_pipeline_statistics_query, validate_and_begin_pipeline_statistics_query}, ArcCommand, ArcPassTimestampWrites, BasePass, BindGroupStateChange, CommandEncoder, CommandEncoderError, DebugGroupError, EncoderStateError, InnerCommandEncoder, MapPassErr, PassErrorScope, PassStateError, PassTimestampWrites, QueryUseError, StateChange, TimestampWritesError, }, device::{Device, DeviceError, MissingDownlevelFlags, MissingFeatures}, global::Global, hal_label, id, init_tracker::MemoryInitKind, pipeline::ComputePipeline, resource::{ self, Buffer, DestroyedResourceError, InvalidResourceError, Labeled, MissingBufferUsageError, ParentDevice, RawResourceAccess, Trackable, }, track::{ResourceUsageCompatibilityError, Tracker}, Label, }; pub type ComputeBasePass = BasePass; /// A pass's [encoder state](https://www.w3.org/TR/webgpu/#encoder-state) and /// its validity are two distinct conditions, i.e., the full matrix of /// (open, ended) x (valid, invalid) is possible. /// /// The presence or absence of the `parent` `Option` indicates the pass's state. /// The presence or absence of an error in `base.error` indicates the pass's /// validity. pub struct ComputePass { /// All pass data & records is stored here. base: ComputeBasePass, /// Parent command encoder that this pass records commands into. /// /// If this is `Some`, then the pass is in WebGPU's "open" state. If it is /// `None`, then the pass is in the "ended" state. /// See parent: Option>, timestamp_writes: Option, // Resource binding dedupe state. current_bind_groups: BindGroupStateChange, current_pipeline: StateChange, } impl ComputePass { /// If the parent command encoder is invalid, the returned pass will be invalid. fn new(parent: Arc, desc: ArcComputePassDescriptor) -> Self { let ArcComputePassDescriptor { label, timestamp_writes, } = desc; Self { base: BasePass::new(&label), parent: Some(parent), timestamp_writes, current_bind_groups: BindGroupStateChange::new(), current_pipeline: StateChange::new(), } } fn new_invalid(parent: Arc, label: &Label, err: ComputePassError) -> Self { Self { base: BasePass::new_invalid(label, err), parent: Some(parent), timestamp_writes: None, current_bind_groups: BindGroupStateChange::new(), current_pipeline: StateChange::new(), } } #[inline] pub fn label(&self) -> Option<&str> { self.base.label.as_deref() } } impl fmt::Debug for ComputePass { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.parent { Some(ref cmd_enc) => write!(f, "ComputePass {{ parent: {} }}", cmd_enc.error_ident()), None => write!(f, "ComputePass {{ parent: None }}"), } } } #[derive(Clone, Debug, Default)] pub struct ComputePassDescriptor<'a, PTW = PassTimestampWrites> { pub label: Label<'a>, /// Defines where and when timestamp values will be written for this pass. pub timestamp_writes: Option, } /// cbindgen:ignore type ArcComputePassDescriptor<'a> = ComputePassDescriptor<'a, ArcPassTimestampWrites>; #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum DispatchError { #[error("Compute pipeline must be set")] MissingPipeline(pass::MissingPipeline), #[error(transparent)] IncompatibleBindGroup(#[from] Box), #[error( "Each current dispatch group size dimension ({current:?}) must be less or equal to {limit}" )] InvalidGroupSize { current: [u32; 3], limit: u32 }, #[error(transparent)] BindingSizeTooSmall(#[from] LateMinBufferBindingSizeMismatch), } impl WebGpuError for DispatchError { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } } /// Error encountered when performing a compute pass. #[derive(Clone, Debug, Error)] pub enum ComputePassErrorInner { #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] EncoderState(#[from] EncoderStateError), #[error("Parent encoder is invalid")] InvalidParentEncoder, #[error(transparent)] DebugGroupError(#[from] DebugGroupError), #[error(transparent)] BindGroupIndexOutOfRange(#[from] pass::BindGroupIndexOutOfRange), #[error(transparent)] DestroyedResource(#[from] DestroyedResourceError), #[error("Indirect buffer offset {0:?} is not a multiple of 4")] UnalignedIndirectBufferOffset(BufferAddress), #[error("Indirect buffer uses bytes {offset}..{end_offset} which overruns indirect buffer of size {buffer_size}")] IndirectBufferOverrun { offset: u64, end_offset: u64, buffer_size: u64, }, #[error(transparent)] ResourceUsageCompatibility(#[from] ResourceUsageCompatibilityError), #[error(transparent)] MissingBufferUsage(#[from] MissingBufferUsageError), #[error(transparent)] Dispatch(#[from] DispatchError), #[error(transparent)] Bind(#[from] BindError), #[error(transparent)] ImmediateData(#[from] ImmediateUploadError), #[error("Immediate data offset must be aligned to 4 bytes")] ImmediateOffsetAlignment, #[error("Immediate data size must be aligned to 4 bytes")] ImmediateDataizeAlignment, #[error("Ran out of immediate data space. Don't set 4gb of immediates per ComputePass.")] ImmediateOutOfMemory, #[error(transparent)] QueryUse(#[from] QueryUseError), #[error(transparent)] MissingFeatures(#[from] MissingFeatures), #[error(transparent)] MissingDownlevelFlags(#[from] MissingDownlevelFlags), #[error("The compute pass has already been ended and no further commands can be recorded")] PassEnded, #[error(transparent)] InvalidResource(#[from] InvalidResourceError), #[error(transparent)] TimestampWrites(#[from] TimestampWritesError), // This one is unreachable, but required for generic pass support #[error(transparent)] InvalidValuesOffset(#[from] pass::InvalidValuesOffset), } /// Error encountered when performing a compute pass, stored for later reporting /// when encoding ends. #[derive(Clone, Debug, Error)] #[error("{scope}")] pub struct ComputePassError { pub scope: PassErrorScope, #[source] pub(super) inner: ComputePassErrorInner, } impl From for ComputePassErrorInner { fn from(value: pass::MissingPipeline) -> Self { Self::Dispatch(DispatchError::MissingPipeline(value)) } } impl MapPassErr for E where E: Into, { fn map_pass_err(self, scope: PassErrorScope) -> ComputePassError { ComputePassError { scope, inner: self.into(), } } } impl WebGpuError for ComputePassError { fn webgpu_error_type(&self) -> ErrorType { let Self { scope: _, inner } = self; match inner { ComputePassErrorInner::Device(e) => e.webgpu_error_type(), ComputePassErrorInner::EncoderState(e) => e.webgpu_error_type(), ComputePassErrorInner::DebugGroupError(e) => e.webgpu_error_type(), ComputePassErrorInner::DestroyedResource(e) => e.webgpu_error_type(), ComputePassErrorInner::ResourceUsageCompatibility(e) => e.webgpu_error_type(), ComputePassErrorInner::MissingBufferUsage(e) => e.webgpu_error_type(), ComputePassErrorInner::Dispatch(e) => e.webgpu_error_type(), ComputePassErrorInner::Bind(e) => e.webgpu_error_type(), ComputePassErrorInner::ImmediateData(e) => e.webgpu_error_type(), ComputePassErrorInner::QueryUse(e) => e.webgpu_error_type(), ComputePassErrorInner::MissingFeatures(e) => e.webgpu_error_type(), ComputePassErrorInner::MissingDownlevelFlags(e) => e.webgpu_error_type(), ComputePassErrorInner::InvalidResource(e) => e.webgpu_error_type(), ComputePassErrorInner::TimestampWrites(e) => e.webgpu_error_type(), ComputePassErrorInner::InvalidValuesOffset(e) => e.webgpu_error_type(), ComputePassErrorInner::InvalidParentEncoder | ComputePassErrorInner::BindGroupIndexOutOfRange { .. } | ComputePassErrorInner::UnalignedIndirectBufferOffset(_) | ComputePassErrorInner::IndirectBufferOverrun { .. } | ComputePassErrorInner::ImmediateOffsetAlignment | ComputePassErrorInner::ImmediateDataizeAlignment | ComputePassErrorInner::ImmediateOutOfMemory | ComputePassErrorInner::PassEnded => ErrorType::Validation, } } } struct State<'scope, 'snatch_guard, 'cmd_enc> { pipeline: Option>, pass: pass::PassState<'scope, 'snatch_guard, 'cmd_enc>, active_query: Option<(Arc, u32)>, immediates: Vec, intermediate_trackers: Tracker, } impl<'scope, 'snatch_guard, 'cmd_enc> State<'scope, 'snatch_guard, 'cmd_enc> { fn is_ready(&self) -> Result<(), DispatchError> { if let Some(pipeline) = self.pipeline.as_ref() { self.pass.binder.check_compatibility(pipeline.as_ref())?; self.pass.binder.check_late_buffer_bindings()?; Ok(()) } else { Err(DispatchError::MissingPipeline(pass::MissingPipeline)) } } /// Flush binding state in preparation for a dispatch. /// /// # Differences between render and compute passes /// /// There are differences between the `flush_bindings` implementations for /// render and compute passes, because render passes have a single usage /// scope for the entire pass, and compute passes have a separate usage /// scope for each dispatch. /// /// For compute passes, bind groups are merged into a fresh usage scope /// here, not into the pass usage scope within calls to `set_bind_group`. As /// specified by WebGPU, for compute passes, we merge only the bind groups /// that are actually used by the pipeline, unlike render passes, which /// merge every bind group that is ever set, even if it is not ultimately /// used by the pipeline. /// /// For compute passes, we call `drain_barriers` here, because barriers may /// be needed before each dispatch if a previous dispatch had a conflicting /// usage. For render passes, barriers are emitted once at the start of the /// render pass. /// /// # Indirect buffer handling /// /// The `indirect_buffer` argument should be passed for any indirect /// dispatch (with or without validation). It will be checked for /// conflicting usages according to WebGPU rules. For the purpose of /// these rules, the fact that we have actually processed the buffer in /// the validation pass is an implementation detail. /// /// The `track_indirect_buffer` argument should be set when doing indirect /// dispatch *without* validation. In this case, the indirect buffer will /// be added to the tracker in order to generate any necessary transitions /// for that usage. /// /// When doing indirect dispatch *with* validation, the indirect buffer is /// processed by the validation pass and is not used by the actual dispatch. /// The indirect validation code handles transitions for the validation /// pass. fn flush_bindings( &mut self, indirect_buffer: Option<&Arc>, track_indirect_buffer: bool, ) -> Result<(), ComputePassErrorInner> { for bind_group in self.pass.binder.list_active() { unsafe { self.pass.scope.merge_bind_group(&bind_group.used)? }; } // Add the indirect buffer. Because usage scopes are per-dispatch, this // is the only place where INDIRECT usage could be added, and it is safe // for us to remove it below. if let Some(buffer) = indirect_buffer { self.pass .scope .buffers .merge_single(buffer, wgt::BufferUses::INDIRECT)?; } // For compute, usage scopes are associated with each dispatch and not // with the pass as a whole. However, because the cost of creating and // dropping `UsageScope`s is significant (even with the pool), we // add and then remove usage from a single usage scope. for bind_group in self.pass.binder.list_active() { self.intermediate_trackers .set_and_remove_from_usage_scope_sparse(&mut self.pass.scope, &bind_group.used); } if track_indirect_buffer { self.intermediate_trackers .buffers .set_and_remove_from_usage_scope_sparse( &mut self.pass.scope.buffers, indirect_buffer.map(|buf| buf.tracker_index()), ); } else if let Some(buffer) = indirect_buffer { self.pass .scope .buffers .remove_usage(buffer, wgt::BufferUses::INDIRECT); } flush_bindings_helper(&mut self.pass)?; CommandEncoder::drain_barriers( self.pass.base.raw_encoder, &mut self.intermediate_trackers, self.pass.base.snatch_guard, ); Ok(()) } } // Running the compute pass. impl Global { /// Creates a compute pass. /// /// If creation fails, an invalid pass is returned. Attempting to record /// commands into an invalid pass is permitted, but a validation error will /// ultimately be generated when the parent encoder is finished, and it is /// not possible to run any commands from the invalid pass. /// /// If successful, puts the encoder into the [`Locked`] state. /// /// [`Locked`]: crate::command::CommandEncoderStatus::Locked pub fn command_encoder_begin_compute_pass( &self, encoder_id: id::CommandEncoderId, desc: &ComputePassDescriptor<'_>, ) -> (ComputePass, Option) { use EncoderStateError as SErr; let scope = PassErrorScope::Pass; let hub = &self.hub; let label = desc.label.as_deref().map(Cow::Borrowed); let cmd_enc = hub.command_encoders.get(encoder_id); let mut cmd_buf_data = cmd_enc.data.lock(); match cmd_buf_data.lock_encoder() { Ok(()) => { drop(cmd_buf_data); if let Err(err) = cmd_enc.device.check_is_valid() { return ( ComputePass::new_invalid(cmd_enc, &label, err.map_pass_err(scope)), None, ); } match desc .timestamp_writes .as_ref() .map(|tw| { Self::validate_pass_timestamp_writes::( &cmd_enc.device, &hub.query_sets.read(), tw, ) }) .transpose() { Ok(timestamp_writes) => { let arc_desc = ArcComputePassDescriptor { label, timestamp_writes, }; (ComputePass::new(cmd_enc, arc_desc), None) } Err(err) => ( ComputePass::new_invalid(cmd_enc, &label, err.map_pass_err(scope)), None, ), } } Err(err @ SErr::Locked) => { // Attempting to open a new pass while the encoder is locked // invalidates the encoder, but does not generate a validation // error. cmd_buf_data.invalidate(err.clone()); drop(cmd_buf_data); ( ComputePass::new_invalid(cmd_enc, &label, err.map_pass_err(scope)), None, ) } Err(err @ (SErr::Ended | SErr::Submitted)) => { // Attempting to open a new pass after the encode has ended // generates an immediate validation error. drop(cmd_buf_data); ( ComputePass::new_invalid(cmd_enc, &label, err.clone().map_pass_err(scope)), Some(err.into()), ) } Err(err @ SErr::Invalid) => { // Passes can be opened even on an invalid encoder. Such passes // are even valid, but since there's no visible side-effect of // the pass being valid and there's no point in storing recorded // commands that will ultimately be discarded, we open an // invalid pass to save that work. drop(cmd_buf_data); ( ComputePass::new_invalid(cmd_enc, &label, err.map_pass_err(scope)), None, ) } Err(SErr::Unlocked) => { unreachable!("lock_encoder cannot fail due to the encoder being unlocked") } } } pub fn compute_pass_end(&self, pass: &mut ComputePass) -> Result<(), EncoderStateError> { profiling::scope!( "CommandEncoder::run_compute_pass {}", pass.base.label.as_deref().unwrap_or("") ); let cmd_enc = pass.parent.take().ok_or(EncoderStateError::Ended)?; let mut cmd_buf_data = cmd_enc.data.lock(); cmd_buf_data.unlock_encoder()?; let base = pass.base.take(); if let Err(ComputePassError { inner: ComputePassErrorInner::EncoderState( err @ (EncoderStateError::Locked | EncoderStateError::Ended), ), scope: _, }) = base { // Most encoding errors are detected and raised within `finish()`. // // However, we raise a validation error here if the pass was opened // within another pass, or on a finished encoder. The latter is // particularly important, because in that case reporting errors via // `CommandEncoder::finish` is not possible. return Err(err.clone()); } cmd_buf_data.push_with(|| -> Result<_, ComputePassError> { Ok(ArcCommand::RunComputePass { pass: base?, timestamp_writes: pass.timestamp_writes.take(), }) }) } } pub(super) fn encode_compute_pass( parent_state: &mut EncodingState, mut base: BasePass, mut timestamp_writes: Option, ) -> Result<(), ComputePassError> { let pass_scope = PassErrorScope::Pass; let device = parent_state.device; // We automatically keep extending command buffers over time, and because // we want to insert a command buffer _before_ what we're about to record, // we need to make sure to close the previous one. parent_state .raw_encoder .close_if_open() .map_pass_err(pass_scope)?; let raw_encoder = parent_state .raw_encoder .open_pass(base.label.as_deref()) .map_pass_err(pass_scope)?; let mut debug_scope_depth = 0; let mut state = State { pipeline: None, pass: pass::PassState { base: EncodingState { device, raw_encoder, tracker: parent_state.tracker, buffer_memory_init_actions: parent_state.buffer_memory_init_actions, texture_memory_actions: parent_state.texture_memory_actions, as_actions: parent_state.as_actions, temp_resources: parent_state.temp_resources, indirect_draw_validation_resources: parent_state.indirect_draw_validation_resources, snatch_guard: parent_state.snatch_guard, debug_scope_depth: &mut debug_scope_depth, }, binder: Binder::new(), temp_offsets: Vec::new(), dynamic_offset_count: 0, pending_discard_init_fixups: SurfacesInDiscardState::new(), scope: device.new_usage_scope(), string_offset: 0, }, active_query: None, immediates: Vec::new(), intermediate_trackers: Tracker::new( device.ordered_buffer_usages, device.ordered_texture_usages, ), }; let indices = &device.tracker_indices; state .pass .base .tracker .buffers .set_size(indices.buffers.size()); state .pass .base .tracker .textures .set_size(indices.textures.size()); let timestamp_writes: Option> = if let Some(tw) = timestamp_writes.take() { tw.query_set.same_device(device).map_pass_err(pass_scope)?; let query_set = state .pass .base .tracker .query_sets .insert_single(tw.query_set); // Unlike in render passes we can't delay resetting the query sets since // there is no auxiliary pass. let range = if let (Some(index_a), Some(index_b)) = (tw.beginning_of_pass_write_index, tw.end_of_pass_write_index) { Some(index_a.min(index_b)..index_a.max(index_b) + 1) } else { tw.beginning_of_pass_write_index .or(tw.end_of_pass_write_index) .map(|i| i..i + 1) }; // Range should always be Some, both values being None should lead to a validation error. // But no point in erroring over that nuance here! if let Some(range) = range { unsafe { state .pass .base .raw_encoder .reset_queries(query_set.raw(), range); } } Some(hal::PassTimestampWrites { query_set: query_set.raw(), beginning_of_pass_write_index: tw.beginning_of_pass_write_index, end_of_pass_write_index: tw.end_of_pass_write_index, }) } else { None }; let hal_desc = hal::ComputePassDescriptor { label: hal_label(base.label.as_deref(), device.instance_flags), timestamp_writes, }; unsafe { state.pass.base.raw_encoder.begin_compute_pass(&hal_desc); } for command in base.commands.drain(..) { match command { ArcComputeCommand::SetBindGroup { index, num_dynamic_offsets, bind_group, } => { let scope = PassErrorScope::SetBindGroup; pass::set_bind_group::( &mut state.pass, device, &base.dynamic_offsets, index, num_dynamic_offsets, bind_group, false, ) .map_pass_err(scope)?; } ArcComputeCommand::SetPipeline(pipeline) => { let scope = PassErrorScope::SetPipelineCompute; set_pipeline(&mut state, device, pipeline).map_pass_err(scope)?; } ArcComputeCommand::SetImmediate { offset, size_bytes, values_offset, } => { let scope = PassErrorScope::SetImmediate; pass::set_immediates::( &mut state.pass, &base.immediates_data, offset, size_bytes, Some(values_offset), |data_slice| { let offset_in_elements = (offset / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize; let size_in_elements = (size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize; state.immediates[offset_in_elements..][..size_in_elements] .copy_from_slice(data_slice); }, ) .map_pass_err(scope)?; } ArcComputeCommand::Dispatch(groups) => { let scope = PassErrorScope::Dispatch { indirect: false }; dispatch(&mut state, groups).map_pass_err(scope)?; } ArcComputeCommand::DispatchIndirect { buffer, offset } => { let scope = PassErrorScope::Dispatch { indirect: true }; dispatch_indirect(&mut state, device, buffer, offset).map_pass_err(scope)?; } ArcComputeCommand::PushDebugGroup { color: _, len } => { pass::push_debug_group(&mut state.pass, &base.string_data, len); } ArcComputeCommand::PopDebugGroup => { let scope = PassErrorScope::PopDebugGroup; pass::pop_debug_group::(&mut state.pass) .map_pass_err(scope)?; } ArcComputeCommand::InsertDebugMarker { color: _, len } => { pass::insert_debug_marker(&mut state.pass, &base.string_data, len); } ArcComputeCommand::WriteTimestamp { query_set, query_index, } => { let scope = PassErrorScope::WriteTimestamp; pass::write_timestamp::( &mut state.pass, device, None, // compute passes do not attempt to coalesce query resets query_set, query_index, ) .map_pass_err(scope)?; } ArcComputeCommand::BeginPipelineStatisticsQuery { query_set, query_index, } => { let scope = PassErrorScope::BeginPipelineStatisticsQuery; validate_and_begin_pipeline_statistics_query( query_set, state.pass.base.raw_encoder, &mut state.pass.base.tracker.query_sets, device, query_index, None, &mut state.active_query, ) .map_pass_err(scope)?; } ArcComputeCommand::EndPipelineStatisticsQuery => { let scope = PassErrorScope::EndPipelineStatisticsQuery; end_pipeline_statistics_query(state.pass.base.raw_encoder, &mut state.active_query) .map_pass_err(scope)?; } } } if *state.pass.base.debug_scope_depth > 0 { Err( ComputePassErrorInner::DebugGroupError(DebugGroupError::MissingPop) .map_pass_err(pass_scope), )?; } unsafe { state.pass.base.raw_encoder.end_compute_pass(); } let State { pass: pass::PassState { pending_discard_init_fixups, .. }, intermediate_trackers, .. } = state; // Stop the current command encoder. parent_state.raw_encoder.close().map_pass_err(pass_scope)?; // Create a new command encoder, which we will insert _before_ the body of the compute pass. // // Use that buffer to insert barriers and clear discarded images. let transit = parent_state .raw_encoder .open_pass(hal_label( Some("(wgpu internal) Pre Pass"), device.instance_flags, )) .map_pass_err(pass_scope)?; fixup_discarded_surfaces( pending_discard_init_fixups.into_iter(), transit, &mut parent_state.tracker.textures, device, parent_state.snatch_guard, ); CommandEncoder::insert_barriers_from_tracker( transit, parent_state.tracker, &intermediate_trackers, parent_state.snatch_guard, ); // Close the command encoder, and swap it with the previous. parent_state .raw_encoder .close_and_swap() .map_pass_err(pass_scope)?; Ok(()) } fn set_pipeline( state: &mut State, device: &Arc, pipeline: Arc, ) -> Result<(), ComputePassErrorInner> { pipeline.same_device(device)?; state.pipeline = Some(pipeline.clone()); let pipeline = state .pass .base .tracker .compute_pipelines .insert_single(pipeline) .clone(); unsafe { state .pass .base .raw_encoder .set_compute_pipeline(pipeline.raw()); } // Rebind resources pass::change_pipeline_layout::( &mut state.pass, &pipeline.layout, &pipeline.late_sized_buffer_groups, || { // This only needs to be here for compute pipelines because they use immediates for // validating indirect draws. state.immediates.clear(); // Note that can only be one range for each stage. See the `MoreThanOneImmediateRangePerStage` error. if pipeline.layout.immediate_size != 0 { // Note that non-0 range start doesn't work anyway https://github.com/gfx-rs/wgpu/issues/4502 let len = pipeline.layout.immediate_size as usize / wgt::IMMEDIATE_DATA_ALIGNMENT as usize; state.immediates.extend(core::iter::repeat_n(0, len)); } }, ) } fn dispatch(state: &mut State, groups: [u32; 3]) -> Result<(), ComputePassErrorInner> { api_log!("ComputePass::dispatch {groups:?}"); state.is_ready()?; state.flush_bindings(None, false)?; let groups_size_limit = state .pass .base .device .limits .max_compute_workgroups_per_dimension; if groups.iter().copied().any(|g| g > groups_size_limit) { return Err(ComputePassErrorInner::Dispatch( DispatchError::InvalidGroupSize { current: groups, limit: groups_size_limit, }, )); } unsafe { state.pass.base.raw_encoder.dispatch(groups); } Ok(()) } fn dispatch_indirect( state: &mut State, device: &Arc, buffer: Arc, offset: u64, ) -> Result<(), ComputePassErrorInner> { api_log!("ComputePass::dispatch_indirect"); buffer.same_device(device)?; state.is_ready()?; state .pass .base .device .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?; buffer.check_usage(wgt::BufferUsages::INDIRECT)?; if !offset.is_multiple_of(4) { return Err(ComputePassErrorInner::UnalignedIndirectBufferOffset(offset)); } let end_offset = offset + size_of::() as u64; if end_offset > buffer.size { return Err(ComputePassErrorInner::IndirectBufferOverrun { offset, end_offset, buffer_size: buffer.size, }); } buffer.check_destroyed(state.pass.base.snatch_guard)?; let stride = 3 * 4; // 3 integers, x/y/z group size state.pass.base.buffer_memory_init_actions.extend( buffer.initialization_status.read().create_action( &buffer, offset..(offset + stride), MemoryInitKind::NeedsInitializedMemory, ), ); if let Some(ref indirect_validation) = state.pass.base.device.indirect_validation { let params = indirect_validation.dispatch.params( &state.pass.base.device.limits, offset, buffer.size, ); unsafe { state .pass .base .raw_encoder .set_compute_pipeline(params.pipeline); } unsafe { state.pass.base.raw_encoder.set_immediates( params.pipeline_layout, 0, &[params.offset_remainder as u32 / 4], ); } unsafe { state.pass.base.raw_encoder.set_bind_group( params.pipeline_layout, 0, params.dst_bind_group, &[], ); } unsafe { state.pass.base.raw_encoder.set_bind_group( params.pipeline_layout, 1, buffer .indirect_validation_bind_groups .get(state.pass.base.snatch_guard) .unwrap() .dispatch .as_ref(), &[params.aligned_offset as u32], ); } let src_transition = state .intermediate_trackers .buffers .set_single(&buffer, wgt::BufferUses::STORAGE_READ_ONLY); let src_barrier = src_transition .map(|transition| transition.into_hal(&buffer, state.pass.base.snatch_guard)); unsafe { state .pass .base .raw_encoder .transition_buffers(src_barrier.as_slice()); } unsafe { state .pass .base .raw_encoder .transition_buffers(&[hal::BufferBarrier { buffer: params.dst_buffer, usage: hal::StateTransition { from: wgt::BufferUses::INDIRECT, to: wgt::BufferUses::STORAGE_READ_WRITE, }, }]); } unsafe { state.pass.base.raw_encoder.dispatch([1, 1, 1]); } // reset state { let pipeline = state.pipeline.as_ref().unwrap(); unsafe { state .pass .base .raw_encoder .set_compute_pipeline(pipeline.raw()); } if !state.immediates.is_empty() { unsafe { state.pass.base.raw_encoder.set_immediates( pipeline.layout.raw(), 0, &state.immediates, ); } } for (i, group, dynamic_offsets) in state.pass.binder.list_valid() { let raw_bg = group.try_raw(state.pass.base.snatch_guard)?; unsafe { state.pass.base.raw_encoder.set_bind_group( pipeline.layout.raw(), i as u32, raw_bg, dynamic_offsets, ); } } } unsafe { state .pass .base .raw_encoder .transition_buffers(&[hal::BufferBarrier { buffer: params.dst_buffer, usage: hal::StateTransition { from: wgt::BufferUses::STORAGE_READ_WRITE, to: wgt::BufferUses::INDIRECT, }, }]); } state.flush_bindings(Some(&buffer), false)?; unsafe { state .pass .base .raw_encoder .dispatch_indirect(params.dst_buffer, 0); } } else { state.flush_bindings(Some(&buffer), true)?; let buf_raw = buffer.try_raw(state.pass.base.snatch_guard)?; unsafe { state .pass .base .raw_encoder .dispatch_indirect(buf_raw, offset); } } Ok(()) } // Recording a compute pass. // // The only error that should be returned from these methods is // `EncoderStateError::Ended`, when the pass has already ended and an immediate // validation error is raised. // // All other errors should be stored in the pass for later reporting when // `CommandEncoder.finish()` is called. // // The `pass_try!` macro should be used to handle errors appropriately. Note // that the `pass_try!` and `pass_base!` macros may return early from the // function that invokes them, like the `?` operator. impl Global { pub fn compute_pass_set_bind_group( &self, pass: &mut ComputePass, index: u32, bind_group_id: Option, offsets: &[DynamicOffset], ) -> Result<(), PassStateError> { let scope = PassErrorScope::SetBindGroup; // This statement will return an error if the pass is ended. It's // important the error check comes before the early-out for // `set_and_check_redundant`. let base = pass_base!(pass, scope); if pass.current_bind_groups.set_and_check_redundant( bind_group_id, index, &mut base.dynamic_offsets, offsets, ) { return Ok(()); } let mut bind_group = None; if let Some(bind_group_id) = bind_group_id { let hub = &self.hub; bind_group = Some(pass_try!( base, scope, hub.bind_groups.get(bind_group_id).get(), )); } base.commands.push(ArcComputeCommand::SetBindGroup { index, num_dynamic_offsets: offsets.len(), bind_group, }); Ok(()) } pub fn compute_pass_set_pipeline( &self, pass: &mut ComputePass, pipeline_id: id::ComputePipelineId, ) -> Result<(), PassStateError> { let redundant = pass.current_pipeline.set_and_check_redundant(pipeline_id); let scope = PassErrorScope::SetPipelineCompute; // This statement will return an error if the pass is ended. // Its important the error check comes before the early-out for `redundant`. let base = pass_base!(pass, scope); if redundant { return Ok(()); } let hub = &self.hub; let pipeline = pass_try!(base, scope, hub.compute_pipelines.get(pipeline_id).get()); base.commands.push(ArcComputeCommand::SetPipeline(pipeline)); Ok(()) } pub fn compute_pass_set_immediates( &self, pass: &mut ComputePass, offset: u32, data: &[u8], ) -> Result<(), PassStateError> { let scope = PassErrorScope::SetImmediate; let base = pass_base!(pass, scope); if offset & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1) != 0 { pass_try!( base, scope, Err(ComputePassErrorInner::ImmediateOffsetAlignment), ); } if data.len() as u32 & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1) != 0 { pass_try!( base, scope, Err(ComputePassErrorInner::ImmediateDataizeAlignment), ) } let value_offset = pass_try!( base, scope, base.immediates_data .len() .try_into() .map_err(|_| ComputePassErrorInner::ImmediateOutOfMemory) ); base.immediates_data.extend( data.chunks_exact(wgt::IMMEDIATE_DATA_ALIGNMENT as usize) .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])), ); base.commands.push(ArcComputeCommand::SetImmediate { offset, size_bytes: data.len() as u32, values_offset: value_offset, }); Ok(()) } pub fn compute_pass_dispatch_workgroups( &self, pass: &mut ComputePass, groups_x: u32, groups_y: u32, groups_z: u32, ) -> Result<(), PassStateError> { let scope = PassErrorScope::Dispatch { indirect: false }; pass_base!(pass, scope) .commands .push(ArcComputeCommand::Dispatch([groups_x, groups_y, groups_z])); Ok(()) } pub fn compute_pass_dispatch_workgroups_indirect( &self, pass: &mut ComputePass, buffer_id: id::BufferId, offset: BufferAddress, ) -> Result<(), PassStateError> { let hub = &self.hub; let scope = PassErrorScope::Dispatch { indirect: true }; let base = pass_base!(pass, scope); let buffer = pass_try!(base, scope, hub.buffers.get(buffer_id).get()); base.commands .push(ArcComputeCommand::DispatchIndirect { buffer, offset }); Ok(()) } pub fn compute_pass_push_debug_group( &self, pass: &mut ComputePass, label: &str, color: u32, ) -> Result<(), PassStateError> { let base = pass_base!(pass, PassErrorScope::PushDebugGroup); let bytes = label.as_bytes(); base.string_data.extend_from_slice(bytes); base.commands.push(ArcComputeCommand::PushDebugGroup { color, len: bytes.len(), }); Ok(()) } pub fn compute_pass_pop_debug_group( &self, pass: &mut ComputePass, ) -> Result<(), PassStateError> { let base = pass_base!(pass, PassErrorScope::PopDebugGroup); base.commands.push(ArcComputeCommand::PopDebugGroup); Ok(()) } pub fn compute_pass_insert_debug_marker( &self, pass: &mut ComputePass, label: &str, color: u32, ) -> Result<(), PassStateError> { let base = pass_base!(pass, PassErrorScope::InsertDebugMarker); let bytes = label.as_bytes(); base.string_data.extend_from_slice(bytes); base.commands.push(ArcComputeCommand::InsertDebugMarker { color, len: bytes.len(), }); Ok(()) } pub fn compute_pass_write_timestamp( &self, pass: &mut ComputePass, query_set_id: id::QuerySetId, query_index: u32, ) -> Result<(), PassStateError> { let scope = PassErrorScope::WriteTimestamp; let base = pass_base!(pass, scope); let hub = &self.hub; let query_set = pass_try!(base, scope, hub.query_sets.get(query_set_id).get()); base.commands.push(ArcComputeCommand::WriteTimestamp { query_set, query_index, }); Ok(()) } pub fn compute_pass_begin_pipeline_statistics_query( &self, pass: &mut ComputePass, query_set_id: id::QuerySetId, query_index: u32, ) -> Result<(), PassStateError> { let scope = PassErrorScope::BeginPipelineStatisticsQuery; let base = pass_base!(pass, scope); let hub = &self.hub; let query_set = pass_try!(base, scope, hub.query_sets.get(query_set_id).get()); base.commands .push(ArcComputeCommand::BeginPipelineStatisticsQuery { query_set, query_index, }); Ok(()) } pub fn compute_pass_end_pipeline_statistics_query( &self, pass: &mut ComputePass, ) -> Result<(), PassStateError> { pass_base!(pass, PassErrorScope::EndPipelineStatisticsQuery) .commands .push(ArcComputeCommand::EndPipelineStatisticsQuery); Ok(()) } } ================================================ FILE: wgpu-core/src/command/compute_command.rs ================================================ #[cfg(feature = "serde")] use crate::command::serde_object_reference_struct; use crate::command::{ArcReferences, ReferenceType}; #[cfg(feature = "serde")] use macro_rules_attribute::apply; /// cbindgen:ignore #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", apply(serde_object_reference_struct))] pub enum ComputeCommand { SetBindGroup { index: u32, num_dynamic_offsets: usize, bind_group: Option, }, SetPipeline(R::ComputePipeline), /// Set a range of immediates to values stored in `immediates_data`. SetImmediate { /// The byte offset within the immediate data storage to write to. This /// must be a multiple of four. offset: u32, /// The number of bytes to write. This must be a multiple of four. size_bytes: u32, /// Index in `immediates_data` of the start of the data /// to be written. /// /// Note: this is not a byte offset like `offset`. Rather, it is the /// index of the first `u32` element in `immediates_data` to read. values_offset: u32, }, Dispatch([u32; 3]), DispatchIndirect { buffer: R::Buffer, offset: wgt::BufferAddress, }, PushDebugGroup { color: u32, len: usize, }, PopDebugGroup, InsertDebugMarker { color: u32, len: usize, }, WriteTimestamp { query_set: R::QuerySet, query_index: u32, }, BeginPipelineStatisticsQuery { query_set: R::QuerySet, query_index: u32, }, EndPipelineStatisticsQuery, } /// cbindgen:ignore pub type ArcComputeCommand = ComputeCommand; ================================================ FILE: wgpu-core/src/command/draw.rs ================================================ use alloc::boxed::Box; use thiserror::Error; use wgt::error::{ErrorType, WebGpuError}; use super::bind::BinderError; use crate::command::pass; use crate::{ binding_model::{BindingError, ImmediateUploadError, LateMinBufferBindingSizeMismatch}, resource::{ DestroyedResourceError, MissingBufferUsageError, MissingTextureUsageError, ResourceErrorIdent, }, track::ResourceUsageCompatibilityError, }; /// Error validating a draw call. #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum DrawError { #[error("Blend constant needs to be set")] MissingBlendConstant, #[error("Render pipeline must be set")] MissingPipeline(#[from] pass::MissingPipeline), #[error("Currently set {pipeline} requires vertex buffer {index} to be set")] MissingVertexBuffer { pipeline: ResourceErrorIdent, index: u32, }, #[error("Index buffer must be set")] MissingIndexBuffer, #[error(transparent)] IncompatibleBindGroup(#[from] Box), #[error("Vertex {last_vertex} extends beyond limit {vertex_limit} imposed by the buffer in slot {slot}. Did you bind the correct `Vertex` step-rate vertex buffer?")] VertexBeyondLimit { last_vertex: u64, vertex_limit: u64, slot: u32, }, #[error("Instance {last_instance} extends beyond limit {instance_limit} imposed by the buffer in slot {slot}. Did you bind the correct `Instance` step-rate vertex buffer?")] InstanceBeyondLimit { last_instance: u64, instance_limit: u64, slot: u32, }, #[error("Index {last_index} extends beyond limit {index_limit}. Did you bind the correct index buffer?")] IndexBeyondLimit { last_index: u64, index_limit: u64 }, #[error("For indexed drawing with strip topology, {pipeline}'s strip index format {strip_index_format:?} must match index buffer format {buffer_format:?}")] UnmatchedStripIndexFormat { pipeline: ResourceErrorIdent, strip_index_format: Option, buffer_format: wgt::IndexFormat, }, #[error(transparent)] BindingSizeTooSmall(#[from] LateMinBufferBindingSizeMismatch), #[error( "Wrong pipeline type for this draw command. Attempted to call {} draw command on {} pipeline", if *wanted_mesh_pipeline {"mesh shader"} else {"standard"}, if *wanted_mesh_pipeline {"standard"} else {"mesh shader"}, )] WrongPipelineType { wanted_mesh_pipeline: bool }, #[error( "Each current draw group size dimension ({current:?}) must be less or equal to {limit}, and the product must be less or equal to {max_total}" )] InvalidGroupSize { current: [u32; 3], limit: u32, max_total: u32, }, #[error( "Mesh shader calls in multiview render passes require enabling the `EXPERIMENTAL_MESH_SHADER_MULTIVIEW` feature, and the highest bit ({highest_view_index}) in the multiview mask must be <= `Limits::max_multiview_view_count` ({max_multiviews})" )] MeshPipelineMultiviewLimitsViolated { highest_view_index: u32, max_multiviews: u32, }, } impl WebGpuError for DrawError { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } } /// Error encountered when encoding a render command. /// This is the shared error set between render bundles and passes. #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum RenderCommandError { #[error(transparent)] BindGroupIndexOutOfRange(#[from] pass::BindGroupIndexOutOfRange), #[error("Vertex buffer index {index} is greater than the device's requested `max_vertex_buffers` limit {max}")] VertexBufferIndexOutOfRange { index: u32, max: u32 }, #[error( "Offset {offset} for vertex buffer in slot {slot} is not a multiple of `VERTEX_ALIGNMENT`" )] UnalignedVertexBuffer { slot: u32, offset: u64 }, #[error("Offset {offset} for index buffer is not a multiple of {alignment}")] UnalignedIndexBuffer { offset: u64, alignment: usize }, #[error("Render pipeline targets are incompatible with render pass")] IncompatiblePipelineTargets(#[from] crate::device::RenderPassCompatibilityError), #[error("{0} writes to depth, while the pass has read-only depth access")] IncompatibleDepthAccess(ResourceErrorIdent), #[error("{0} writes to stencil, while the pass has read-only stencil access")] IncompatibleStencilAccess(ResourceErrorIdent), #[error(transparent)] ResourceUsageCompatibility(#[from] ResourceUsageCompatibilityError), #[error(transparent)] DestroyedResource(#[from] DestroyedResourceError), #[error(transparent)] MissingBufferUsage(#[from] MissingBufferUsageError), #[error(transparent)] MissingTextureUsage(#[from] MissingTextureUsageError), #[error(transparent)] ImmediateData(#[from] ImmediateUploadError), #[error(transparent)] BindingError(#[from] BindingError), #[error("Viewport size {{ w: {w}, h: {h} }} greater than device's requested `max_texture_dimension_2d` limit {max}, or less than zero")] InvalidViewportRectSize { w: f32, h: f32, max: u32 }, #[error("Viewport has invalid rect {rect:?} for device's requested `max_texture_dimension_2d` limit; Origin less than -2 * `max_texture_dimension_2d` ({min}), or rect extends past 2 * `max_texture_dimension_2d` - 1 ({max})")] InvalidViewportRectPosition { rect: Rect, min: f32, max: f32 }, #[error("Viewport minDepth {0} and/or maxDepth {1} are not in [0, 1]")] InvalidViewportDepth(f32, f32), #[error("Scissor {0:?} is not contained in the render target {1:?}")] InvalidScissorRect(Rect, wgt::Extent3d), #[error("Support for {0} is not implemented yet")] Unimplemented(&'static str), } impl WebGpuError for RenderCommandError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::IncompatiblePipelineTargets(e) => e.webgpu_error_type(), Self::ResourceUsageCompatibility(e) => e.webgpu_error_type(), Self::DestroyedResource(e) => e.webgpu_error_type(), Self::MissingBufferUsage(e) => e.webgpu_error_type(), Self::MissingTextureUsage(e) => e.webgpu_error_type(), Self::ImmediateData(e) => e.webgpu_error_type(), Self::BindingError(e) => e.webgpu_error_type(), Self::BindGroupIndexOutOfRange { .. } | Self::VertexBufferIndexOutOfRange { .. } | Self::UnalignedIndexBuffer { .. } | Self::UnalignedVertexBuffer { .. } | Self::IncompatibleDepthAccess(..) | Self::IncompatibleStencilAccess(..) | Self::InvalidViewportRectSize { .. } | Self::InvalidViewportRectPosition { .. } | Self::InvalidViewportDepth(..) | Self::InvalidScissorRect(..) | Self::Unimplemented(..) => ErrorType::Validation, } } } #[derive(Clone, Copy, Debug, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Rect { pub x: T, pub y: T, pub w: T, pub h: T, } ================================================ FILE: wgpu-core/src/command/encoder.rs ================================================ use alloc::{sync::Arc, vec::Vec}; use crate::{ command::memory_init::CommandBufferTextureMemoryActions, device::{queue::TempResource, Device}, init_tracker::BufferInitTrackerAction, ray_tracing::AsAction, snatch::SnatchGuard, track::Tracker, }; /// State applicable when encoding commands onto a compute pass, render pass, or /// directly to a command encoder. /// /// Most encoding routines just want to receive an open encoder, write /// command(s) to it, and leave it open for whatever is next. In this case the /// `E` type parameter has the default value of `dyn hal::DynCommandEncoder`. To /// avoid confusion about encoder state, we set the convention that _the encoder /// in an `EncodingState` holding a bare HAL reference must always be open_. /// /// Compute and render passes are more complicated. Because they record a /// command buffer for a housekeeping pre-pass which is inserted before the pass /// itself, the first thing they will do is close and reopen the encoder if it /// is already open. Unnecessary empty HAL passes can be avoided by passing them /// the encoder in whatever state it happens to be. In this case, `E` is /// `InnerCommandEncoder`, which tracks the state of the encoder. The callee /// (the render or compute pass) will open and close the encoder as necessary. /// /// This structure is not supported by cbindgen because it contains a trait /// object reference. /// /// cbindgen:ignore pub(crate) struct EncodingState<'snatch_guard, 'cmd_enc, E: ?Sized = dyn hal::DynCommandEncoder> { pub(crate) device: &'cmd_enc Arc, pub(crate) raw_encoder: &'cmd_enc mut E, pub(crate) tracker: &'cmd_enc mut Tracker, pub(crate) buffer_memory_init_actions: &'cmd_enc mut Vec, pub(crate) texture_memory_actions: &'cmd_enc mut CommandBufferTextureMemoryActions, pub(crate) as_actions: &'cmd_enc mut Vec, pub(crate) temp_resources: &'cmd_enc mut Vec, pub(crate) indirect_draw_validation_resources: &'cmd_enc mut crate::indirect_validation::DrawResources, pub(crate) snatch_guard: &'snatch_guard SnatchGuard<'snatch_guard>, /// Current debug scope nesting depth. /// /// When encoding a compute or render pass, this is the depth of debug /// scopes in the pass, not the depth of debug scopes in the parent encoder. pub(crate) debug_scope_depth: &'cmd_enc mut u32, } ================================================ FILE: wgpu-core/src/command/encoder_command.rs ================================================ use core::{convert::Infallible, num::NonZero}; use alloc::{string::String, sync::Arc, vec::Vec}; #[cfg(feature = "serde")] use macro_rules_attribute::{apply, attribute_alias}; use crate::{ command::ColorAttachments, id, instance::Surface, resource::{Buffer, QuerySet, Texture}, }; pub trait ReferenceType { type Buffer: Clone + core::fmt::Debug; type Surface: Clone; // Surface does not implement Debug, although it probably could. type Texture: Clone + core::fmt::Debug; type TextureView: Clone + core::fmt::Debug; type ExternalTexture: Clone + core::fmt::Debug; type QuerySet: Clone + core::fmt::Debug; type BindGroup: Clone + core::fmt::Debug; type RenderPipeline: Clone + core::fmt::Debug; type RenderBundle: Clone + core::fmt::Debug; type ComputePipeline: Clone + core::fmt::Debug; type Blas: Clone + core::fmt::Debug; type Tlas: Clone + core::fmt::Debug; } /// Reference wgpu objects via numeric IDs assigned by [`crate::identity::IdentityManager`]. #[derive(Clone, Debug)] pub struct IdReferences; /// Reference wgpu objects via the integer value of pointers. /// /// This is used for trace recording and playback. Recording stores the pointer /// value of `Arc` references in the trace. Playback uses the integer values /// as keys to a `HashMap`. #[cfg(any(feature = "trace", feature = "replay"))] #[doc(hidden)] #[derive(Clone, Debug)] pub struct PointerReferences; /// Reference wgpu objects via `Arc`s. #[derive(Clone, Debug)] pub struct ArcReferences; impl ReferenceType for IdReferences { type Buffer = id::BufferId; type Surface = id::SurfaceId; type Texture = id::TextureId; type TextureView = id::TextureViewId; type ExternalTexture = id::ExternalTextureId; type QuerySet = id::QuerySetId; type BindGroup = id::BindGroupId; type RenderPipeline = id::RenderPipelineId; type RenderBundle = id::RenderBundleId; type ComputePipeline = id::ComputePipelineId; type Blas = id::BlasId; type Tlas = id::TlasId; } #[cfg(any(feature = "trace", feature = "replay"))] impl ReferenceType for PointerReferences { type Buffer = id::PointerId; type Surface = id::PointerId; type Texture = id::PointerId; type TextureView = id::PointerId; type ExternalTexture = id::PointerId; type QuerySet = id::PointerId; type BindGroup = id::PointerId; type RenderPipeline = id::PointerId; type RenderBundle = id::PointerId; type ComputePipeline = id::PointerId; type Blas = id::PointerId; type Tlas = id::PointerId; } impl ReferenceType for ArcReferences { type Buffer = Arc; type Surface = Arc; type Texture = Arc; type TextureView = Arc; type ExternalTexture = Arc; type QuerySet = Arc; type BindGroup = Arc; type RenderPipeline = Arc; type RenderBundle = Arc; type ComputePipeline = Arc; type Blas = Arc; type Tlas = Arc; } #[cfg(feature = "serde")] attribute_alias! { #[apply(serde_object_reference_struct)] = #[derive(serde::Serialize, serde::Deserialize)] #[serde(bound = "R::Buffer: serde::Serialize + for<'d> serde::Deserialize<'d>,\ R::Surface: serde::Serialize + for<'d> serde::Deserialize<'d>,\ R::Texture: serde::Serialize + for<'d> serde::Deserialize<'d>,\ R::TextureView: serde::Serialize + for<'d> serde::Deserialize<'d>,\ R::ExternalTexture: serde::Serialize + for<'d> serde::Deserialize<'d>,\ R::QuerySet: serde::Serialize + for<'d> serde::Deserialize<'d>,\ R::BindGroup: serde::Serialize + for<'d> serde::Deserialize<'d>,\ R::RenderPipeline: serde::Serialize + for<'d> serde::Deserialize<'d>,\ R::RenderBundle: serde::Serialize + for<'d> serde::Deserialize<'d>,\ R::ComputePipeline: serde::Serialize + for<'d> serde::Deserialize<'d>,\ R::Blas: serde::Serialize + for<'d> serde::Deserialize<'d>,\ R::Tlas: serde::Serialize + for<'d> serde::Deserialize<'d>,\ wgt::BufferTransition: serde::Serialize + for<'d> serde::Deserialize<'d>,\ wgt::TextureTransition: serde::Serialize + for<'d> serde::Deserialize<'d>" )]; } #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", apply(serde_object_reference_struct))] pub enum Command { CopyBufferToBuffer { src: R::Buffer, src_offset: wgt::BufferAddress, dst: R::Buffer, dst_offset: wgt::BufferAddress, size: Option, }, CopyBufferToTexture { src: wgt::TexelCopyBufferInfo, dst: wgt::TexelCopyTextureInfo, size: wgt::Extent3d, }, CopyTextureToBuffer { src: wgt::TexelCopyTextureInfo, dst: wgt::TexelCopyBufferInfo, size: wgt::Extent3d, }, CopyTextureToTexture { src: wgt::TexelCopyTextureInfo, dst: wgt::TexelCopyTextureInfo, size: wgt::Extent3d, }, ClearBuffer { dst: R::Buffer, offset: wgt::BufferAddress, size: Option, }, ClearTexture { dst: R::Texture, subresource_range: wgt::ImageSubresourceRange, }, WriteTimestamp { query_set: R::QuerySet, query_index: u32, }, ResolveQuerySet { query_set: R::QuerySet, start_query: u32, query_count: u32, destination: R::Buffer, destination_offset: wgt::BufferAddress, }, PushDebugGroup(String), PopDebugGroup, InsertDebugMarker(String), RunComputePass { pass: crate::command::BasePass, Infallible>, timestamp_writes: Option>, }, RunRenderPass { pass: crate::command::BasePass, Infallible>, color_attachments: ColorAttachments, depth_stencil_attachment: Option>, timestamp_writes: Option>, occlusion_query_set: Option, multiview_mask: Option>, }, BuildAccelerationStructures { blas: Vec>, tlas: Vec>, }, TransitionResources { buffer_transitions: Vec>, texture_transitions: Vec>, }, } pub type ArcCommand = Command; ================================================ FILE: wgpu-core/src/command/ffi.rs ================================================ //! Types that are useful for FFI bindings to `wgpu`. use crate::{command::IdReferences, id}; pub type TexelCopyBufferInfo = wgt::TexelCopyBufferInfo; pub type TexelCopyTextureInfo = wgt::TexelCopyTextureInfo; pub type CopyExternalImageDestInfo = wgt::CopyExternalImageDestInfo; pub type Command = super::Command; ================================================ FILE: wgpu-core/src/command/memory_init.rs ================================================ use alloc::{ sync::Arc, vec::{Drain, Vec}, }; use core::ops::Range; use hashbrown::hash_map::Entry; use crate::{ device::Device, init_tracker::*, resource::{DestroyedResourceError, ParentDevice, RawResourceAccess, Texture, Trackable}, snatch::SnatchGuard, track::{DeviceTracker, TextureTracker}, FastHashMap, }; use super::{clear_texture, BakedCommands, ClearError}; /// Surface that was discarded by `StoreOp::Discard` of a preceding renderpass. /// Any read access to this surface needs to be preceded by a texture initialization. #[derive(Clone)] pub(crate) struct TextureSurfaceDiscard { pub texture: Arc, pub mip_level: u32, pub layer: u32, } pub(crate) type SurfacesInDiscardState = Vec; #[derive(Default)] pub(crate) struct CommandBufferTextureMemoryActions { /// The tracker actions that we need to be executed before the command /// buffer is executed. init_actions: Vec, /// All the discards that haven't been followed by init again within the /// command buffer i.e. everything in this list resets the texture init /// state *after* the command buffer execution discards: Vec, } impl CommandBufferTextureMemoryActions { pub(crate) fn drain_init_actions(&mut self) -> Drain<'_, TextureInitTrackerAction> { self.init_actions.drain(..) } pub(crate) fn discard(&mut self, discard: TextureSurfaceDiscard) { self.discards.push(discard); } // Registers a TextureInitTrackerAction. // Returns previously discarded surface that need to be initialized *immediately* now. // Only returns a non-empty list if action is MemoryInitKind::NeedsInitializedMemory. #[must_use] pub(crate) fn register_init_action( &mut self, action: &TextureInitTrackerAction, ) -> SurfacesInDiscardState { let mut immediately_necessary_clears = SurfacesInDiscardState::new(); // Note that within a command buffer we may stack arbitrary memory init // actions on the same texture Since we react to them in sequence, they // are going to be dropped again at queue submit // // We don't need to add MemoryInitKind::NeedsInitializedMemory to // init_actions if a surface is part of the discard list. But that would // mean splitting up the action which is more than we'd win here. self.init_actions.extend( action .texture .initialization_status .read() .check_action(action), ); // We expect very few discarded surfaces at any point in time which is // why a simple linear search is likely best. (i.e. most of the time // self.discards is empty!) let init_actions = &mut self.init_actions; self.discards.retain(|discarded_surface| { if discarded_surface.texture.is_equal(&action.texture) && action.range.layer_range.contains(&discarded_surface.layer) && action .range .mip_range .contains(&discarded_surface.mip_level) { if let MemoryInitKind::NeedsInitializedMemory = action.kind { immediately_necessary_clears.push(discarded_surface.clone()); // Mark surface as implicitly initialized (this is relevant // because it might have been uninitialized prior to // discarding init_actions.push(TextureInitTrackerAction { texture: discarded_surface.texture.clone(), range: TextureInitRange { mip_range: discarded_surface.mip_level ..(discarded_surface.mip_level + 1), layer_range: discarded_surface.layer..(discarded_surface.layer + 1), }, kind: MemoryInitKind::ImplicitlyInitialized, }); } false } else { true } }); immediately_necessary_clears } // Shortcut for register_init_action when it is known that the action is an // implicit init, not requiring any immediate resource init. pub(crate) fn register_implicit_init( &mut self, texture: &Arc, range: TextureInitRange, ) { let must_be_empty = self.register_init_action(&TextureInitTrackerAction { texture: texture.clone(), range, kind: MemoryInitKind::ImplicitlyInitialized, }); assert!(must_be_empty.is_empty()); } } // Utility function that takes discarded surfaces from (several calls to) // register_init_action and initializes them on the spot. // // Takes care of barriers as well! pub(crate) fn fixup_discarded_surfaces>( inits: InitIter, encoder: &mut dyn hal::DynCommandEncoder, texture_tracker: &mut TextureTracker, device: &Device, snatch_guard: &SnatchGuard<'_>, ) { for init in inits { clear_texture( &init.texture, TextureInitRange { mip_range: init.mip_level..(init.mip_level + 1), layer_range: init.layer..(init.layer + 1), }, encoder, texture_tracker, &device.alignments, device.zero_buffer.as_ref(), snatch_guard, device.instance_flags, ) .unwrap(); } } impl BakedCommands { // inserts all buffer initializations that are going to be needed for // executing the commands and updates resource init states accordingly pub(crate) fn initialize_buffer_memory( &mut self, device_tracker: &mut DeviceTracker, snatch_guard: &SnatchGuard<'_>, ) -> Result<(), DestroyedResourceError> { profiling::scope!("initialize_buffer_memory"); // Gather init ranges for each buffer so we can collapse them. // It is not possible to do this at an earlier point since previously // executed command buffer change the resource init state. let mut uninitialized_ranges_per_buffer = FastHashMap::default(); for buffer_use in self.buffer_memory_init_actions.drain(..) { let mut initialization_status = buffer_use.buffer.initialization_status.write(); // align the end to 4 let end_remainder = buffer_use.range.end % wgt::COPY_BUFFER_ALIGNMENT; let end = if end_remainder == 0 { buffer_use.range.end } else { buffer_use.range.end + wgt::COPY_BUFFER_ALIGNMENT - end_remainder }; let uninitialized_ranges = initialization_status.drain(buffer_use.range.start..end); match buffer_use.kind { MemoryInitKind::ImplicitlyInitialized => {} MemoryInitKind::NeedsInitializedMemory => { match uninitialized_ranges_per_buffer.entry(buffer_use.buffer.tracker_index()) { Entry::Vacant(e) => { e.insert(( buffer_use.buffer.clone(), uninitialized_ranges.collect::>>(), )); } Entry::Occupied(mut e) => { e.get_mut().1.extend(uninitialized_ranges); } } } } } for (buffer, mut ranges) in uninitialized_ranges_per_buffer.into_values() { // Collapse touching ranges. ranges.sort_by_key(|r| r.start); for i in (1..ranges.len()).rev() { // The memory init tracker made sure of this! assert!(ranges[i - 1].end <= ranges[i].start); if ranges[i].start == ranges[i - 1].end { ranges[i - 1].end = ranges[i].end; ranges.swap_remove(i); // Ordering not important at this point } } // Don't do use_replace since the buffer may already no longer have // a ref_count. // // However, we *know* that it is currently in use, so the tracker // must already know about it. let transition = device_tracker .buffers .set_single(&buffer, wgt::BufferUses::COPY_DST); let raw_buf = buffer.try_raw(snatch_guard)?; unsafe { self.encoder.raw.transition_buffers( transition .map(|pending| pending.into_hal(&buffer, snatch_guard)) .as_slice(), ); } for range in ranges.iter() { assert!( range.start % wgt::COPY_BUFFER_ALIGNMENT == 0, "Buffer {:?} has an uninitialized range with a start \ not aligned to 4 (start was {})", raw_buf, range.start ); assert!( range.end % wgt::COPY_BUFFER_ALIGNMENT == 0, "Buffer {:?} has an uninitialized range with an end \ not aligned to 4 (end was {})", raw_buf, range.end ); unsafe { self.encoder.raw.clear_buffer(raw_buf, range.clone()); } } } Ok(()) } // inserts all texture initializations that are going to be needed for // executing the commands and updates resource init states accordingly any // textures that are left discarded by this command buffer will be marked as // uninitialized pub(crate) fn initialize_texture_memory( &mut self, device_tracker: &mut DeviceTracker, device: &Device, snatch_guard: &SnatchGuard<'_>, ) -> Result<(), DestroyedResourceError> { profiling::scope!("initialize_texture_memory"); let mut ranges: Vec = Vec::new(); for texture_use in self.texture_memory_actions.drain_init_actions() { let mut initialization_status = texture_use.texture.initialization_status.write(); let use_range = texture_use.range; let affected_mip_trackers = initialization_status .mips .iter_mut() .enumerate() .skip(use_range.mip_range.start as usize) .take((use_range.mip_range.end - use_range.mip_range.start) as usize); match texture_use.kind { MemoryInitKind::ImplicitlyInitialized => { for (_, mip_tracker) in affected_mip_trackers { mip_tracker.drain(use_range.layer_range.clone()); } } MemoryInitKind::NeedsInitializedMemory => { for (mip_level, mip_tracker) in affected_mip_trackers { for layer_range in mip_tracker.drain(use_range.layer_range.clone()) { ranges.push(TextureInitRange { mip_range: (mip_level as u32)..(mip_level as u32 + 1), layer_range, }); } } } } // TODO: Could we attempt some range collapsing here? for range in ranges.drain(..) { let clear_result = clear_texture( &texture_use.texture, range, self.encoder.raw.as_mut(), &mut device_tracker.textures, &device.alignments, device.zero_buffer.as_ref(), snatch_guard, device.instance_flags, ); // A Texture can be destroyed between the command recording // and now, this is out of our control so we have to handle // it gracefully. if let Err(ClearError::DestroyedResource(e)) = clear_result { return Err(e); } // Other errors are unexpected. if let Err(error) = clear_result { panic!("{error}"); } } } // Now that all buffers/textures have the proper init state for before // cmdbuf start, we discard init states for textures it left discarded // after its execution. for surface_discard in self.texture_memory_actions.discards.iter() { surface_discard .texture .initialization_status .write() .discard(surface_discard.mip_level, surface_discard.layer); } Ok(()) } } ================================================ FILE: wgpu-core/src/command/mod.rs ================================================ //! # Command Encoding //! //! TODO: High-level description of command encoding. //! //! The convention in this module is that functions accepting a [`&mut dyn //! hal::DynCommandEncoder`] are low-level helpers and may assume the encoder is //! in the open state, ready to encode commands. Encoders that are not open //! should be nested within some other container that provides additional //! state tracking, like [`InnerCommandEncoder`]. mod allocator; mod bind; mod bundle; mod clear; mod compute; mod compute_command; mod draw; mod encoder; mod encoder_command; pub mod ffi; mod memory_init; mod pass; mod query; mod ray_tracing; mod render; mod render_command; mod timestamp_writes; mod transfer; mod transition_resources; use alloc::{borrow::ToOwned as _, boxed::Box, string::String, sync::Arc, vec::Vec}; use core::convert::Infallible; use core::mem::{self, ManuallyDrop}; use core::{ops, panic}; #[cfg(feature = "serde")] pub(crate) use self::encoder_command::serde_object_reference_struct; #[cfg(any(feature = "trace", feature = "replay"))] #[doc(hidden)] pub use self::encoder_command::PointerReferences; // This module previously did `pub use *` for some of the submodules. When that // was removed, every type that was previously public via `use *` was listed // here. Some types (in particular `CopySide`) may be exported unnecessarily. pub use self::{ bundle::{ bundle_ffi, CreateRenderBundleError, ExecutionError, RenderBundle, RenderBundleDescriptor, RenderBundleEncoder, RenderBundleEncoderDescriptor, RenderBundleError, RenderBundleErrorInner, }, clear::ClearError, compute::{ ComputeBasePass, ComputePass, ComputePassDescriptor, ComputePassError, ComputePassErrorInner, DispatchError, }, compute_command::ArcComputeCommand, draw::{DrawError, Rect, RenderCommandError}, encoder_command::{ArcCommand, ArcReferences, Command, IdReferences, ReferenceType}, query::{QueryError, QueryUseError, ResolveError, SimplifiedQueryType}, render::{ ArcRenderPassColorAttachment, AttachmentError, AttachmentErrorLocation, ColorAttachmentError, ColorAttachments, LoadOp, PassChannel, RenderBasePass, RenderPass, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPassError, RenderPassErrorInner, ResolvedPassChannel, ResolvedRenderPassDepthStencilAttachment, StoreOp, }, render_command::ArcRenderCommand, transfer::{CopySide, TransferError}, transition_resources::TransitionResourcesError, }; pub(crate) use self::{ clear::clear_texture, encoder::EncodingState, memory_init::CommandBufferTextureMemoryActions, render::{get_stride_of_indirect_args, VertexLimits}, transfer::{ extract_texture_selector, validate_linear_texture_data, validate_texture_buffer_copy, validate_texture_copy_dst_format, validate_texture_copy_range, }, }; pub(crate) use allocator::CommandAllocator; /// cbindgen:ignore pub use self::{compute_command::ComputeCommand, render_command::RenderCommand}; pub(crate) use timestamp_writes::ArcPassTimestampWrites; pub use timestamp_writes::PassTimestampWrites; use crate::binding_model::BindingError; use crate::device::queue::TempResource; use crate::device::{Device, DeviceError, MissingFeatures}; use crate::id::Id; use crate::lock::{rank, Mutex}; use crate::snatch::SnatchGuard; use crate::init_tracker::BufferInitTrackerAction; use crate::ray_tracing::{AsAction, BuildAccelerationStructureError}; use crate::resource::{ DestroyedResourceError, Fallible, InvalidResourceError, Labeled, ParentDevice as _, QuerySet, }; use crate::storage::Storage; use crate::track::{DeviceTracker, ResourceUsageCompatibilityError, Tracker, UsageScope}; use crate::{api_log, global::Global, id, resource_log, Label}; use crate::{hal_label, LabelHelpers}; use wgt::error::{ErrorType, WebGpuError}; use thiserror::Error; /// cbindgen:ignore pub type TexelCopyBufferInfo = ffi::TexelCopyBufferInfo; /// cbindgen:ignore pub type TexelCopyTextureInfo = ffi::TexelCopyTextureInfo; /// cbindgen:ignore pub type CopyExternalImageDestInfo = ffi::CopyExternalImageDestInfo; const IMMEDIATES_CLEAR_ARRAY: &[u32] = &[0_u32; 64]; pub(crate) struct EncoderErrorState { error: CommandEncoderError, #[cfg(feature = "trace")] trace_commands: Option>>, } /// Construct an `EncoderErrorState` with only a `CommandEncoderError` (without /// any traced commands). /// /// This is used in cases where pass begin/end were mismatched, if the same /// encoder was finished multiple times, or in the status of a command buffer /// (in which case the commands were already saved to the trace). In some of /// these cases there may be commands that could be saved to the trace, but if /// the application is that confused about using encoders, it's not clear /// whether it's worth the effort to try and preserve the commands. fn make_error_state>(error: E) -> CommandEncoderStatus { CommandEncoderStatus::Error(EncoderErrorState { error: error.into(), #[cfg(feature = "trace")] trace_commands: None, }) } /// The current state of a command or pass encoder. /// /// In the WebGPU spec, the state of an encoder (open, locked, or ended) is /// orthogonal to the validity of the encoder. However, this enum does not /// represent the state of an invalid encoder. pub(crate) enum CommandEncoderStatus { /// Ready to record commands. An encoder's initial state. /// /// Command building methods like [`command_encoder_clear_buffer`] and /// [`compute_pass_end`] require the encoder to be in this /// state. /// /// This corresponds to WebGPU's "open" state. /// See /// /// [`command_encoder_clear_buffer`]: Global::command_encoder_clear_buffer /// [`compute_pass_end`]: Global::compute_pass_end Recording(CommandBufferMutable), /// Locked by a render or compute pass. /// /// This state is entered when a render/compute pass is created, /// and exited when the pass is ended. /// /// As long as the command encoder is locked, any command building operation /// on it will fail and put the encoder into the [`Self::Error`] state. See /// Locked(CommandBufferMutable), Consumed, /// Command recording is complete, and the buffer is ready for submission. /// /// [`Global::command_encoder_finish`] transitions a /// `CommandBuffer` from the `Recording` state into this state. /// /// [`Global::queue_submit`] requires that command buffers are /// in this state. /// /// This corresponds to WebGPU's "ended" state. /// See Finished(CommandBufferMutable), /// The command encoder is invalid. /// /// The error that caused the invalidation is stored here, and will /// be raised by `CommandEncoder.finish()`. Error(EncoderErrorState), /// Temporary state used internally by methods on `CommandEncoderStatus`. /// Encoder should never be left in this state. Transitioning, } impl CommandEncoderStatus { #[doc(hidden)] fn replay(&mut self, commands: Vec>) { let Self::Recording(cmd_buf_data) = self else { panic!("encoder should be in the recording state"); }; cmd_buf_data.commands.extend(commands); } /// Push a command provided by a closure onto the encoder. /// /// If the encoder is in the [`Self::Recording`] state, calls the closure to /// obtain a command, and pushes it onto the encoder. If the closure returns /// an error, stores that error in the encoder for later reporting when /// `finish()` is called. Returns `Ok(())` even if the closure returned an /// error. /// /// If the encoder is not in the [`Self::Recording`] state, the closure will /// not be called and nothing will be recorded. The encoder will be /// invalidated (if it is not already). If the error is a [validation error /// that should be raised immediately][ves], returns it in `Err`, otherwise, /// returns `Ok(())`. /// /// [ves]: https://www.w3.org/TR/webgpu/#abstract-opdef-validate-the-encoder-state fn push_with Result, E: Clone + Into>( &mut self, f: F, ) -> Result<(), EncoderStateError> { match self { Self::Recording(cmd_buf_data) => { cmd_buf_data.encoder.api.set(EncodingApi::Wgpu); match f() { Ok(cmd) => cmd_buf_data.commands.push(cmd), Err(err) => { self.invalidate(err); } } Ok(()) } Self::Locked(_) => { // Invalidate the encoder and do not record anything, but do not // return an immediate validation error. self.invalidate(EncoderStateError::Locked); Ok(()) } // Encoder is ended. Invalidate the encoder, do not record anything, // and return an immediate validation error. Self::Finished(_) => Err(self.invalidate(EncoderStateError::Ended)), Self::Consumed => Err(EncoderStateError::Ended), // Encoder is already invalid. Do not record anything, but do not // return an immediate validation error. Self::Error(_) => Ok(()), Self::Transitioning => unreachable!(), } } /// Call a closure with the inner command buffer structure. /// /// If the encoder is in the [`Self::Recording`] state, calls the provided /// closure. If the closure returns an error, stores that error in the /// encoder for later reporting when `finish()` is called. Returns `Ok(())` /// even if the closure returned an error. /// /// If the encoder is not in the [`Self::Recording`] state, the closure will /// not be called. The encoder will be invalidated (if it is not already). /// If the error is a [validation error that should be raised /// immediately][ves], returns it in `Err`, otherwise, returns `Ok(())`. /// /// [ves]: https://www.w3.org/TR/webgpu/#abstract-opdef-validate-the-encoder-state fn with_buffer< F: FnOnce(&mut CommandBufferMutable) -> Result<(), E>, E: Clone + Into, >( &mut self, api: EncodingApi, f: F, ) -> Result<(), EncoderStateError> { match self { Self::Recording(inner) => { inner.encoder.api.set(api); RecordingGuard { inner: self }.record(f); Ok(()) } Self::Locked(_) => { // Invalidate the encoder and do not record anything, but do not // return an immediate validation error. self.invalidate(EncoderStateError::Locked); Ok(()) } // Encoder is ended. Invalidate the encoder, do not record anything, // and return an immediate validation error. Self::Finished(_) => Err(self.invalidate(EncoderStateError::Ended)), Self::Consumed => Err(EncoderStateError::Ended), // Encoder is already invalid. Do not record anything, but do not // return an immediate validation error. Self::Error(_) => Ok(()), Self::Transitioning => unreachable!(), } } /// Special version of record used by `command_encoder_as_hal_mut`. This /// differs from the regular version in two ways: /// /// 1. The recording closure is infallible. /// 2. The recording closure takes `Option<&mut CommandBufferMutable>`, and /// in the case that the encoder is not in a valid state for recording, the /// closure is still called, with `None` as its argument. pub(crate) fn record_as_hal_mut) -> T>( &mut self, f: F, ) -> T { match self { Self::Recording(inner) => { inner.encoder.api.set(EncodingApi::Raw); RecordingGuard { inner: self }.record_as_hal_mut(f) } Self::Locked(_) => { self.invalidate(EncoderStateError::Locked); f(None) } Self::Finished(_) => { self.invalidate(EncoderStateError::Ended); f(None) } Self::Consumed => f(None), Self::Error(_) => f(None), Self::Transitioning => unreachable!(), } } /// Locks the encoder by putting it in the [`Self::Locked`] state. /// /// Render or compute passes call this on start. At the end of the pass, /// they call [`Self::unlock_encoder`] to put the [`CommandBuffer`] back /// into the [`Self::Recording`] state. fn lock_encoder(&mut self) -> Result<(), EncoderStateError> { match mem::replace(self, Self::Transitioning) { Self::Recording(inner) => { *self = Self::Locked(inner); Ok(()) } st @ Self::Finished(_) => { // Attempting to open a pass on a finished encoder raises a // validation error but does not invalidate the encoder. This is // related to https://github.com/gpuweb/gpuweb/issues/5207. *self = st; Err(EncoderStateError::Ended) } Self::Locked(_) => Err(self.invalidate(EncoderStateError::Locked)), st @ Self::Consumed => { *self = st; Err(EncoderStateError::Ended) } st @ Self::Error(_) => { *self = st; Err(EncoderStateError::Invalid) } Self::Transitioning => unreachable!(), } } /// Unlocks the encoder and puts it back into the [`Self::Recording`] state. /// /// This function is the unlocking counterpart to [`Self::lock_encoder`]. It /// is only valid to call this function if the encoder is in the /// [`Self::Locked`] state. /// /// If the encoder is in a state other than [`Self::Locked`] and a /// validation error should be raised immediately, returns it in `Err`, /// otherwise, stores the error in the encoder and returns `Ok(())`. fn unlock_encoder(&mut self) -> Result<(), EncoderStateError> { match mem::replace(self, Self::Transitioning) { Self::Locked(inner) => { *self = Self::Recording(inner); Ok(()) } st @ Self::Finished(_) => { *self = st; Err(EncoderStateError::Ended) } Self::Recording(_) => { *self = make_error_state(EncoderStateError::Unlocked); Err(EncoderStateError::Unlocked) } st @ Self::Consumed => { *self = st; Err(EncoderStateError::Ended) } st @ Self::Error(_) => { // Encoder is already invalid. The error will be reported by // `CommandEncoder.finish`. *self = st; Ok(()) } Self::Transitioning => unreachable!(), } } fn finish(&mut self) -> Self { // Replace our state with `Consumed`, and return either the inner // state or an error, to be transferred to the command buffer. match mem::replace(self, Self::Consumed) { Self::Recording(inner) => { // Raw encoding leaves the encoder open in `command_encoder_as_hal_mut`. // Otherwise, nothing should have opened it yet. if inner.encoder.api != EncodingApi::Raw { assert!(!inner.encoder.is_open); } Self::Finished(inner) } Self::Consumed | Self::Finished(_) => make_error_state(EncoderStateError::Ended), Self::Locked(_) => make_error_state(EncoderStateError::Locked), st @ Self::Error(_) => st, Self::Transitioning => unreachable!(), } } /// Invalidate the command encoder due to an error. /// /// The error `err` is stored so that it can be reported when the encoder is /// finished. If tracing is enabled, the traced commands are also stored. /// /// Since we do not track the state of an invalid encoder, it is not /// necessary to unlock an encoder that has been invalidated. fn invalidate>(&mut self, err: E) -> E { #[cfg(feature = "trace")] let trace_commands = match self { Self::Recording(cmd_buf_data) => Some( mem::take(&mut cmd_buf_data.commands) .into_iter() .map(crate::device::trace::IntoTrace::into_trace) .collect(), ), _ => None, }; let enc_err = err.clone().into(); api_log!("Invalidating command encoder: {enc_err:?}"); *self = Self::Error(EncoderErrorState { error: enc_err, #[cfg(feature = "trace")] trace_commands, }); err } } /// A guard to enforce error reporting, for a [`CommandBuffer`] in the [`Recording`] state. /// /// An [`RecordingGuard`] holds a mutable reference to a [`CommandEncoderStatus`] that /// has been verified to be in the [`Recording`] state. The [`RecordingGuard`] dereferences /// mutably to the [`CommandBufferMutable`] that the status holds. /// /// Dropping an [`RecordingGuard`] sets the [`CommandBuffer`]'s state to /// [`CommandEncoderStatus::Error`]. If your use of the guard was /// successful, call its [`mark_successful`] method to dispose of it. /// /// [`Recording`]: CommandEncoderStatus::Recording /// [`mark_successful`]: Self::mark_successful pub(crate) struct RecordingGuard<'a> { inner: &'a mut CommandEncoderStatus, } impl<'a> RecordingGuard<'a> { pub(crate) fn mark_successful(self) { mem::forget(self) } fn record< F: FnOnce(&mut CommandBufferMutable) -> Result<(), E>, E: Clone + Into, >( mut self, f: F, ) { match f(&mut self) { Ok(()) => self.mark_successful(), Err(err) => { self.inner.invalidate(err); } } } /// Special version of record used by `command_encoder_as_hal_mut`. This /// version takes an infallible recording closure. pub(crate) fn record_as_hal_mut) -> T>( mut self, f: F, ) -> T { let res = f(Some(&mut self)); self.mark_successful(); res } } impl<'a> Drop for RecordingGuard<'a> { fn drop(&mut self) { if matches!(*self.inner, CommandEncoderStatus::Error(_)) { // Don't overwrite an error that is already present. return; } self.inner.invalidate(EncoderStateError::Invalid); } } impl<'a> ops::Deref for RecordingGuard<'a> { type Target = CommandBufferMutable; fn deref(&self) -> &Self::Target { match &*self.inner { CommandEncoderStatus::Recording(command_buffer_mutable) => command_buffer_mutable, _ => unreachable!(), } } } impl<'a> ops::DerefMut for RecordingGuard<'a> { fn deref_mut(&mut self) -> &mut Self::Target { match self.inner { CommandEncoderStatus::Recording(command_buffer_mutable) => command_buffer_mutable, _ => unreachable!(), } } } pub(crate) struct CommandEncoder { pub(crate) device: Arc, pub(crate) label: String, /// The mutable state of this command encoder. pub(crate) data: Mutex, } crate::impl_resource_type!(CommandEncoder); crate::impl_labeled!(CommandEncoder); crate::impl_parent_device!(CommandEncoder); crate::impl_storage_item!(CommandEncoder); impl Drop for CommandEncoder { fn drop(&mut self) { resource_log!("Drop {}", self.error_ident()); } } /// The encoding API being used with a `CommandEncoder`. /// /// Mixing APIs on the same encoder is not allowed. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum EncodingApi { // The regular wgpu encoding APIs are being used. Wgpu, // The raw hal encoding API is being used. Raw, // Neither encoding API has been called yet. Undecided, // The encoder is used internally by wgpu. InternalUse, } impl EncodingApi { pub(crate) fn set(&mut self, api: EncodingApi) { match *self { EncodingApi::Undecided => { *self = api; } self_api if self_api != api => { panic!("Mixing the wgpu encoding API with the raw encoding API is not permitted"); } _ => {} } } } /// A raw [`CommandEncoder`][rce], and the raw [`CommandBuffer`][rcb]s built from it. /// /// Each wgpu-core [`CommandBuffer`] owns an instance of this type, which is /// where the commands are actually stored. /// /// This holds a `Vec` of raw [`CommandBuffer`][rcb]s, not just one. We are not /// always able to record commands in the order in which they must ultimately be /// submitted to the queue, but raw command buffers don't permit inserting new /// commands into the middle of a recorded stream. However, hal queue submission /// accepts a series of command buffers at once, so we can simply break the /// stream up into multiple buffers, and then reorder the buffers. See /// [`InnerCommandEncoder::close_and_swap`] for a specific example of this. /// /// [rce]: hal::Api::CommandEncoder /// [rcb]: hal::Api::CommandBuffer pub(crate) struct InnerCommandEncoder { /// The underlying `wgpu_hal` [`CommandEncoder`]. /// /// Successfully executed command buffers' encoders are saved in a /// [`CommandAllocator`] for recycling. /// /// [`CommandEncoder`]: hal::Api::CommandEncoder /// [`CommandAllocator`]: crate::command::CommandAllocator pub(crate) raw: ManuallyDrop>, /// All the raw command buffers for our owning [`CommandBuffer`], in /// submission order. /// /// These command buffers were all constructed with `raw`. The /// [`wgpu_hal::CommandEncoder`] trait forbids these from outliving `raw`, /// and requires that we provide all of these when we call /// [`raw.reset_all()`][CE::ra], so the encoder and its buffers travel /// together. /// /// [CE::ra]: hal::CommandEncoder::reset_all /// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder pub(crate) list: Vec>, pub(crate) device: Arc, /// True if `raw` is in the "recording" state. /// /// See the documentation for [`wgpu_hal::CommandEncoder`] for /// details on the states `raw` can be in. /// /// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder pub(crate) is_open: bool, /// Tracks which API is being used to encode commands. /// /// Mixing the wgpu encoding API with access to the raw hal encoder via /// `as_hal_mut` is not supported. this field tracks which API is being used /// in order to detect and reject invalid usage. pub(crate) api: EncodingApi, pub(crate) label: String, } impl InnerCommandEncoder { /// Finish the current command buffer and insert it just before /// the last element in [`self.list`][l]. /// /// On return, the underlying hal encoder is closed. /// /// What is this for? /// /// The `wgpu_hal` contract requires that each render or compute pass's /// commands be preceded by calls to [`transition_buffers`] and /// [`transition_textures`], to put the resources the pass operates on in /// the appropriate state. Unfortunately, we don't know which transitions /// are needed until we're done recording the pass itself. Rather than /// iterating over the pass twice, we note the necessary transitions as we /// record its commands, finish the raw command buffer for the actual pass, /// record a new raw command buffer for the transitions, and jam that buffer /// in just before the pass's. This is the function that jams in the /// transitions' command buffer. /// /// # Panics /// /// - If the encoder is not open. /// /// [l]: InnerCommandEncoder::list /// [`transition_buffers`]: hal::CommandEncoder::transition_buffers /// [`transition_textures`]: hal::CommandEncoder::transition_textures fn close_and_swap(&mut self) -> Result<(), DeviceError> { assert!(self.is_open); self.is_open = false; let new = unsafe { self.raw.end_encoding() }.map_err(|e| self.device.handle_hal_error(e))?; self.list.insert(self.list.len() - 1, new); Ok(()) } /// Finish the current command buffer and insert it at the beginning /// of [`self.list`][l]. /// /// On return, the underlying hal encoder is closed. /// /// # Panics /// /// - If the encoder is not open. /// /// [l]: InnerCommandEncoder::list pub(crate) fn close_and_push_front(&mut self) -> Result<(), DeviceError> { assert!(self.is_open); self.is_open = false; let new = unsafe { self.raw.end_encoding() }.map_err(|e| self.device.handle_hal_error(e))?; self.list.insert(0, new); Ok(()) } /// Finish the current command buffer, and push it onto /// the end of [`self.list`][l]. /// /// On return, the underlying hal encoder is closed. /// /// # Panics /// /// - If the encoder is not open. /// /// [l]: InnerCommandEncoder::list pub(crate) fn close(&mut self) -> Result<(), DeviceError> { assert!(self.is_open); self.is_open = false; let cmd_buf = unsafe { self.raw.end_encoding() }.map_err(|e| self.device.handle_hal_error(e))?; self.list.push(cmd_buf); Ok(()) } /// Finish the current command buffer, if any, and add it to the /// end of [`self.list`][l]. /// /// If we have opened this command encoder, finish its current /// command buffer, and push it onto the end of [`self.list`][l]. /// If this command buffer is closed, do nothing. /// /// On return, the underlying hal encoder is closed. /// /// [l]: InnerCommandEncoder::list fn close_if_open(&mut self) -> Result<(), DeviceError> { if self.is_open { self.is_open = false; let cmd_buf = unsafe { self.raw.end_encoding() }.map_err(|e| self.device.handle_hal_error(e))?; self.list.push(cmd_buf); } Ok(()) } /// If the command encoder is not open, begin recording a new command buffer. /// /// If the command encoder was already open, does nothing. /// /// In both cases, returns a reference to the raw encoder. fn open_if_closed(&mut self) -> Result<&mut dyn hal::DynCommandEncoder, DeviceError> { if !self.is_open { let hal_label = hal_label(Some(self.label.as_str()), self.device.instance_flags); unsafe { self.raw.begin_encoding(hal_label) } .map_err(|e| self.device.handle_hal_error(e))?; self.is_open = true; } Ok(self.raw.as_mut()) } /// Begin recording a new command buffer, if we haven't already. /// /// The underlying hal encoder is put in the "recording" state. pub(crate) fn open(&mut self) -> Result<&mut dyn hal::DynCommandEncoder, DeviceError> { if !self.is_open { let hal_label = hal_label(Some(self.label.as_str()), self.device.instance_flags); unsafe { self.raw.begin_encoding(hal_label) } .map_err(|e| self.device.handle_hal_error(e))?; self.is_open = true; } Ok(self.raw.as_mut()) } /// Begin recording a new command buffer for a render or compute pass, with /// its own label. /// /// The underlying hal encoder is put in the "recording" state. /// /// # Panics /// /// - If the encoder is already open. pub(crate) fn open_pass( &mut self, label: Option<&str>, ) -> Result<&mut dyn hal::DynCommandEncoder, DeviceError> { assert!(!self.is_open); let hal_label = hal_label(label, self.device.instance_flags); unsafe { self.raw.begin_encoding(hal_label) } .map_err(|e| self.device.handle_hal_error(e))?; self.is_open = true; Ok(self.raw.as_mut()) } } impl Drop for InnerCommandEncoder { fn drop(&mut self) { if self.is_open { unsafe { self.raw.discard_encoding() }; } unsafe { self.raw.reset_all(mem::take(&mut self.list)); } // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; self.device.command_allocator.release_encoder(raw); } } /// Look at the documentation for [`CommandBufferMutable`] for an explanation of /// the fields in this struct. This is the "built" counterpart to that type. pub(crate) struct BakedCommands { pub(crate) encoder: InnerCommandEncoder, pub(crate) trackers: Tracker, pub(crate) temp_resources: Vec, pub(crate) indirect_draw_validation_resources: crate::indirect_validation::DrawResources, buffer_memory_init_actions: Vec, texture_memory_actions: CommandBufferTextureMemoryActions, } /// The mutable state of a [`CommandBuffer`]. pub struct CommandBufferMutable { /// The [`wgpu_hal::Api::CommandBuffer`]s we've built so far, and the encoder /// they belong to. /// /// [`wgpu_hal::Api::CommandBuffer`]: hal::Api::CommandBuffer pub(crate) encoder: InnerCommandEncoder, /// All the resources that the commands recorded so far have referred to. pub(crate) trackers: Tracker, /// The regions of buffers and textures these commands will read and write. /// /// This is used to determine which portions of which /// buffers/textures we actually need to initialize. If we're /// definitely going to write to something before we read from it, /// we don't need to clear its contents. buffer_memory_init_actions: Vec, texture_memory_actions: CommandBufferTextureMemoryActions, as_actions: Vec, temp_resources: Vec, indirect_draw_validation_resources: crate::indirect_validation::DrawResources, pub(crate) commands: Vec>, /// If tracing, `command_encoder_finish` replaces the `Arc`s in `commands` /// with integer pointers, and moves them into `trace_commands`. #[cfg(feature = "trace")] pub(crate) trace_commands: Option>>, } impl CommandBufferMutable { pub(crate) fn into_baked_commands(self) -> BakedCommands { BakedCommands { encoder: self.encoder, trackers: self.trackers, temp_resources: self.temp_resources, indirect_draw_validation_resources: self.indirect_draw_validation_resources, buffer_memory_init_actions: self.buffer_memory_init_actions, texture_memory_actions: self.texture_memory_actions, } } } /// A buffer of commands to be submitted to the GPU for execution. /// /// Once a command buffer is submitted to the queue, its contents are taken /// to construct a [`BakedCommands`], whose contents eventually become the /// property of the submission queue. pub struct CommandBuffer { pub(crate) device: Arc, /// The `label` from the descriptor used to create the resource. label: String, /// The mutable state of this command buffer. pub(crate) data: Mutex, } impl Drop for CommandBuffer { fn drop(&mut self) { resource_log!("Drop {}", self.error_ident()); } } impl CommandEncoder { pub(crate) fn new( encoder: Box, device: &Arc, label: &Label, ) -> Self { CommandEncoder { device: device.clone(), label: label.to_string(), data: Mutex::new( rank::COMMAND_BUFFER_DATA, CommandEncoderStatus::Recording(CommandBufferMutable { encoder: InnerCommandEncoder { raw: ManuallyDrop::new(encoder), list: Vec::new(), device: device.clone(), is_open: false, api: EncodingApi::Undecided, label: label.to_string(), }, trackers: Tracker::new( device.ordered_buffer_usages, device.ordered_texture_usages, ), buffer_memory_init_actions: Default::default(), texture_memory_actions: Default::default(), as_actions: Default::default(), temp_resources: Default::default(), indirect_draw_validation_resources: crate::indirect_validation::DrawResources::new(device.clone()), commands: Vec::new(), #[cfg(feature = "trace")] trace_commands: if device.trace.lock().is_some() { Some(Vec::new()) } else { None }, }), ), } } pub(crate) fn new_invalid( device: &Arc, label: &Label, err: CommandEncoderError, ) -> Self { CommandEncoder { device: device.clone(), label: label.to_string(), data: Mutex::new(rank::COMMAND_BUFFER_DATA, make_error_state(err)), } } pub(crate) fn insert_barriers_from_tracker( raw: &mut dyn hal::DynCommandEncoder, base: &mut Tracker, head: &Tracker, snatch_guard: &SnatchGuard, ) { profiling::scope!("insert_barriers"); base.buffers.set_from_tracker(&head.buffers); base.textures.set_from_tracker(&head.textures); Self::drain_barriers(raw, base, snatch_guard); } pub(crate) fn insert_barriers_from_scope( raw: &mut dyn hal::DynCommandEncoder, base: &mut Tracker, head: &UsageScope, snatch_guard: &SnatchGuard, ) { profiling::scope!("insert_barriers"); base.buffers.set_from_usage_scope(&head.buffers); base.textures.set_from_usage_scope(&head.textures); Self::drain_barriers(raw, base, snatch_guard); } pub(crate) fn drain_barriers( raw: &mut dyn hal::DynCommandEncoder, base: &mut Tracker, snatch_guard: &SnatchGuard, ) { profiling::scope!("drain_barriers"); let buffer_barriers = base .buffers .drain_transitions(snatch_guard) .collect::>(); let (transitions, textures) = base.textures.drain_transitions(snatch_guard); let texture_barriers = transitions .into_iter() .enumerate() .map(|(i, p)| p.into_hal(textures[i].unwrap().raw())) .collect::>(); unsafe { raw.transition_buffers(&buffer_barriers); raw.transition_textures(&texture_barriers); } } pub(crate) fn insert_barriers_from_device_tracker( raw: &mut dyn hal::DynCommandEncoder, base: &mut DeviceTracker, head: &Tracker, snatch_guard: &SnatchGuard, ) { profiling::scope!("insert_barriers_from_device_tracker"); let buffer_barriers = base .buffers .set_from_tracker_and_drain_transitions(&head.buffers, snatch_guard) .collect::>(); let texture_barriers = base .textures .set_from_tracker_and_drain_transitions(&head.textures, snatch_guard) .collect::>(); unsafe { raw.transition_buffers(&buffer_barriers); raw.transition_textures(&texture_barriers); } } fn encode_commands( device: &Arc, cmd_buf_data: &mut CommandBufferMutable, ) -> Result<(), CommandEncoderError> { device.check_is_valid()?; let snatch_guard = device.snatchable_lock.read(); let mut debug_scope_depth = 0; if cmd_buf_data.encoder.api == EncodingApi::Raw { // Should have panicked on the first call that switched APIs, // but lets be sure. assert!(cmd_buf_data.commands.is_empty()); } let commands = mem::take(&mut cmd_buf_data.commands); #[cfg(feature = "trace")] if device.trace.lock().is_some() { cmd_buf_data.trace_commands = Some( commands .iter() .map(crate::device::trace::IntoTrace::to_trace) .collect(), ); } for command in commands { if matches!( command, ArcCommand::RunRenderPass { .. } | ArcCommand::RunComputePass { .. } ) { // Compute passes and render passes can accept either an // open or closed encoder. This state object holds an // `InnerCommandEncoder`. See the documentation of // [`EncodingState`]. let mut state = EncodingState { device, raw_encoder: &mut cmd_buf_data.encoder, tracker: &mut cmd_buf_data.trackers, buffer_memory_init_actions: &mut cmd_buf_data.buffer_memory_init_actions, texture_memory_actions: &mut cmd_buf_data.texture_memory_actions, as_actions: &mut cmd_buf_data.as_actions, temp_resources: &mut cmd_buf_data.temp_resources, indirect_draw_validation_resources: &mut cmd_buf_data .indirect_draw_validation_resources, snatch_guard: &snatch_guard, debug_scope_depth: &mut debug_scope_depth, }; match command { ArcCommand::RunRenderPass { pass, color_attachments, depth_stencil_attachment, timestamp_writes, occlusion_query_set, multiview_mask, } => { api_log!( "Begin encoding render pass with '{}' label", pass.label.as_deref().unwrap_or("") ); let res = render::encode_render_pass( &mut state, pass, color_attachments, depth_stencil_attachment, timestamp_writes, occlusion_query_set, multiview_mask, ); match res.as_ref() { Err(err) => { api_log!("Finished encoding render pass ({err:?})") } Ok(_) => { api_log!("Finished encoding render pass (success)") } } res?; } ArcCommand::RunComputePass { pass, timestamp_writes, } => { api_log!( "Begin encoding compute pass with '{}' label", pass.label.as_deref().unwrap_or("") ); let res = compute::encode_compute_pass(&mut state, pass, timestamp_writes); match res.as_ref() { Err(err) => { api_log!("Finished encoding compute pass ({err:?})") } Ok(_) => { api_log!("Finished encoding compute pass (success)") } } res?; } _ => unreachable!(), } } else { // All the other non-pass encoding routines assume the // encoder is open, so open it if necessary. This state // object holds an `&mut dyn hal::DynCommandEncoder`. By // convention, a bare HAL encoder reference in // [`EncodingState`] must always be an open encoder. let raw_encoder = cmd_buf_data.encoder.open_if_closed()?; let mut state = EncodingState { device, raw_encoder, tracker: &mut cmd_buf_data.trackers, buffer_memory_init_actions: &mut cmd_buf_data.buffer_memory_init_actions, texture_memory_actions: &mut cmd_buf_data.texture_memory_actions, as_actions: &mut cmd_buf_data.as_actions, temp_resources: &mut cmd_buf_data.temp_resources, indirect_draw_validation_resources: &mut cmd_buf_data .indirect_draw_validation_resources, snatch_guard: &snatch_guard, debug_scope_depth: &mut debug_scope_depth, }; match command { ArcCommand::CopyBufferToBuffer { src, src_offset, dst, dst_offset, size, } => { transfer::copy_buffer_to_buffer( &mut state, &src, src_offset, &dst, dst_offset, size, )?; } ArcCommand::CopyBufferToTexture { src, dst, size } => { transfer::copy_buffer_to_texture(&mut state, &src, &dst, &size)?; } ArcCommand::CopyTextureToBuffer { src, dst, size } => { transfer::copy_texture_to_buffer(&mut state, &src, &dst, &size)?; } ArcCommand::CopyTextureToTexture { src, dst, size } => { transfer::copy_texture_to_texture(&mut state, &src, &dst, &size)?; } ArcCommand::ClearBuffer { dst, offset, size } => { clear::clear_buffer(&mut state, dst, offset, size)?; } ArcCommand::ClearTexture { dst, subresource_range, } => { clear::clear_texture_cmd(&mut state, dst, &subresource_range)?; } ArcCommand::WriteTimestamp { query_set, query_index, } => { query::write_timestamp(&mut state, query_set, query_index)?; } ArcCommand::ResolveQuerySet { query_set, start_query, query_count, destination, destination_offset, } => { query::resolve_query_set( &mut state, query_set, start_query, query_count, destination, destination_offset, )?; } ArcCommand::PushDebugGroup(label) => { push_debug_group(&mut state, &label)?; } ArcCommand::PopDebugGroup => { pop_debug_group(&mut state)?; } ArcCommand::InsertDebugMarker(label) => { insert_debug_marker(&mut state, &label)?; } ArcCommand::BuildAccelerationStructures { blas, tlas } => { ray_tracing::build_acceleration_structures(&mut state, blas, tlas)?; } ArcCommand::TransitionResources { buffer_transitions, texture_transitions, } => { transition_resources::transition_resources( &mut state, buffer_transitions, texture_transitions, )?; } ArcCommand::RunComputePass { .. } | ArcCommand::RunRenderPass { .. } => { unreachable!() } } } } if debug_scope_depth > 0 { Err(CommandEncoderError::DebugGroupError( DebugGroupError::MissingPop, ))?; } // Close the encoder, unless it was closed already by a render or compute pass. cmd_buf_data.encoder.close_if_open()?; // Note: if we want to stop tracking the swapchain texture view, // this is the place to do it. Ok(()) } fn finish( self: &Arc, desc: &wgt::CommandBufferDescriptor(name.to_owned(), hal_instance), surfaces: Registry::new(), hub: Hub::new(), } } /// # Safety /// /// - The raw instance handle returned must not be manually destroyed. pub unsafe fn instance_as_hal(&self) -> Option<&A::Instance> { unsafe { self.instance.as_hal::() } } /// # Safety /// /// - The raw handles obtained from the Instance must not be manually destroyed pub unsafe fn from_instance(instance: Instance) -> Self { profiling::scope!("Global::new"); Self { instance, surfaces: Registry::new(), hub: Hub::new(), } } pub fn generate_report(&self) -> GlobalReport { GlobalReport { surfaces: self.surfaces.generate_report(), hub: self.hub.generate_report(), } } } impl fmt::Debug for Global { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Global").finish() } } impl Drop for Global { fn drop(&mut self) { profiling::scope!("Global::drop"); resource_log!("Global::drop"); } } #[cfg(send_sync)] fn _test_send_sync(global: &Global) { fn test_internal(_: T) {} test_internal(global) } ================================================ FILE: wgpu-core/src/hash_utils.rs ================================================ //! Module for hashing utilities. //! //! Named hash_utils to prevent clashing with the core::hash module. /// HashMap using a fast, non-cryptographic hash algorithm. pub type FastHashMap = hashbrown::HashMap>; /// HashSet using a fast, non-cryptographic hash algorithm. pub type FastHashSet = hashbrown::HashSet>; /// IndexMap using a fast, non-cryptographic hash algorithm. pub type FastIndexMap = indexmap::IndexMap>; ================================================ FILE: wgpu-core/src/hub.rs ================================================ /*! Allocating resource ids, and tracking the resources they refer to. The `wgpu_core` API uses identifiers of type [`Id`] to refer to resources of type `R`. For example, [`id::DeviceId`] is an alias for `Id`, and [`id::BufferId`] is an alias for `Id`. `Id` implements `Copy`, `Hash`, `Eq`, `Ord`, and of course `Debug`. [`id::DeviceId`]: crate::id::DeviceId [`id::BufferId`]: crate::id::BufferId Each `Id` contains not only an index for the resource it denotes but also a Backend indicating which `wgpu` backend it belongs to. `Id`s also incorporate a generation number, for additional validation. The resources to which identifiers refer are freed explicitly. Attempting to use an identifier for a resource that has been freed elicits an error result. Eventually, we would like to remove numeric IDs from wgpu-core. See . ## Assigning ids to resources The users of `wgpu_core` generally want resource ids to be assigned in one of two ways: - Users like `wgpu` want `wgpu_core` to assign ids to resources itself. For example, `wgpu` expects to call `Global::device_create_buffer` and have the return value indicate the newly created buffer's id. - Users like Firefox want to allocate ids themselves, and pass `Global::device_create_buffer` and friends the id to assign the new resource. To accommodate either pattern, `wgpu_core` methods that create resources all expect an `id_in` argument that the caller can use to specify the id, and they all return the id used. For example, the declaration of `Global::device_create_buffer` looks like this: ```ignore impl Global { /* ... */ pub fn device_create_buffer( &self, device_id: id::DeviceId, desc: &resource::BufferDescriptor, id_in: Input, ) -> (id::BufferId, Option) { /* ... */ } /* ... */ } ``` Users that want to assign resource ids themselves pass in the id they want as the `id_in` argument, whereas users that want `wgpu_core` itself to choose ids always pass `()`. In either case, the id ultimately assigned is returned as the first element of the tuple. Producing true identifiers from `id_in` values is the job of an [`crate::identity::IdentityManager`] or ids will be received from outside through `Option` arguments. ## Id allocation and streaming Perhaps surprisingly, allowing users to assign resource ids themselves enables major performance improvements in some applications. The `wgpu_core` API is designed for use by Firefox's [WebGPU] implementation. For security, web content and GPU use must be kept segregated in separate processes, with all interaction between them mediated by an inter-process communication protocol. As web content uses the WebGPU API, the content process sends messages to the GPU process, which interacts with the platform's GPU APIs on content's behalf, occasionally sending results back. In a classic Rust API, a resource allocation function takes parameters describing the resource to create, and if creation succeeds, it returns the resource id in a `Result::Ok` value. However, this design is a poor fit for the split-process design described above: content must wait for the reply to its buffer-creation message (say) before it can know which id it can use in the next message that uses that buffer. On a common usage pattern, the classic Rust design imposes the latency of a full cross-process round trip. We can avoid incurring these round-trip latencies simply by letting the content process assign resource ids itself. With this approach, content can choose an id for the new buffer, send a message to create the buffer, and then immediately send the next message operating on that buffer, since it already knows its id. Allowing content and GPU process activity to be pipelined greatly improves throughput. To help propagate errors correctly in this style of usage, when resource creation fails, the id supplied for that resource is marked to indicate as much, allowing subsequent operations using that id to be properly flagged as errors as well. [`process`]: crate::identity::IdentityManager::process [`Id`]: crate::id::Id [wrapped in a mutex]: trait.IdentityHandler.html#impl-IdentityHandler%3CI%3E-for-Mutex%3CIdentityManager%3E [WebGPU]: https://www.w3.org/TR/webgpu/ ## IDs and tracing As of `wgpu` v27, commands are encoded all at once when `CommandEncoder::finish` is called, not when the encoding methods are called for each command. This implies storing a representation of the commands in memory until `finish` is called. `Arc`s are more suitable for this purpose than numeric ids. Rather than redundantly store both `Id`s and `Arc`s, tracing has been changed to work with `Arc`s. The serialized trace identifies resources by the integer value of `Arc::as_ptr`. These IDs have the type [`crate::id::PointerId`]. The trace player uses hash maps to go from `PointerId`s to `Arc`s when replaying a trace. */ use alloc::sync::Arc; use core::fmt::Debug; use crate::{ binding_model::{BindGroup, BindGroupLayout, PipelineLayout}, command::{CommandBuffer, CommandEncoder, RenderBundle}, device::{queue::Queue, Device}, instance::Adapter, pipeline::{ComputePipeline, PipelineCache, RenderPipeline, ShaderModule}, registry::{Registry, RegistryReport}, resource::{ Blas, Buffer, ExternalTexture, Fallible, QuerySet, Sampler, StagingBuffer, Texture, TextureView, Tlas, }, }; #[derive(Debug, PartialEq, Eq)] pub struct HubReport { pub adapters: RegistryReport, pub devices: RegistryReport, pub queues: RegistryReport, pub pipeline_layouts: RegistryReport, pub shader_modules: RegistryReport, pub bind_group_layouts: RegistryReport, pub bind_groups: RegistryReport, pub command_encoders: RegistryReport, pub command_buffers: RegistryReport, pub render_bundles: RegistryReport, pub render_pipelines: RegistryReport, pub compute_pipelines: RegistryReport, pub pipeline_caches: RegistryReport, pub query_sets: RegistryReport, pub buffers: RegistryReport, pub textures: RegistryReport, pub texture_views: RegistryReport, pub external_textures: RegistryReport, pub samplers: RegistryReport, } impl HubReport { pub fn is_empty(&self) -> bool { self.adapters.is_empty() } } #[allow(rustdoc::private_intra_doc_links)] /// All the resources tracked by a [`crate::global::Global`]. /// /// ## Locking /// /// Each field in `Hub` is a [`Registry`] holding all the values of a /// particular type of resource, all protected by a single RwLock. /// So for example, to access any [`Buffer`], you must acquire a read /// lock on the `Hub`s entire buffers registry. The lock guard /// gives you access to the `Registry`'s [`Storage`], which you can /// then index with the buffer's id. (Yes, this design causes /// contention; see [#2272].) /// /// But most `wgpu` operations require access to several different /// kinds of resource, so you often need to hold locks on several /// different fields of your [`Hub`] simultaneously. /// /// Inside the `Registry` there are `Arc` where `T` is a Resource /// Lock of `Registry` happens only when accessing to get the specific resource /// /// [`Storage`]: crate::storage::Storage pub struct Hub { pub(crate) adapters: Registry>, pub(crate) devices: Registry>, pub(crate) queues: Registry>, pub(crate) pipeline_layouts: Registry>, pub(crate) shader_modules: Registry>, pub(crate) bind_group_layouts: Registry>, pub(crate) bind_groups: Registry>, pub(crate) command_encoders: Registry>, pub(crate) command_buffers: Registry>, pub(crate) render_bundles: Registry>, pub(crate) render_pipelines: Registry>, pub(crate) compute_pipelines: Registry>, pub(crate) pipeline_caches: Registry>, pub(crate) query_sets: Registry>, pub(crate) buffers: Registry>, pub(crate) staging_buffers: Registry, pub(crate) textures: Registry>, pub(crate) texture_views: Registry>, pub(crate) external_textures: Registry>, pub(crate) samplers: Registry>, pub(crate) blas_s: Registry>, pub(crate) tlas_s: Registry>, } impl Hub { pub(crate) fn new() -> Self { Self { adapters: Registry::new(), devices: Registry::new(), queues: Registry::new(), pipeline_layouts: Registry::new(), shader_modules: Registry::new(), bind_group_layouts: Registry::new(), bind_groups: Registry::new(), command_encoders: Registry::new(), command_buffers: Registry::new(), render_bundles: Registry::new(), render_pipelines: Registry::new(), compute_pipelines: Registry::new(), pipeline_caches: Registry::new(), query_sets: Registry::new(), buffers: Registry::new(), staging_buffers: Registry::new(), textures: Registry::new(), texture_views: Registry::new(), external_textures: Registry::new(), samplers: Registry::new(), blas_s: Registry::new(), tlas_s: Registry::new(), } } pub fn generate_report(&self) -> HubReport { HubReport { adapters: self.adapters.generate_report(), devices: self.devices.generate_report(), queues: self.queues.generate_report(), pipeline_layouts: self.pipeline_layouts.generate_report(), shader_modules: self.shader_modules.generate_report(), bind_group_layouts: self.bind_group_layouts.generate_report(), bind_groups: self.bind_groups.generate_report(), command_encoders: self.command_encoders.generate_report(), command_buffers: self.command_buffers.generate_report(), render_bundles: self.render_bundles.generate_report(), render_pipelines: self.render_pipelines.generate_report(), compute_pipelines: self.compute_pipelines.generate_report(), pipeline_caches: self.pipeline_caches.generate_report(), query_sets: self.query_sets.generate_report(), buffers: self.buffers.generate_report(), textures: self.textures.generate_report(), texture_views: self.texture_views.generate_report(), external_textures: self.external_textures.generate_report(), samplers: self.samplers.generate_report(), } } } ================================================ FILE: wgpu-core/src/id.rs ================================================ use crate::{Epoch, Index}; use core::{ cmp::Ordering, fmt::{self, Debug}, hash::Hash, marker::PhantomData, num::NonZeroU64, }; use wgt::WasmNotSendSync; const _: () = { if size_of::() != 4 { panic!() } }; const _: () = { if size_of::() != 4 { panic!() } }; const _: () = { if size_of::() != 8 { panic!() } }; /// The raw underlying representation of an identifier. #[repr(transparent)] #[cfg_attr( any(feature = "serde", feature = "trace"), derive(serde::Serialize), serde(into = "SerialId") )] #[cfg_attr( any(feature = "serde", feature = "replay"), derive(serde::Deserialize), serde(try_from = "SerialId") )] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct RawId(NonZeroU64); impl RawId { /// Zip together an identifier and return its raw underlying representation. /// /// # Panics /// /// If both ID components are zero. pub fn zip(index: Index, epoch: Epoch) -> RawId { let v = (index as u64) | ((epoch as u64) << 32); Self(NonZeroU64::new(v).expect("IDs may not be zero")) } /// Unzip a raw identifier into its components. pub fn unzip(self) -> (Index, Epoch) { (self.0.get() as Index, (self.0.get() >> 32) as Epoch) } } /// An identifier for a wgpu object. /// /// An `Id` value identifies a value stored in a [`Global`]'s [`Hub`]. /// /// ## Note on `Id` typing /// /// You might assume that an `Id` can only be used to retrieve a resource of /// type `T`, but that is not quite the case. The id types in `wgpu-core`'s /// public API ([`TextureId`], for example) can refer to resources belonging to /// any backend, but the corresponding resource types ([`Texture`], for /// example) are always parameterized by a specific backend `A`. /// /// So the `T` in `Id` is usually a resource type like `Texture`, /// where [`Noop`] is the `wgpu_hal` dummy back end. These empty types are /// never actually used, beyond just making sure you access each `Storage` with /// the right kind of identifier. The members of [`Hub`] pair up each /// `X` type with the resource type `X`, for some specific backend /// `A`. /// /// [`Global`]: crate::global::Global /// [`Hub`]: crate::hub::Hub /// [`Hub`]: crate::hub::Hub /// [`Texture`]: crate::resource::Texture /// [`Registry`]: crate::registry::Registry /// [`Noop`]: hal::api::Noop #[repr(transparent)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(transparent))] pub struct Id(RawId, PhantomData); // This type represents Id in a more readable (and editable) way. #[cfg(feature = "serde")] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug)] pub enum SerialId { // The only variant forces RON to not ignore "Id" Id(Index, Epoch), } #[cfg(feature = "serde")] impl From for SerialId { fn from(id: RawId) -> Self { let (index, epoch) = id.unzip(); Self::Id(index, epoch) } } #[cfg(feature = "serde")] pub struct ZeroIdError; #[cfg(feature = "serde")] impl fmt::Display for ZeroIdError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "IDs may not be zero") } } #[cfg(feature = "serde")] impl TryFrom for RawId { type Error = ZeroIdError; fn try_from(id: SerialId) -> Result { let SerialId::Id(index, epoch) = id; if index == 0 && epoch == 0 { Err(ZeroIdError) } else { Ok(RawId::zip(index, epoch)) } } } /// Identify an object by the pointer returned by `Arc::as_ptr`. /// /// This is used for tracing. See [IDs and tracing](crate::hub#ids-and-tracing). #[allow(dead_code)] #[cfg(feature = "serde")] #[derive(Debug, serde::Serialize, serde::Deserialize)] pub enum PointerId { // The only variant forces RON to not ignore "Id" PointerId(core::num::NonZeroUsize, #[serde(skip)] PhantomData), } #[cfg(feature = "serde")] impl Copy for PointerId {} #[cfg(feature = "serde")] impl Clone for PointerId { fn clone(&self) -> Self { *self } } #[cfg(feature = "serde")] impl PartialEq for PointerId { fn eq(&self, other: &Self) -> bool { let PointerId::PointerId(this, _) = self; let PointerId::PointerId(other, _) = other; this == other } } #[cfg(feature = "serde")] impl Eq for PointerId {} #[cfg(feature = "serde")] impl Hash for PointerId { fn hash(&self, state: &mut H) { let PointerId::PointerId(this, _) = self; this.hash(state); } } #[cfg(feature = "serde")] impl From<&alloc::sync::Arc> for PointerId { fn from(arc: &alloc::sync::Arc) -> Self { // Since the memory representation of `Arc` is just a pointer to // `ArcInner`, it would be nice to use that pointer as the trace ID, // since many `into_trace` implementations would then be no-ops at // runtime. However, `Arc::as_ptr` returns a pointer to the contained // data, not to the `ArcInner`. The `ArcInner` stores the reference // counts before the data, so the machine code for this conversion has // to add an offset to the pointer. PointerId::PointerId( core::num::NonZeroUsize::new(alloc::sync::Arc::as_ptr(arc) as usize).unwrap(), PhantomData, ) } } impl Id where T: Marker, { /// # Safety /// /// The raw id must be valid for the type. pub unsafe fn from_raw(raw: RawId) -> Self { Self(raw, PhantomData) } /// Coerce the identifiers into its raw underlying representation. pub fn into_raw(self) -> RawId { self.0 } #[inline] pub fn zip(index: Index, epoch: Epoch) -> Self { Id(RawId::zip(index, epoch), PhantomData) } #[inline] pub fn unzip(self) -> (Index, Epoch) { self.0.unzip() } } impl Copy for Id where T: Marker {} impl Clone for Id where T: Marker, { #[inline] fn clone(&self) -> Self { *self } } impl Debug for Id where T: Marker, { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { let (index, epoch) = self.unzip(); write!(formatter, "Id({index},{epoch})")?; Ok(()) } } impl Hash for Id where T: Marker, { #[inline] fn hash(&self, state: &mut H) { self.0.hash(state); } } impl PartialEq for Id where T: Marker, { #[inline] fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } impl Eq for Id where T: Marker {} impl PartialOrd for Id where T: Marker, { #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Id where T: Marker, { #[inline] fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) } } /// Marker trait used to determine which types uniquely identify a resource. /// /// For example, `Device` will have the same type of identifier as /// `Device` because `Device` for any `T` defines the same maker type. pub trait Marker: 'static + WasmNotSendSync {} // This allows `()` to be used as a marker type for tests. // // We don't want these in production code, since they essentially remove type // safety, like how identifiers across different types can be compared. #[cfg(test)] impl Marker for () {} /// Define identifiers for each resource. macro_rules! ids { ($( $(#[$($meta:meta)*])* pub type $name:ident $marker:ident; )*) => { /// Marker types for each resource. pub mod markers { $( #[derive(Debug)] pub enum $marker {} impl super::Marker for $marker {} )* } $( $(#[$($meta)*])* pub type $name = Id; )* } } ids! { pub type AdapterId Adapter; pub type SurfaceId Surface; pub type DeviceId Device; pub type QueueId Queue; pub type BufferId Buffer; pub type StagingBufferId StagingBuffer; pub type TextureViewId TextureView; pub type TextureId Texture; pub type ExternalTextureId ExternalTexture; pub type SamplerId Sampler; pub type BindGroupLayoutId BindGroupLayout; pub type PipelineLayoutId PipelineLayout; pub type BindGroupId BindGroup; pub type ShaderModuleId ShaderModule; pub type RenderPipelineId RenderPipeline; pub type ComputePipelineId ComputePipeline; pub type PipelineCacheId PipelineCache; pub type CommandEncoderId CommandEncoder; pub type CommandBufferId CommandBuffer; pub type RenderPassEncoderId RenderPassEncoder; pub type ComputePassEncoderId ComputePassEncoder; pub type RenderBundleEncoderId RenderBundleEncoder; pub type RenderBundleId RenderBundle; pub type QuerySetId QuerySet; pub type BlasId Blas; pub type TlasId Tlas; } #[test] fn test_id() { let indexes = [0, Index::MAX / 2 - 1, Index::MAX / 2 + 1, Index::MAX]; let epochs = [1, Epoch::MAX / 2 - 1, Epoch::MAX / 2 + 1, Epoch::MAX]; for &i in &indexes { for &e in &epochs { let id = Id::<()>::zip(i, e); let (index, epoch) = id.unzip(); assert_eq!(index, i); assert_eq!(epoch, e); } } } ================================================ FILE: wgpu-core/src/identity.rs ================================================ use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData}; use crate::{ id::{Id, Marker}, lock::{rank, Mutex}, Epoch, Index, }; #[derive(Copy, Clone, Debug, PartialEq)] enum IdSource { External, Allocated, None, } /// A simple structure to allocate [`Id`] identifiers. /// /// Calling [`alloc`] returns a fresh, never-before-seen id. Calling [`release`] /// marks an id as dead; it will never be returned again by `alloc`. /// /// `IdentityValues` returns `Id`s whose index values are suitable for use as /// indices into a `Vec` that holds those ids' referents: /// /// - Every live id has a distinct index value. Every live id's index /// selects a distinct element in the vector. /// /// - `IdentityValues` prefers low index numbers. If you size your vector to /// accommodate the indices produced here, the vector's length will reflect /// the highwater mark of actual occupancy. /// /// - `IdentityValues` reuses the index values of freed ids before returning /// ids with new index values. Freed vector entries get reused. /// /// - The non-reuse property is achieved by storing an `epoch` alongside the /// index in an `Id`. Index values are reused, but only with a different /// epoch. /// /// `IdentityValues` can also be used to track the count of IDs allocated by /// some external allocator. Combining internal and external allocation is not /// allowed; calling both `alloc` and `mark_as_used` on the same /// `IdentityValues` will result in a panic. The external mode is used when /// [playing back a trace of wgpu operations][player]. /// /// [`Id`]: crate::id::Id /// [`alloc`]: IdentityValues::alloc /// [`release`]: IdentityValues::release /// [player]: https://github.com/gfx-rs/wgpu/tree/trunk/player/ #[derive(Debug)] pub(super) struct IdentityValues { free: Vec<(Index, Epoch)>, next_index: Index, count: usize, // Sanity check: The allocation logic works under the assumption that we don't // do a mix of allocating ids from here and providing ids manually for the same // storage container. id_source: IdSource, } impl IdentityValues { /// Allocate a fresh, never-before-seen ID. /// /// # Panics /// /// If `mark_as_used` has previously been called on this `IdentityValues`. pub fn alloc(&mut self) -> Id { assert!( self.id_source != IdSource::External, "Mix of internally allocated and externally provided IDs" ); self.id_source = IdSource::Allocated; self.count += 1; match self.free.pop() { Some((index, epoch)) => Id::zip(index, epoch + 1), None => { let index = self.next_index; self.next_index += 1; let epoch = 1; Id::zip(index, epoch) } } } /// Increment the count of used IDs. /// /// # Panics /// /// If `alloc` has previously been called on this `IdentityValues`. pub fn mark_as_used(&mut self, id: Id) -> Id { assert!( self.id_source != IdSource::Allocated, "Mix of internally allocated and externally provided IDs" ); self.id_source = IdSource::External; self.count += 1; id } /// Free `id` and/or decrement the count of used IDs. /// /// Freed IDs will never be returned from `alloc` again. pub fn release(&mut self, id: Id) { if let IdSource::Allocated = self.id_source { let (index, epoch) = id.unzip(); self.free.push((index, epoch)); } self.count -= 1; } pub fn count(&self) -> usize { self.count } } #[derive(Debug)] pub struct IdentityManager { pub(super) values: Mutex, _phantom: PhantomData, } impl IdentityManager { pub fn process(&self) -> Id { self.values.lock().alloc() } pub fn mark_as_used(&self, id: Id) -> Id { self.values.lock().mark_as_used(id) } pub fn free(&self, id: Id) { self.values.lock().release(id) } } impl IdentityManager { pub fn new() -> Self { Self { values: Mutex::new( rank::IDENTITY_MANAGER_VALUES, IdentityValues { free: Vec::new(), next_index: 0, count: 0, id_source: IdSource::None, }, ), _phantom: PhantomData, } } } #[test] fn test_epoch_end_of_life() { use crate::id; let man = IdentityManager::::new(); let id1 = man.process(); assert_eq!(id1.unzip(), (0, 1)); man.free(id1); let id2 = man.process(); // confirm that the epoch 1 is no longer re-used assert_eq!(id2.unzip(), (0, 2)); } ================================================ FILE: wgpu-core/src/indirect_validation/dispatch.rs ================================================ use super::CreateIndirectValidationPipelineError; use crate::{ device::DeviceError, hal_label, pipeline::{CreateComputePipelineError, CreateShaderModuleError}, }; use alloc::{boxed::Box, format, string::ToString as _}; use core::num::NonZeroU64; /// This machinery requires the following limits: /// /// - max_bind_groups: 2, /// - max_dynamic_storage_buffers_per_pipeline_layout: 1, /// - max_storage_buffers_per_shader_stage: 2, /// - max_storage_buffer_binding_size: 3 * min_storage_buffer_offset_alignment, /// - max_immediate_size: 4, /// - max_compute_invocations_per_workgroup 1 /// /// These are all indirectly satisfied by `DownlevelFlags::INDIRECT_EXECUTION`, which is also /// required for this module's functionality to work. #[derive(Debug)] pub(crate) struct Dispatch { module: Box, dst_bind_group_layout: Box, src_bind_group_layout: Box, pipeline_layout: Box, pipeline: Box, dst_buffer: Box, dst_bind_group: Box, } pub struct Params<'a> { pub pipeline_layout: &'a dyn hal::DynPipelineLayout, pub pipeline: &'a dyn hal::DynComputePipeline, pub dst_buffer: &'a dyn hal::DynBuffer, pub dst_bind_group: &'a dyn hal::DynBindGroup, pub aligned_offset: u64, pub offset_remainder: u64, } impl Dispatch { pub(super) fn new( device: &dyn hal::DynDevice, instance_flags: wgt::InstanceFlags, limits: &wgt::Limits, ) -> Result { let max_compute_workgroups_per_dimension = limits.max_compute_workgroups_per_dimension; let src = format!( " @group(0) @binding(0) var dst: array; @group(1) @binding(0) var src: array; struct OffsetPc {{ inner: u32, }} var offset: OffsetPc; @compute @workgroup_size(1) fn main() {{ let src = vec3(src[offset.inner], src[offset.inner + 1], src[offset.inner + 2]); let max_compute_workgroups_per_dimension = {max_compute_workgroups_per_dimension}u; if ( src.x > max_compute_workgroups_per_dimension || src.y > max_compute_workgroups_per_dimension || src.z > max_compute_workgroups_per_dimension ) {{ dst = array(0u, 0u, 0u, 0u, 0u, 0u); }} else {{ dst = array(src.x, src.y, src.z, src.x, src.y, src.z); }} }} " ); // SAFETY: The value we are passing to `new_unchecked` is not zero, so this is safe. const SRC_BUFFER_SIZE: NonZeroU64 = NonZeroU64::new(size_of::() as u64 * 3).unwrap(); // SAFETY: The value we are passing to `new_unchecked` is not zero, so this is safe. const DST_BUFFER_SIZE: NonZeroU64 = NonZeroU64::new(SRC_BUFFER_SIZE.get() * 2).unwrap(); #[cfg(feature = "wgsl")] let module = naga::front::wgsl::parse_str(&src).map_err(|inner| { CreateShaderModuleError::Parsing(naga::error::ShaderError { source: src.clone(), label: None, inner: Box::new(inner), }) })?; #[cfg(not(feature = "wgsl"))] #[allow(clippy::diverging_sub_expression)] let module = panic!("Indirect validation requires the wgsl feature flag to be enabled!"); let info = crate::device::create_validator( wgt::Features::IMMEDIATES, wgt::DownlevelFlags::empty(), naga::valid::ValidationFlags::all(), ) .validate(&module) .map_err(|inner| { CreateShaderModuleError::Validation(naga::error::ShaderError { source: src, label: None, inner: Box::new(inner), }) })?; let hal_shader = hal::ShaderInput::Naga(hal::NagaShader { module: alloc::borrow::Cow::Owned(module), info, debug_source: None, }); let hal_desc = hal::ShaderModuleDescriptor { label: hal_label( Some("(wgpu internal) Indirect dispatch validation shader module"), instance_flags, ), runtime_checks: wgt::ShaderRuntimeChecks::unchecked(), }; let module = unsafe { device.create_shader_module(&hal_desc, hal_shader) }.map_err(|error| { match error { hal::ShaderError::Device(error) => { CreateShaderModuleError::Device(DeviceError::from_hal(error)) } hal::ShaderError::Compilation(ref msg) => { log::error!("Shader error: {msg}"); CreateShaderModuleError::Generation } } })?; let dst_bind_group_layout_desc = hal::BindGroupLayoutDescriptor { label: hal_label( Some("(wgpu internal) Indirect dispatch validation destination bind group layout"), instance_flags, ), flags: hal::BindGroupLayoutFlags::empty(), entries: &[wgt::BindGroupLayoutEntry { binding: 0, visibility: wgt::ShaderStages::COMPUTE, ty: wgt::BindingType::Buffer { ty: wgt::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: Some(DST_BUFFER_SIZE), }, count: None, }], }; let dst_bind_group_layout = unsafe { device .create_bind_group_layout(&dst_bind_group_layout_desc) .map_err(DeviceError::from_hal)? }; let src_bind_group_layout_desc = hal::BindGroupLayoutDescriptor { label: hal_label( Some("(wgpu internal) Indirect dispatch validation source bind group layout"), instance_flags, ), flags: hal::BindGroupLayoutFlags::empty(), entries: &[wgt::BindGroupLayoutEntry { binding: 0, visibility: wgt::ShaderStages::COMPUTE, ty: wgt::BindingType::Buffer { ty: wgt::BufferBindingType::Storage { read_only: true }, has_dynamic_offset: true, min_binding_size: Some(SRC_BUFFER_SIZE), }, count: None, }], }; let src_bind_group_layout = unsafe { device .create_bind_group_layout(&src_bind_group_layout_desc) .map_err(DeviceError::from_hal)? }; let pipeline_layout_desc = hal::PipelineLayoutDescriptor { label: hal_label( Some("(wgpu internal) Indirect dispatch validation pipeline layout"), instance_flags, ), flags: hal::PipelineLayoutFlags::empty(), bind_group_layouts: &[ Some(dst_bind_group_layout.as_ref()), Some(src_bind_group_layout.as_ref()), ], immediate_size: 4, }; let pipeline_layout = unsafe { device .create_pipeline_layout(&pipeline_layout_desc) .map_err(DeviceError::from_hal)? }; let pipeline_desc = hal::ComputePipelineDescriptor { label: hal_label( Some("(wgpu internal) Indirect dispatch validation pipeline"), instance_flags, ), layout: pipeline_layout.as_ref(), stage: hal::ProgrammableStage { module: module.as_ref(), entry_point: "main", constants: &Default::default(), zero_initialize_workgroup_memory: false, }, cache: None, }; let pipeline = unsafe { device.create_compute_pipeline(&pipeline_desc) }.map_err(|err| match err { hal::PipelineError::Device(error) => { CreateComputePipelineError::Device(DeviceError::from_hal(error)) } hal::PipelineError::Linkage(_stages, msg) => { CreateComputePipelineError::Internal(msg) } hal::PipelineError::EntryPoint(_stage) => CreateComputePipelineError::Internal( crate::device::ENTRYPOINT_FAILURE_ERROR.to_string(), ), hal::PipelineError::PipelineConstants(_, error) => { CreateComputePipelineError::PipelineConstants(error) } })?; let dst_buffer_desc = hal::BufferDescriptor { label: hal_label( Some("(wgpu internal) Indirect dispatch validation destination buffer"), instance_flags, ), size: DST_BUFFER_SIZE.get(), usage: wgt::BufferUses::INDIRECT | wgt::BufferUses::STORAGE_READ_WRITE, memory_flags: hal::MemoryFlags::empty(), }; let dst_buffer = unsafe { device.create_buffer(&dst_buffer_desc) }.map_err(DeviceError::from_hal)?; let dst_bind_group_desc = hal::BindGroupDescriptor { label: hal_label( Some("(wgpu internal) Indirect dispatch validation destination bind group"), instance_flags, ), layout: dst_bind_group_layout.as_ref(), entries: &[hal::BindGroupEntry { binding: 0, resource_index: 0, count: 1, }], // SAFETY: We just created the buffer with this size. buffers: &[hal::BufferBinding::new_unchecked( dst_buffer.as_ref(), 0, Some(DST_BUFFER_SIZE), )], samplers: &[], textures: &[], acceleration_structures: &[], external_textures: &[], }; let dst_bind_group = unsafe { device .create_bind_group(&dst_bind_group_desc) .map_err(DeviceError::from_hal) }?; Ok(Self { module, dst_bind_group_layout, src_bind_group_layout, pipeline_layout, pipeline, dst_buffer, dst_bind_group, }) } /// `Ok(None)` will only be returned if `buffer_size` is `0`. pub(super) fn create_src_bind_group( &self, device: &dyn hal::DynDevice, limits: &wgt::Limits, buffer_size: u64, buffer: &dyn hal::DynBuffer, instance_flags: wgt::InstanceFlags, ) -> Result>, DeviceError> { let binding_size = calculate_src_buffer_binding_size(buffer_size, limits); let Some(binding_size) = NonZeroU64::new(binding_size) else { return Ok(None); }; let hal_desc = hal::BindGroupDescriptor { label: hal_label( Some("(wgpu internal) Indirect dispatch validation source bind group"), instance_flags, ), layout: self.src_bind_group_layout.as_ref(), entries: &[hal::BindGroupEntry { binding: 0, resource_index: 0, count: 1, }], // SAFETY: We calculated the binding size to fit within the buffer. buffers: &[hal::BufferBinding::new_unchecked(buffer, 0, binding_size)], samplers: &[], textures: &[], acceleration_structures: &[], external_textures: &[], }; unsafe { device .create_bind_group(&hal_desc) .map(Some) .map_err(DeviceError::from_hal) } } pub fn params<'a>(&'a self, limits: &wgt::Limits, offset: u64, buffer_size: u64) -> Params<'a> { // The offset we receive is only required to be aligned to 4 bytes. // // Binding offsets and dynamic offsets are required to be aligned to // min_storage_buffer_offset_alignment (256 bytes by default). // // So, we work around this limitation by calculating an aligned offset // and pass the remainder through a immediate data. // // We could bind the whole buffer and only have to pass the offset // through a immediate data but we might run into the // max_storage_buffer_binding_size limit. // // See the inner docs of `calculate_src_buffer_binding_size` to // see how we get the appropriate `binding_size`. let alignment = limits.min_storage_buffer_offset_alignment as u64; let binding_size = calculate_src_buffer_binding_size(buffer_size, limits); let aligned_offset = offset - offset % alignment; // This works because `binding_size` is either `buffer_size` or `alignment * 2 + buffer_size % alignment`. let max_aligned_offset = buffer_size - binding_size; let aligned_offset = aligned_offset.min(max_aligned_offset); let offset_remainder = offset - aligned_offset; Params { pipeline_layout: self.pipeline_layout.as_ref(), pipeline: self.pipeline.as_ref(), dst_buffer: self.dst_buffer.as_ref(), dst_bind_group: self.dst_bind_group.as_ref(), aligned_offset, offset_remainder, } } pub(super) fn dispose(self, device: &dyn hal::DynDevice) { let Dispatch { module, dst_bind_group_layout, src_bind_group_layout, pipeline_layout, pipeline, dst_buffer, dst_bind_group, } = self; unsafe { device.destroy_bind_group(dst_bind_group); device.destroy_buffer(dst_buffer); device.destroy_compute_pipeline(pipeline); device.destroy_pipeline_layout(pipeline_layout); device.destroy_bind_group_layout(src_bind_group_layout); device.destroy_bind_group_layout(dst_bind_group_layout); device.destroy_shader_module(module); } } } fn calculate_src_buffer_binding_size(buffer_size: u64, limits: &wgt::Limits) -> u64 { let alignment = limits.min_storage_buffer_offset_alignment as u64; // We need to choose a binding size that can address all possible sets of 12 contiguous bytes in the buffer taking // into account that the dynamic offset needs to be a multiple of `min_storage_buffer_offset_alignment`. // Given the know variables: `offset`, `buffer_size`, `alignment` and the rule `offset + 12 <= buffer_size`. // Let `chunks = floor(buffer_size / alignment)`. // Let `chunk` be the interval `[0, chunks]`. // Let `offset = alignment * chunk + r` where `r` is the interval [0, alignment - 4]. // Let `binding` be the interval `[offset, offset + 12]`. // Let `aligned_offset = alignment * chunk`. // Let `aligned_binding` be the interval `[aligned_offset, aligned_offset + r + 12]`. // Let `aligned_binding_size = r + 12 = [12, alignment + 8]`. // Let `min_aligned_binding_size = alignment + 8`. // `min_aligned_binding_size` is the minimum binding size required to address all 12 contiguous bytes in the buffer // but the last aligned_offset + min_aligned_binding_size might overflow the buffer. In order to avoid this we must // pick a larger `binding_size` that satisfies: `last_aligned_offset + binding_size = buffer_size` and // `binding_size >= min_aligned_binding_size`. // Let `buffer_size = alignment * chunks + sr` where `sr` is the interval [0, alignment - 4]. // Let `last_aligned_offset = alignment * (chunks - u)` where `u` is the interval [0, chunks]. // => `binding_size = buffer_size - last_aligned_offset` // => `binding_size = alignment * chunks + sr - alignment * (chunks - u)` // => `binding_size = alignment * chunks + sr - alignment * chunks + alignment * u` // => `binding_size = sr + alignment * u` // => `min_aligned_binding_size <= sr + alignment * u` // => `alignment + 8 <= sr + alignment * u` // => `u` must be at least 2 // => `binding_size = sr + alignment * 2` let binding_size = 2 * alignment + (buffer_size % alignment); binding_size.min(buffer_size) } ================================================ FILE: wgpu-core/src/indirect_validation/draw.rs ================================================ use super::{ utils::{BufferBarrierScratch, BufferBarriers, UniqueIndexExt as _, UniqueIndexScratch}, CreateIndirectValidationPipelineError, }; use crate::{ command::RenderPassErrorInner, device::{queue::TempResource, Device, DeviceError}, hal_label, lock::{rank, Mutex}, pipeline::{CreateComputePipelineError, CreateShaderModuleError}, resource::{RawResourceAccess as _, StagingBuffer, Trackable}, snatch::SnatchGuard, track::TrackerIndex, FastHashMap, }; use alloc::{boxed::Box, string::ToString, sync::Arc, vec, vec::Vec}; use core::{mem::size_of, num::NonZeroU64}; use wgt::Limits; /// Note: This needs to be under: /// /// default max_compute_workgroups_per_dimension * size_of::() * `workgroup_size` used by the shader /// /// = (2^16 - 1) * 2^4 * 2^6 /// /// It is currently set to: /// /// = (2^16 - 1) * 2^4 /// /// This is enough space for: /// /// - 65535 [`wgt::DrawIndirectArgs`] / [`MetadataEntry`] /// - 52428 [`wgt::DrawIndexedIndirectArgs`] const BUFFER_SIZE: wgt::BufferSize = wgt::BufferSize::new(1_048_560).unwrap(); /// Holds all device-level resources that are needed to validate indirect draws. /// /// This machinery requires the following limits: /// /// - max_bind_groups: 3, /// - max_dynamic_storage_buffers_per_pipeline_layout: 1, /// - max_storage_buffers_per_shader_stage: 3, /// - max_immediate_size: 8, /// /// These are all indirectly satisfied by `DownlevelFlags::INDIRECT_EXECUTION`, which is also /// required for this module's functionality to work. #[derive(Debug)] pub(crate) struct Draw { module: Box, metadata_bind_group_layout: Box, src_bind_group_layout: Box, dst_bind_group_layout: Box, pipeline_layout: Box, pipeline: Box, free_indirect_entries: Mutex>, free_metadata_entries: Mutex>, } impl Draw { pub(super) fn new( device: &dyn hal::DynDevice, required_features: &wgt::Features, instance_flags: wgt::InstanceFlags, backend: wgt::Backend, ) -> Result { let module = create_validation_module(device, instance_flags)?; let metadata_bind_group_layout = create_bind_group_layout( device, true, false, BUFFER_SIZE, hal_label( Some("(wgpu internal) Indirect draw validation metadata bind group layout"), instance_flags, ), )?; let src_bind_group_layout = create_bind_group_layout( device, true, true, wgt::BufferSize::new(4 * 4).unwrap(), hal_label( Some("(wgpu internal) Indirect draw validation source bind group layout"), instance_flags, ), )?; let dst_bind_group_layout = create_bind_group_layout( device, false, false, BUFFER_SIZE, hal_label( Some("(wgpu internal) Indirect draw validation destination bind group layout"), instance_flags, ), )?; let pipeline_layout_desc = hal::PipelineLayoutDescriptor { label: hal_label( Some("(wgpu internal) Indirect draw validation pipeline layout"), instance_flags, ), flags: hal::PipelineLayoutFlags::empty(), bind_group_layouts: &[ Some(metadata_bind_group_layout.as_ref()), Some(src_bind_group_layout.as_ref()), Some(dst_bind_group_layout.as_ref()), ], immediate_size: 8, }; let pipeline_layout = unsafe { device .create_pipeline_layout(&pipeline_layout_desc) .map_err(DeviceError::from_hal)? }; let supports_indirect_first_instance = required_features.contains(wgt::Features::INDIRECT_FIRST_INSTANCE); let write_d3d12_special_constants = backend == wgt::Backend::Dx12; let pipeline = create_validation_pipeline( device, module.as_ref(), pipeline_layout.as_ref(), supports_indirect_first_instance, write_d3d12_special_constants, instance_flags, )?; Ok(Self { module, metadata_bind_group_layout, src_bind_group_layout, dst_bind_group_layout, pipeline_layout, pipeline, free_indirect_entries: Mutex::new(rank::BUFFER_POOL, Vec::new()), free_metadata_entries: Mutex::new(rank::BUFFER_POOL, Vec::new()), }) } /// `Ok(None)` will only be returned if `buffer_size` is `0`. pub(super) fn create_src_bind_group( &self, device: &dyn hal::DynDevice, limits: &Limits, buffer_size: u64, buffer: &dyn hal::DynBuffer, instance_flags: wgt::InstanceFlags, ) -> Result>, DeviceError> { let binding_size = calculate_src_buffer_binding_size(buffer_size, limits); let Some(binding_size) = NonZeroU64::new(binding_size) else { return Ok(None); }; let hal_desc = hal::BindGroupDescriptor { label: hal_label( Some("(wgpu internal) Indirect draw validation source bind group"), instance_flags, ), layout: self.src_bind_group_layout.as_ref(), entries: &[hal::BindGroupEntry { binding: 0, resource_index: 0, count: 1, }], // SAFETY: We calculated the binding size to fit within the buffer. buffers: &[hal::BufferBinding::new_unchecked(buffer, 0, binding_size)], samplers: &[], textures: &[], acceleration_structures: &[], external_textures: &[], }; unsafe { device .create_bind_group(&hal_desc) .map(Some) .map_err(DeviceError::from_hal) } } fn acquire_dst_entry( &self, device: &dyn hal::DynDevice, instance_flags: wgt::InstanceFlags, ) -> Result { let mut free_buffers = self.free_indirect_entries.lock(); match free_buffers.pop() { Some(buffer) => Ok(buffer), None => { let usage = wgt::BufferUses::INDIRECT | wgt::BufferUses::STORAGE_READ_WRITE; create_buffer_and_bind_group( device, usage, self.dst_bind_group_layout.as_ref(), hal_label(Some("(wgpu internal) Indirect draw validation destination buffer"), instance_flags), hal_label(Some("(wgpu internal) Indirect draw validation destination bind group layout"), instance_flags), ) } } } fn release_dst_entries(&self, entries: impl Iterator) { self.free_indirect_entries.lock().extend(entries); } fn acquire_metadata_entry( &self, device: &dyn hal::DynDevice, instance_flags: wgt::InstanceFlags, ) -> Result { let mut free_buffers = self.free_metadata_entries.lock(); match free_buffers.pop() { Some(buffer) => Ok(buffer), None => { let usage = wgt::BufferUses::COPY_DST | wgt::BufferUses::STORAGE_READ_ONLY; create_buffer_and_bind_group( device, usage, self.metadata_bind_group_layout.as_ref(), hal_label( Some("(wgpu internal) Indirect draw validation metadata buffer"), instance_flags, ), hal_label( Some("(wgpu internal) Indirect draw validation metadata bind group layout"), instance_flags, ), ) } } } fn release_metadata_entries(&self, entries: impl Iterator) { self.free_metadata_entries.lock().extend(entries); } /// Injects a compute pass that will validate all indirect draws in the current render pass. pub(crate) fn inject_validation_pass( &self, device: &Arc, snatch_guard: &SnatchGuard, resources: &mut DrawResources, temp_resources: &mut Vec, encoder: &mut dyn hal::DynCommandEncoder, batcher: DrawBatcher, ) -> Result<(), RenderPassErrorInner> { let mut batches = batcher.batches; if batches.is_empty() { return Ok(()); } let max_staging_buffer_size = 1 << 26; // ~67MiB let mut staging_buffers = Vec::new(); let mut current_size = 0; for batch in batches.values_mut() { let data = batch.metadata(); let offset = if current_size + data.len() > max_staging_buffer_size { let staging_buffer = StagingBuffer::new(device, NonZeroU64::new(current_size as u64).unwrap())?; staging_buffers.push(staging_buffer); current_size = data.len(); 0 } else { let offset = current_size; current_size += data.len(); offset as u64 }; batch.staging_buffer_index = staging_buffers.len(); batch.staging_buffer_offset = offset; } if current_size != 0 { let staging_buffer = StagingBuffer::new(device, NonZeroU64::new(current_size as u64).unwrap())?; staging_buffers.push(staging_buffer); } for batch in batches.values() { let data = batch.metadata(); let staging_buffer = &mut staging_buffers[batch.staging_buffer_index]; unsafe { staging_buffer.write_with_offset( data, 0, batch.staging_buffer_offset as isize, data.len(), ) }; } let staging_buffers: Vec<_> = staging_buffers .into_iter() .map(|buffer| buffer.flush()) .collect(); let mut current_metadata_entry = None; for batch in batches.values_mut() { let data = batch.metadata(); let (metadata_resource_index, metadata_buffer_offset) = resources.get_metadata_subrange(data.len() as u64, &mut current_metadata_entry)?; batch.metadata_resource_index = metadata_resource_index; batch.metadata_buffer_offset = metadata_buffer_offset; } let buffer_barrier_scratch = &mut BufferBarrierScratch::new(); let unique_index_scratch = &mut UniqueIndexScratch::new(); BufferBarriers::new(buffer_barrier_scratch) .extend( batches .values() .map(|batch| batch.staging_buffer_index) .unique(unique_index_scratch) .map(|index| hal::BufferBarrier { buffer: staging_buffers[index].raw(), usage: hal::StateTransition { from: wgt::BufferUses::MAP_WRITE, to: wgt::BufferUses::COPY_SRC, }, }), ) .extend( batches .values() .map(|batch| batch.metadata_resource_index) .unique(unique_index_scratch) .map(|index| hal::BufferBarrier { buffer: resources.get_metadata_buffer(index), usage: hal::StateTransition { from: wgt::BufferUses::STORAGE_READ_ONLY, to: wgt::BufferUses::COPY_DST, }, }), ) .encode(encoder); for batch in batches.values() { let data = batch.metadata(); let data_size = NonZeroU64::new(data.len() as u64).unwrap(); let staging_buffer = &staging_buffers[batch.staging_buffer_index]; let metadata_buffer = resources.get_metadata_buffer(batch.metadata_resource_index); unsafe { encoder.copy_buffer_to_buffer( staging_buffer.raw(), metadata_buffer, &[hal::BufferCopy { src_offset: batch.staging_buffer_offset, dst_offset: batch.metadata_buffer_offset, size: data_size, }], ); } } for staging_buffer in staging_buffers { temp_resources.push(TempResource::StagingBuffer(staging_buffer)); } BufferBarriers::new(buffer_barrier_scratch) .extend( batches .values() .map(|batch| batch.metadata_resource_index) .unique(unique_index_scratch) .map(|index| hal::BufferBarrier { buffer: resources.get_metadata_buffer(index), usage: hal::StateTransition { from: wgt::BufferUses::COPY_DST, to: wgt::BufferUses::STORAGE_READ_ONLY, }, }), ) .extend( batches .values() .map(|batch| batch.dst_resource_index) .unique(unique_index_scratch) .map(|index| hal::BufferBarrier { buffer: resources.get_dst_buffer(index), usage: hal::StateTransition { from: wgt::BufferUses::INDIRECT, to: wgt::BufferUses::STORAGE_READ_WRITE, }, }), ) .encode(encoder); let desc = hal::ComputePassDescriptor { label: hal_label( Some("(wgpu internal) Indirect draw validation pass"), device.instance_flags, ), timestamp_writes: None, }; unsafe { encoder.begin_compute_pass(&desc); } unsafe { encoder.set_compute_pipeline(self.pipeline.as_ref()); } for batch in batches.values() { let pipeline_layout = self.pipeline_layout.as_ref(); let metadata_start = (batch.metadata_buffer_offset / size_of::() as u64) as u32; let metadata_count = batch.entries.len() as u32; unsafe { encoder.set_immediates(pipeline_layout, 0, &[metadata_start, metadata_count]); } let metadata_bind_group = resources.get_metadata_bind_group(batch.metadata_resource_index); unsafe { encoder.set_bind_group(pipeline_layout, 0, metadata_bind_group, &[]); } // Make sure the indirect buffer is still valid. batch.src_buffer.try_raw(snatch_guard)?; let src_bind_group = batch .src_buffer .indirect_validation_bind_groups .get(snatch_guard) .unwrap() .draw .as_ref(); unsafe { encoder.set_bind_group( pipeline_layout, 1, src_bind_group, &[batch.src_dynamic_offset as u32], ); } let dst_bind_group = resources.get_dst_bind_group(batch.dst_resource_index); unsafe { encoder.set_bind_group(pipeline_layout, 2, dst_bind_group, &[]); } unsafe { encoder.dispatch([(batch.entries.len() as u32).div_ceil(64), 1, 1]); } } unsafe { encoder.end_compute_pass(); } BufferBarriers::new(buffer_barrier_scratch) .extend( batches .values() .map(|batch| batch.dst_resource_index) .unique(unique_index_scratch) .map(|index| hal::BufferBarrier { buffer: resources.get_dst_buffer(index), usage: hal::StateTransition { from: wgt::BufferUses::STORAGE_READ_WRITE, to: wgt::BufferUses::INDIRECT, }, }), ) .encode(encoder); Ok(()) } pub(super) fn dispose(self, device: &dyn hal::DynDevice) { let Draw { module, metadata_bind_group_layout, src_bind_group_layout, dst_bind_group_layout, pipeline_layout, pipeline, free_indirect_entries, free_metadata_entries, } = self; for entry in free_indirect_entries.into_inner().drain(..) { unsafe { device.destroy_bind_group(entry.bind_group); device.destroy_buffer(entry.buffer); } } for entry in free_metadata_entries.into_inner().drain(..) { unsafe { device.destroy_bind_group(entry.bind_group); device.destroy_buffer(entry.buffer); } } unsafe { device.destroy_compute_pipeline(pipeline); device.destroy_pipeline_layout(pipeline_layout); device.destroy_bind_group_layout(metadata_bind_group_layout); device.destroy_bind_group_layout(src_bind_group_layout); device.destroy_bind_group_layout(dst_bind_group_layout); device.destroy_shader_module(module); } } } fn create_validation_module( device: &dyn hal::DynDevice, instance_flags: wgt::InstanceFlags, ) -> Result, CreateIndirectValidationPipelineError> { let src = include_str!("./validate_draw.wgsl"); #[cfg(feature = "wgsl")] let module = naga::front::wgsl::parse_str(src).map_err(|inner| { CreateShaderModuleError::Parsing(naga::error::ShaderError { source: src.to_string(), label: None, inner: Box::new(inner), }) })?; #[cfg(not(feature = "wgsl"))] #[allow(clippy::diverging_sub_expression)] let module = panic!("Indirect validation requires the wgsl feature flag to be enabled!"); let info = crate::device::create_validator( wgt::Features::IMMEDIATES, wgt::DownlevelFlags::empty(), naga::valid::ValidationFlags::all(), ) .validate(&module) .map_err(|inner| { CreateShaderModuleError::Validation(naga::error::ShaderError { source: src.to_string(), label: None, inner: Box::new(inner), }) })?; let hal_shader = hal::ShaderInput::Naga(hal::NagaShader { module: alloc::borrow::Cow::Owned(module), info, debug_source: None, }); let hal_desc = hal::ShaderModuleDescriptor { label: hal_label( Some("(wgpu internal) Indirect draw validation shader module"), instance_flags, ), runtime_checks: wgt::ShaderRuntimeChecks::unchecked(), }; let module = unsafe { device.create_shader_module(&hal_desc, hal_shader) }.map_err( |error| match error { hal::ShaderError::Device(error) => { CreateShaderModuleError::Device(DeviceError::from_hal(error)) } hal::ShaderError::Compilation(ref msg) => { log::error!("Shader error: {msg}"); CreateShaderModuleError::Generation } }, )?; Ok(module) } fn create_validation_pipeline( device: &dyn hal::DynDevice, module: &dyn hal::DynShaderModule, pipeline_layout: &dyn hal::DynPipelineLayout, supports_indirect_first_instance: bool, write_d3d12_special_constants: bool, instance_flags: wgt::InstanceFlags, ) -> Result, CreateIndirectValidationPipelineError> { let pipeline_desc = hal::ComputePipelineDescriptor { label: hal_label( Some("(wgpu internal) Indirect draw validation pipeline"), instance_flags, ), layout: pipeline_layout, stage: hal::ProgrammableStage { module, entry_point: "main", constants: &hashbrown::HashMap::from([ ( "supports_indirect_first_instance".to_string(), f64::from(supports_indirect_first_instance), ), ( "write_d3d12_special_constants".to_string(), f64::from(write_d3d12_special_constants), ), ]), zero_initialize_workgroup_memory: false, }, cache: None, }; let pipeline = unsafe { device.create_compute_pipeline(&pipeline_desc) }.map_err(|err| match err { hal::PipelineError::Device(error) => { CreateComputePipelineError::Device(DeviceError::from_hal(error)) } hal::PipelineError::Linkage(_stages, msg) => CreateComputePipelineError::Internal(msg), hal::PipelineError::EntryPoint(_stage) => CreateComputePipelineError::Internal( crate::device::ENTRYPOINT_FAILURE_ERROR.to_string(), ), hal::PipelineError::PipelineConstants(_, error) => { CreateComputePipelineError::PipelineConstants(error) } })?; Ok(pipeline) } fn create_bind_group_layout( device: &dyn hal::DynDevice, read_only: bool, has_dynamic_offset: bool, min_binding_size: wgt::BufferSize, label: Option<&'static str>, ) -> Result, CreateIndirectValidationPipelineError> { let bind_group_layout_desc = hal::BindGroupLayoutDescriptor { label, flags: hal::BindGroupLayoutFlags::empty(), entries: &[wgt::BindGroupLayoutEntry { binding: 0, visibility: wgt::ShaderStages::COMPUTE, ty: wgt::BindingType::Buffer { ty: wgt::BufferBindingType::Storage { read_only }, has_dynamic_offset, min_binding_size: Some(min_binding_size), }, count: None, }], }; let bind_group_layout = unsafe { device .create_bind_group_layout(&bind_group_layout_desc) .map_err(DeviceError::from_hal)? }; Ok(bind_group_layout) } /// Returns the largest binding size that when combined with dynamic offsets can address the whole buffer. fn calculate_src_buffer_binding_size(buffer_size: u64, limits: &Limits) -> u64 { let max_storage_buffer_binding_size = limits.max_storage_buffer_binding_size; let min_storage_buffer_offset_alignment = limits.min_storage_buffer_offset_alignment as u64; if buffer_size <= max_storage_buffer_binding_size { buffer_size } else { let buffer_rem = buffer_size % min_storage_buffer_offset_alignment; let binding_rem = max_storage_buffer_binding_size % min_storage_buffer_offset_alignment; // Can the buffer remainder fit in the binding remainder? // If so, align max binding size and add buffer remainder if buffer_rem <= binding_rem { max_storage_buffer_binding_size - binding_rem + buffer_rem } // If not, align max binding size, shorten it by a chunk and add buffer remainder else { max_storage_buffer_binding_size - binding_rem - min_storage_buffer_offset_alignment + buffer_rem } } } /// Splits the given `offset` into a dynamic offset & offset. fn calculate_src_offsets(buffer_size: u64, limits: &Limits, offset: u64) -> (u64, u64) { let binding_size = calculate_src_buffer_binding_size(buffer_size, limits); let min_storage_buffer_offset_alignment = limits.min_storage_buffer_offset_alignment as u64; let chunk_adjustment = match min_storage_buffer_offset_alignment { // No need to adjust since the src_offset is 4 byte aligned. 4 => 0, // With 16/20 bytes of data we can straddle up to 2 8 byte boundaries: // - 16 bytes of data: (4|8|4) // - 20 bytes of data: (4|8|8, 8|8|4) 8 => 2, // With 16/20 bytes of data we can straddle up to 1 16+ byte boundary: // - 16 bytes of data: (4|12, 8|8, 12|4) // - 20 bytes of data: (4|16, 8|12, 12|8, 16|4) 16.. => 1, _ => unreachable!(), }; let chunks = binding_size / min_storage_buffer_offset_alignment; let dynamic_offset_stride = chunks.saturating_sub(chunk_adjustment) * min_storage_buffer_offset_alignment; if dynamic_offset_stride == 0 { return (0, offset); } let max_dynamic_offset = buffer_size - binding_size; let max_dynamic_offset_index = max_dynamic_offset / dynamic_offset_stride; let src_dynamic_offset_index = offset / dynamic_offset_stride; let src_dynamic_offset = src_dynamic_offset_index.min(max_dynamic_offset_index) * dynamic_offset_stride; let src_offset = offset - src_dynamic_offset; (src_dynamic_offset, src_offset) } #[derive(Debug)] struct BufferPoolEntry { buffer: Box, bind_group: Box, } fn create_buffer_and_bind_group( device: &dyn hal::DynDevice, usage: wgt::BufferUses, bind_group_layout: &dyn hal::DynBindGroupLayout, buffer_label: Option<&'static str>, bind_group_label: Option<&'static str>, ) -> Result { let buffer_desc = hal::BufferDescriptor { label: buffer_label, size: BUFFER_SIZE.get(), usage, memory_flags: hal::MemoryFlags::empty(), }; let buffer = unsafe { device.create_buffer(&buffer_desc) }?; let bind_group_desc = hal::BindGroupDescriptor { label: bind_group_label, layout: bind_group_layout, entries: &[hal::BindGroupEntry { binding: 0, resource_index: 0, count: 1, }], // SAFETY: We just created the buffer with this size. buffers: &[hal::BufferBinding::new_unchecked( buffer.as_ref(), 0, BUFFER_SIZE, )], samplers: &[], textures: &[], acceleration_structures: &[], external_textures: &[], }; let bind_group = unsafe { device.create_bind_group(&bind_group_desc) }?; Ok(BufferPoolEntry { buffer, bind_group }) } #[derive(Clone)] struct CurrentEntry { index: usize, offset: u64, } /// Holds all command buffer-level resources that are needed to validate indirect draws. pub(crate) struct DrawResources { device: Arc, dst_entries: Vec, metadata_entries: Vec, } impl Drop for DrawResources { fn drop(&mut self) { if let Some(ref indirect_validation) = self.device.indirect_validation { let indirect_draw_validation = &indirect_validation.draw; indirect_draw_validation.release_dst_entries(self.dst_entries.drain(..)); indirect_draw_validation.release_metadata_entries(self.metadata_entries.drain(..)); } } } impl DrawResources { pub(crate) fn new(device: Arc) -> Self { DrawResources { device, dst_entries: Vec::new(), metadata_entries: Vec::new(), } } pub(crate) fn get_dst_buffer(&self, index: usize) -> &dyn hal::DynBuffer { self.dst_entries.get(index).unwrap().buffer.as_ref() } fn get_dst_bind_group(&self, index: usize) -> &dyn hal::DynBindGroup { self.dst_entries.get(index).unwrap().bind_group.as_ref() } fn get_metadata_buffer(&self, index: usize) -> &dyn hal::DynBuffer { self.metadata_entries.get(index).unwrap().buffer.as_ref() } fn get_metadata_bind_group(&self, index: usize) -> &dyn hal::DynBindGroup { self.metadata_entries .get(index) .unwrap() .bind_group .as_ref() } fn get_dst_subrange( &mut self, size: u64, current_entry: &mut Option, ) -> Result<(usize, u64), DeviceError> { let indirect_draw_validation = &self.device.indirect_validation.as_ref().unwrap().draw; let ensure_entry = |index: usize| { if self.dst_entries.len() <= index { let entry = indirect_draw_validation .acquire_dst_entry(self.device.raw(), self.device.instance_flags)?; self.dst_entries.push(entry); } Ok(()) }; let entry_data = Self::get_subrange_impl(ensure_entry, current_entry, size)?; Ok((entry_data.index, entry_data.offset)) } fn get_metadata_subrange( &mut self, size: u64, current_entry: &mut Option, ) -> Result<(usize, u64), DeviceError> { let indirect_draw_validation = &self.device.indirect_validation.as_ref().unwrap().draw; let ensure_entry = |index: usize| { if self.metadata_entries.len() <= index { let entry = indirect_draw_validation .acquire_metadata_entry(self.device.raw(), self.device.instance_flags)?; self.metadata_entries.push(entry); } Ok(()) }; let entry_data = Self::get_subrange_impl(ensure_entry, current_entry, size)?; Ok((entry_data.index, entry_data.offset)) } fn get_subrange_impl( ensure_entry: impl FnOnce(usize) -> Result<(), hal::DeviceError>, current_entry: &mut Option, size: u64, ) -> Result { let index = if let Some(current_entry) = current_entry.as_mut() { if current_entry.offset + size <= BUFFER_SIZE.get() { let entry_data = current_entry.clone(); current_entry.offset += size; return Ok(entry_data); } else { current_entry.index + 1 } } else { 0 }; ensure_entry(index).map_err(DeviceError::from_hal)?; let entry_data = CurrentEntry { index, offset: 0 }; *current_entry = Some(CurrentEntry { index, offset: size, }); Ok(entry_data) } } /// This must match the `MetadataEntry` struct used by the shader. #[repr(C)] struct MetadataEntry { src_offset: u32, dst_offset: u32, vertex_or_index_limit: u32, instance_limit: u32, } impl MetadataEntry { fn new( indexed: bool, src_offset: u64, dst_offset: u64, vertex_or_index_limit: u64, instance_limit: u64, ) -> Self { const U32_MAX_AS_U64: u64 = u32::MAX as u64; // NOTE: buffer sizes should never exceed `u32::MAX`. assert!(src_offset <= U32_MAX_AS_U64); assert!(dst_offset <= U32_MAX_AS_U64); let src_offset = src_offset as u32; let src_offset = src_offset / 4; // translate byte offset to offset in u32's // `src_offset` needs at most 30 bits, // pack `indexed` in bit 31 of `src_offset` let src_offset = src_offset | ((indexed as u32) << 31); // max value for limits since first_X and X_count indirect draw arguments are u32 let max_limit = U32_MAX_AS_U64 + U32_MAX_AS_U64; // 1 11111111 11111111 11111111 11111110 let vertex_or_index_limit = vertex_or_index_limit.min(max_limit); let vertex_or_index_limit_bit_32 = (vertex_or_index_limit >> 32) as u32; // extract bit 32 let vertex_or_index_limit = vertex_or_index_limit as u32; // truncate the limit to a u32 let instance_limit = instance_limit.min(max_limit); let instance_limit_bit_32 = (instance_limit >> 32) as u32; // extract bit 32 let instance_limit = instance_limit as u32; // truncate the limit to a u32 let dst_offset = dst_offset as u32; let dst_offset = dst_offset / 4; // translate byte offset to offset in u32's // `dst_offset` needs at most 30 bits, // pack `vertex_or_index_limit_bit_32` in bit 30 of `dst_offset` and // pack `instance_limit_bit_32` in bit 31 of `dst_offset` let dst_offset = dst_offset | (vertex_or_index_limit_bit_32 << 30) | (instance_limit_bit_32 << 31); Self { src_offset, dst_offset, vertex_or_index_limit, instance_limit, } } } struct DrawIndirectValidationBatch { src_buffer: Arc, src_dynamic_offset: u64, dst_resource_index: usize, entries: Vec, staging_buffer_index: usize, staging_buffer_offset: u64, metadata_resource_index: usize, metadata_buffer_offset: u64, } impl DrawIndirectValidationBatch { /// Data to be written to the metadata buffer. fn metadata(&self) -> &[u8] { unsafe { core::slice::from_raw_parts( self.entries.as_ptr().cast::(), self.entries.len() * size_of::(), ) } } } /// Accumulates all needed data needed to validate indirect draws. pub(crate) struct DrawBatcher { batches: FastHashMap<(TrackerIndex, u64, usize), DrawIndirectValidationBatch>, current_dst_entry: Option, } impl DrawBatcher { pub(crate) fn new() -> Self { Self { batches: FastHashMap::default(), current_dst_entry: None, } } /// Add an indirect draw to be validated. /// /// Returns the index of the indirect buffer in `indirect_draw_validation_resources` /// and the offset to be used for the draw. pub(crate) fn add<'a>( &mut self, indirect_draw_validation_resources: &'a mut DrawResources, device: &Device, src_buffer: &Arc, offset: u64, family: crate::command::DrawCommandFamily, vertex_or_index_limit: u64, instance_limit: u64, ) -> Result<(usize, u64), DeviceError> { // space for D3D12 special constants let extra = if device.backend() == wgt::Backend::Dx12 { 3 * size_of::() as u64 } else { 0 }; let stride = extra + crate::command::get_stride_of_indirect_args(family); let (dst_resource_index, dst_offset) = indirect_draw_validation_resources .get_dst_subrange(stride, &mut self.current_dst_entry)?; let buffer_size = src_buffer.size; let limits = device.adapter.limits(); let (src_dynamic_offset, src_offset) = calculate_src_offsets(buffer_size, &limits, offset); let src_buffer_tracker_index = src_buffer.tracker_index(); let entry = MetadataEntry::new( family == crate::command::DrawCommandFamily::DrawIndexed, src_offset, dst_offset, vertex_or_index_limit, instance_limit, ); match self.batches.entry(( src_buffer_tracker_index, src_dynamic_offset, dst_resource_index, )) { hashbrown::hash_map::Entry::Occupied(mut occupied_entry) => { occupied_entry.get_mut().entries.push(entry) } hashbrown::hash_map::Entry::Vacant(vacant_entry) => { vacant_entry.insert(DrawIndirectValidationBatch { src_buffer: src_buffer.clone(), src_dynamic_offset, dst_resource_index, entries: vec![entry], // these will be initialized once we accumulated all entries for the batch staging_buffer_index: 0, staging_buffer_offset: 0, metadata_resource_index: 0, metadata_buffer_offset: 0, }); } } Ok((dst_resource_index, dst_offset)) } } ================================================ FILE: wgpu-core/src/indirect_validation/mod.rs ================================================ use crate::{ device::DeviceError, pipeline::{CreateComputePipelineError, CreateShaderModuleError}, }; use alloc::boxed::Box; use thiserror::Error; mod dispatch; mod draw; mod utils; pub(crate) use dispatch::Dispatch; pub(crate) use draw::{Draw, DrawBatcher, DrawResources}; #[derive(Clone, Debug, Error)] #[non_exhaustive] enum CreateIndirectValidationPipelineError { #[error(transparent)] DeviceError(#[from] DeviceError), #[error(transparent)] ShaderModule(#[from] CreateShaderModuleError), #[error(transparent)] ComputePipeline(#[from] CreateComputePipelineError), } pub(crate) struct IndirectValidation { pub(crate) dispatch: Dispatch, pub(crate) draw: Draw, } impl IndirectValidation { pub(crate) fn new( device: &dyn hal::DynDevice, required_limits: &wgt::Limits, required_features: &wgt::Features, instance_flags: wgt::InstanceFlags, backend: wgt::Backend, ) -> Result { let dispatch = match Dispatch::new(device, instance_flags, required_limits) { Ok(dispatch) => dispatch, Err(e) => { log::error!("indirect-validation error: {e:?}"); return Err(DeviceError::Lost); } }; let draw = match Draw::new(device, required_features, instance_flags, backend) { Ok(draw) => draw, Err(e) => { log::error!("indirect-draw-validation error: {e:?}"); return Err(DeviceError::Lost); } }; Ok(Self { dispatch, draw }) } pub(crate) fn dispose(self, device: &dyn hal::DynDevice) { let Self { dispatch, draw } = self; dispatch.dispose(device); draw.dispose(device); } } #[derive(Debug)] pub(crate) struct BindGroups { pub(crate) dispatch: Box, draw: Box, } impl BindGroups { /// `Ok(None)` will only be returned if `buffer_size` is `0`. pub(crate) fn new( indirect_validation: &IndirectValidation, device: &crate::device::Device, buffer_size: u64, buffer: &dyn hal::DynBuffer, ) -> Result, DeviceError> { let dispatch = indirect_validation.dispatch.create_src_bind_group( device.raw(), &device.limits, buffer_size, buffer, device.instance_flags, )?; let draw = indirect_validation.draw.create_src_bind_group( device.raw(), &device.adapter.limits(), buffer_size, buffer, device.instance_flags, )?; match (dispatch, draw) { (None, None) => Ok(None), (None, Some(_)) => unreachable!(), (Some(_), None) => unreachable!(), (Some(dispatch), Some(draw)) => Ok(Some(Self { dispatch, draw })), } } pub(crate) fn dispose(self, device: &dyn hal::DynDevice) { let Self { dispatch, draw } = self; unsafe { device.destroy_bind_group(dispatch); device.destroy_bind_group(draw); } } } ================================================ FILE: wgpu-core/src/indirect_validation/utils.rs ================================================ use alloc::vec::Vec; pub(crate) struct UniqueIndexScratch(bit_set::BitSet); impl UniqueIndexScratch { pub(crate) fn new() -> Self { Self(bit_set::BitSet::new()) } } pub(crate) struct UniqueIndex<'a, I: Iterator> { inner: I, scratch: &'a mut UniqueIndexScratch, } impl<'a, I: Iterator> UniqueIndex<'a, I> { fn new(inner: I, scratch: &'a mut UniqueIndexScratch) -> Self { scratch.0.make_empty(); Self { inner, scratch } } } impl<'a, I: Iterator> Iterator for UniqueIndex<'a, I> { type Item = usize; fn next(&mut self) -> Option { self.inner.find(|&i| self.scratch.0.insert(i)) } } pub(crate) trait UniqueIndexExt: Iterator { fn unique<'a>(self, scratch: &'a mut UniqueIndexScratch) -> UniqueIndex<'a, Self> where Self: Sized, { UniqueIndex::new(self, scratch) } } impl> UniqueIndexExt for T {} type BufferBarrier<'b> = hal::BufferBarrier<'b, dyn hal::DynBuffer>; pub(crate) struct BufferBarrierScratch<'b>(Vec>); impl<'b> BufferBarrierScratch<'b> { pub(crate) fn new() -> Self { Self(Vec::new()) } } pub(crate) struct BufferBarriers<'a, 'b> { scratch: &'a mut BufferBarrierScratch<'b>, } impl<'a, 'b> BufferBarriers<'a, 'b> { pub(crate) fn new(scratch: &'a mut BufferBarrierScratch<'_>) -> Self { // change lifetime of buffer reference, this is safe since `scratch` is empty, // it was either just created or it has been cleared on `BufferBarriers::drop` let scratch = unsafe { core::mem::transmute::<&'a mut BufferBarrierScratch<'_>, &'a mut BufferBarrierScratch<'b>>( scratch, ) }; Self { scratch } } pub(crate) fn extend(self, iter: impl Iterator>) -> Self { self.scratch.0.extend(iter); self } pub(crate) fn encode(self, encoder: &mut dyn hal::DynCommandEncoder) { unsafe { encoder.transition_buffers(&self.scratch.0); } } } impl<'a, 'b> Drop for BufferBarriers<'a, 'b> { fn drop(&mut self) { self.scratch.0.clear(); } } ================================================ FILE: wgpu-core/src/indirect_validation/validate_draw.wgsl ================================================ override supports_indirect_first_instance: bool; override write_d3d12_special_constants: bool; struct MetadataEntry { // bits 0..30 are an offset into `src` // bit 31 signifies that we are validating an indexed draw src_offset: u32, // bits 0..30 are an offset into `dst` // bit 30 is the most significant bit of `vertex_or_index_limit` // bit 31 is the most significant bit of `instance_limit` dst_offset: u32, vertex_or_index_limit: u32, instance_limit: u32, } struct MetadataRange { start: u32, count: u32, } var metadata_range: MetadataRange; @group(0) @binding(0) var metadata: array; @group(1) @binding(0) var src: array; @group(2) @binding(0) var dst: array; fn is_bit_set(data: u32, index: u32) -> bool { return ((data >> index) & 1u) == 1u; } @compute @workgroup_size(64) fn main(@builtin(global_invocation_id) global_invocation_id: vec3u) { if global_invocation_id.x >= metadata_range.count { return; } let metadata = metadata[metadata_range.start + global_invocation_id.x]; var failed = false; let is_indexed = is_bit_set(metadata.src_offset, 31); let src_base_offset = ((metadata.src_offset << 2) >> 2); let dst_base_offset = ((metadata.dst_offset << 2) >> 2); let first_vertex_or_index = src[src_base_offset + 2]; let vertex_or_index_count = src[src_base_offset + 0]; { let can_overflow = is_bit_set(metadata.dst_offset, 30); let sub_overflows = metadata.vertex_or_index_limit < first_vertex_or_index; failed |= sub_overflows && !can_overflow; let vertex_or_index_limit = metadata.vertex_or_index_limit - first_vertex_or_index; failed |= vertex_or_index_limit < vertex_or_index_count; } let first_instance = src[src_base_offset + 3 + u32(is_indexed)]; let instance_count = src[src_base_offset + 1]; { let can_overflow = is_bit_set(metadata.dst_offset, 31); let sub_overflows = metadata.instance_limit < first_instance; failed |= sub_overflows && !can_overflow; let instance_limit = metadata.instance_limit - first_instance; failed |= instance_limit < instance_count; } if !supports_indirect_first_instance { failed |= first_instance != 0u; } let dst_offset = select(0u, 3u, write_d3d12_special_constants); if failed { if write_d3d12_special_constants { dst[dst_base_offset + 0] = 0u; dst[dst_base_offset + 1] = 0u; dst[dst_base_offset + 2] = 0u; } dst[dst_base_offset + dst_offset + 0] = 0u; dst[dst_base_offset + dst_offset + 1] = 0u; dst[dst_base_offset + dst_offset + 2] = 0u; dst[dst_base_offset + dst_offset + 3] = 0u; if (is_indexed) { dst[dst_base_offset + dst_offset + 4] = 0u; } } else { if write_d3d12_special_constants { dst[dst_base_offset + 0] = src[src_base_offset + 2 + u32(is_indexed)]; dst[dst_base_offset + 1] = src[src_base_offset + 3 + u32(is_indexed)]; dst[dst_base_offset + 2] = 0u; } dst[dst_base_offset + dst_offset + 0] = src[src_base_offset + 0]; dst[dst_base_offset + dst_offset + 1] = src[src_base_offset + 1]; dst[dst_base_offset + dst_offset + 2] = src[src_base_offset + 2]; dst[dst_base_offset + dst_offset + 3] = src[src_base_offset + 3]; if (is_indexed) { dst[dst_base_offset + dst_offset + 4] = src[src_base_offset + 4]; } } } ================================================ FILE: wgpu-core/src/init_tracker/buffer.rs ================================================ use super::{InitTracker, MemoryInitKind}; use crate::resource::Buffer; use alloc::sync::Arc; use core::ops::Range; #[derive(Debug, Clone)] pub(crate) struct BufferInitTrackerAction { pub buffer: Arc, pub range: Range, pub kind: MemoryInitKind, } pub(crate) type BufferInitTracker = InitTracker; impl BufferInitTracker { /// Checks if an action has/requires any effect on the initialization status /// and shrinks its range if possible. pub(crate) fn check_action( &self, action: &BufferInitTrackerAction, ) -> Option { self.create_action(&action.buffer, action.range.clone(), action.kind) } /// Creates an action if it would have any effect on the initialization /// status and shrinks the range if possible. pub(crate) fn create_action( &self, buffer: &Arc, query_range: Range, kind: MemoryInitKind, ) -> Option { self.check(query_range) .map(|range| BufferInitTrackerAction { buffer: buffer.clone(), range, kind, }) } } ================================================ FILE: wgpu-core/src/init_tracker/mod.rs ================================================ /*! Lazy initialization of texture and buffer memory. The WebGPU specification requires all texture & buffer memory to be zero initialized on first read. To avoid unnecessary inits, we track the initialization status of every resource and perform inits lazily. The granularity is different for buffers and textures: - Buffer: Byte granularity to support usecases with large, partially bound buffers well. - Texture: Mip-level per layer. That is, a 2D surface is either completely initialized or not, subrects are not tracked. Every use of a buffer/texture generates a InitTrackerAction which are recorded and later resolved at queue submit by merging them with the current state and each other in execution order. It is important to note that from the point of view of the memory init system there are two kind of writes: - **Full writes**: Any kind of memcpy operation. These cause a `MemoryInitKind.ImplicitlyInitialized` action. - **(Potentially) partial writes**: For example, write use in a Shader. The system is not able to determine if a resource is fully initialized afterwards but is no longer allowed to perform any clears, therefore this leads to a `MemoryInitKind.NeedsInitializedMemory` action, exactly like a read would. */ use core::{fmt, iter, ops::Range}; use smallvec::SmallVec; mod buffer; mod texture; pub(crate) use buffer::{BufferInitTracker, BufferInitTrackerAction}; pub(crate) use texture::{ has_copy_partial_init_tracker_coverage, TextureInitRange, TextureInitTracker, TextureInitTrackerAction, }; #[derive(Debug, Clone, Copy)] pub(crate) enum MemoryInitKind { // The memory range is going to be written by an already initialized source, // thus doesn't need extra attention other than marking as initialized. ImplicitlyInitialized, // The memory range is going to be read, therefore needs to ensure prior // initialization. NeedsInitializedMemory, } // Most of the time a resource is either fully uninitialized (one element) or // initialized (zero elements). type UninitializedRangeVec = SmallVec<[Range; 1]>; /// Tracks initialization status of a linear range from 0..size #[derive(Debug, Clone)] pub(crate) struct InitTracker { /// Non-overlapping list of all uninitialized ranges, sorted by /// range end. uninitialized_ranges: UninitializedRangeVec, } pub(crate) struct UninitializedIter<'a, Idx: fmt::Debug + Ord + Copy> { uninitialized_ranges: &'a UninitializedRangeVec, drain_range: Range, next_index: usize, } impl<'a, Idx> Iterator for UninitializedIter<'a, Idx> where Idx: fmt::Debug + Ord + Copy, { type Item = Range; fn next(&mut self) -> Option { self.uninitialized_ranges .get(self.next_index) .and_then(|range| { if range.start < self.drain_range.end { self.next_index += 1; Some( range.start.max(self.drain_range.start) ..range.end.min(self.drain_range.end), ) } else { None } }) } } pub(crate) struct InitTrackerDrain<'a, Idx: fmt::Debug + Ord + Copy> { uninitialized_ranges: &'a mut UninitializedRangeVec, drain_range: Range, first_index: usize, next_index: usize, } impl<'a, Idx> Iterator for InitTrackerDrain<'a, Idx> where Idx: fmt::Debug + Ord + Copy, { type Item = Range; fn next(&mut self) -> Option { if let Some(r) = self .uninitialized_ranges .get(self.next_index) .and_then(|range| { if range.start < self.drain_range.end { Some(range.clone()) } else { None } }) { self.next_index += 1; Some(r.start.max(self.drain_range.start)..r.end.min(self.drain_range.end)) } else { let num_affected = self.next_index - self.first_index; if num_affected == 0 { return None; } let first_range = &mut self.uninitialized_ranges[self.first_index]; // Split one "big" uninitialized range? if num_affected == 1 && first_range.start < self.drain_range.start && first_range.end > self.drain_range.end { let old_start = first_range.start; first_range.start = self.drain_range.end; self.uninitialized_ranges .insert(self.first_index, old_start..self.drain_range.start); } // Adjust border ranges and delete everything in-between. else { let remove_start = if first_range.start >= self.drain_range.start { self.first_index } else { first_range.end = self.drain_range.start; self.first_index + 1 }; let last_range = &mut self.uninitialized_ranges[self.next_index - 1]; let remove_end = if last_range.end <= self.drain_range.end { self.next_index } else { last_range.start = self.drain_range.end; self.next_index - 1 }; self.uninitialized_ranges.drain(remove_start..remove_end); } None } } } impl<'a, Idx> Drop for InitTrackerDrain<'a, Idx> where Idx: fmt::Debug + Ord + Copy, { fn drop(&mut self) { if self.next_index <= self.first_index { for _ in self {} } } } impl InitTracker where Idx: fmt::Debug + Ord + Copy + Default, { pub(crate) fn new(size: Idx) -> Self { Self { uninitialized_ranges: iter::once(Idx::default()..size).collect(), } } /// Checks for uninitialized ranges within a given query range. /// /// If `query_range` includes any uninitialized portions of this init /// tracker's resource, return the smallest subrange of `query_range` that /// covers all uninitialized regions. /// /// The returned range may be larger than necessary, to keep this function /// O(log n). pub(crate) fn check(&self, query_range: Range) -> Option> { let index = self .uninitialized_ranges .partition_point(|r| r.end <= query_range.start); self.uninitialized_ranges .get(index) .and_then(|start_range| { if start_range.start < query_range.end { let start = start_range.start.max(query_range.start); match self.uninitialized_ranges.get(index + 1) { Some(next_range) => { if next_range.start < query_range.end { // Would need to keep iterating for more // accurate upper bound. Don't do that here. Some(start..query_range.end) } else { Some(start..start_range.end.min(query_range.end)) } } None => Some(start..start_range.end.min(query_range.end)), } } else { None } }) } // Returns an iterator over the uninitialized ranges in a query range. pub(crate) fn uninitialized(&mut self, drain_range: Range) -> UninitializedIter<'_, Idx> { let index = self .uninitialized_ranges .partition_point(|r| r.end <= drain_range.start); UninitializedIter { drain_range, uninitialized_ranges: &self.uninitialized_ranges, next_index: index, } } // Drains uninitialized ranges in a query range. pub(crate) fn drain(&mut self, drain_range: Range) -> InitTrackerDrain<'_, Idx> { let index = self .uninitialized_ranges .partition_point(|r| r.end <= drain_range.start); InitTrackerDrain { drain_range, uninitialized_ranges: &mut self.uninitialized_ranges, first_index: index, next_index: index, } } } impl InitTracker { // Makes a single entry uninitialized if not already uninitialized pub(crate) fn discard(&mut self, pos: u32) { // first range where end>=idx let r_idx = self.uninitialized_ranges.partition_point(|r| r.end < pos); if let Some(r) = self.uninitialized_ranges.get(r_idx) { // Extend range at end if r.end == pos { // merge with next? if let Some(right) = self.uninitialized_ranges.get(r_idx + 1) { if right.start == pos + 1 { self.uninitialized_ranges[r_idx] = r.start..right.end; self.uninitialized_ranges.remove(r_idx + 1); return; } } self.uninitialized_ranges[r_idx] = r.start..(pos + 1); } else if r.start > pos { // may still extend range at beginning if r.start == pos + 1 { self.uninitialized_ranges[r_idx] = pos..r.end; } else { // previous range end must be smaller than idx, therefore no merge possible self.uninitialized_ranges.push(pos..(pos + 1)); } } } else { self.uninitialized_ranges.push(pos..(pos + 1)); } } } #[cfg(test)] mod test { use alloc::{vec, vec::Vec}; use core::ops::Range; type Tracker = super::InitTracker; #[test] fn check_for_newly_created_tracker() { let tracker = Tracker::new(10); assert_eq!(tracker.check(0..10), Some(0..10)); assert_eq!(tracker.check(0..3), Some(0..3)); assert_eq!(tracker.check(3..4), Some(3..4)); assert_eq!(tracker.check(4..10), Some(4..10)); } #[test] fn check_for_drained_tracker() { let mut tracker = Tracker::new(10); tracker.drain(0..10); assert_eq!(tracker.check(0..10), None); assert_eq!(tracker.check(0..3), None); assert_eq!(tracker.check(3..4), None); assert_eq!(tracker.check(4..10), None); } #[test] fn check_for_partially_filled_tracker() { let mut tracker = Tracker::new(25); // Two regions of uninitialized memory tracker.drain(0..5); tracker.drain(10..15); tracker.drain(20..25); assert_eq!(tracker.check(0..25), Some(5..25)); // entire range assert_eq!(tracker.check(0..5), None); // left non-overlapping assert_eq!(tracker.check(3..8), Some(5..8)); // left overlapping region assert_eq!(tracker.check(3..17), Some(5..17)); // left overlapping region + contained region // right overlapping region + contained region (yes, doesn't fix range end!) assert_eq!(tracker.check(8..22), Some(8..22)); // right overlapping region assert_eq!(tracker.check(17..22), Some(17..20)); // right non-overlapping assert_eq!(tracker.check(20..25), None); } #[test] fn drain_already_drained() { let mut tracker = Tracker::new(30); tracker.drain(10..20); // Overlapping with non-cleared tracker.drain(5..15); // Left overlap tracker.drain(15..25); // Right overlap tracker.drain(0..30); // Inner overlap // Clear fully cleared tracker.drain(0..30); assert_eq!(tracker.check(0..30), None); } #[test] fn drain_never_returns_ranges_twice_for_same_range() { let mut tracker = Tracker::new(19); assert_eq!(tracker.drain(0..19).count(), 1); assert_eq!(tracker.drain(0..19).count(), 0); let mut tracker = Tracker::new(17); assert_eq!(tracker.drain(5..8).count(), 1); assert_eq!(tracker.drain(5..8).count(), 0); assert_eq!(tracker.drain(1..3).count(), 1); assert_eq!(tracker.drain(1..3).count(), 0); assert_eq!(tracker.drain(7..13).count(), 1); assert_eq!(tracker.drain(7..13).count(), 0); } #[test] fn drain_splits_ranges_correctly() { let mut tracker = Tracker::new(1337); assert_eq!( tracker.drain(21..42).collect::>>(), vec![21..42] ); assert_eq!( tracker.drain(900..1000).collect::>>(), vec![900..1000] ); // Split ranges. assert_eq!( tracker.drain(5..1003).collect::>>(), vec![5..21, 42..900, 1000..1003] ); assert_eq!( tracker.drain(0..1337).collect::>>(), vec![0..5, 1003..1337] ); } #[test] fn discard_adds_range_on_cleared() { let mut tracker = Tracker::new(10); tracker.drain(0..10); tracker.discard(0); tracker.discard(5); tracker.discard(9); assert_eq!(tracker.check(0..1), Some(0..1)); assert_eq!(tracker.check(1..5), None); assert_eq!(tracker.check(5..6), Some(5..6)); assert_eq!(tracker.check(6..9), None); assert_eq!(tracker.check(9..10), Some(9..10)); } #[test] fn discard_does_nothing_on_uncleared() { let mut tracker = Tracker::new(10); tracker.discard(0); tracker.discard(5); tracker.discard(9); assert_eq!(tracker.uninitialized_ranges.len(), 1); assert_eq!(tracker.uninitialized_ranges[0], 0..10); } #[test] fn discard_extends_ranges() { let mut tracker = Tracker::new(10); tracker.drain(3..7); tracker.discard(2); tracker.discard(7); assert_eq!(tracker.uninitialized_ranges.len(), 2); assert_eq!(tracker.uninitialized_ranges[0], 0..3); assert_eq!(tracker.uninitialized_ranges[1], 7..10); } #[test] fn discard_merges_ranges() { let mut tracker = Tracker::new(10); tracker.drain(3..4); tracker.discard(3); assert_eq!(tracker.uninitialized_ranges.len(), 1); assert_eq!(tracker.uninitialized_ranges[0], 0..10); } } ================================================ FILE: wgpu-core/src/init_tracker/texture.rs ================================================ use super::{InitTracker, MemoryInitKind}; use crate::resource::Texture; use alloc::{sync::Arc, vec::Vec}; use arrayvec::ArrayVec; use core::ops::Range; use wgt::TextureSelector; #[derive(Debug, Clone)] pub(crate) struct TextureInitRange { pub(crate) mip_range: Range, // Strictly array layers. We do *not* track volume slices separately. pub(crate) layer_range: Range, } // Returns true if a copy operation doesn't fully cover the texture init // tracking granularity. I.e. if this function returns true for a pending copy // operation, the target texture needs to be ensured to be initialized first! pub(crate) fn has_copy_partial_init_tracker_coverage( copy_size: &wgt::Extent3d, mip_level: u32, desc: &wgt::TextureDescriptor<(), Vec>, ) -> bool { let target_size = desc.mip_level_size(mip_level).unwrap(); copy_size.width != target_size.width || copy_size.height != target_size.height || (desc.dimension == wgt::TextureDimension::D3 && copy_size.depth_or_array_layers != target_size.depth_or_array_layers) } impl From for TextureInitRange { fn from(selector: TextureSelector) -> Self { TextureInitRange { mip_range: selector.mips, layer_range: selector.layers, } } } #[derive(Debug, Clone)] pub(crate) struct TextureInitTrackerAction { pub(crate) texture: Arc, pub(crate) range: TextureInitRange, pub(crate) kind: MemoryInitKind, } pub(crate) type TextureLayerInitTracker = InitTracker; #[derive(Debug)] pub(crate) struct TextureInitTracker { pub mips: ArrayVec, } impl TextureInitTracker { pub(crate) fn new(mip_level_count: u32, depth_or_array_layers: u32) -> Self { TextureInitTracker { mips: core::iter::repeat_n( TextureLayerInitTracker::new(depth_or_array_layers), mip_level_count as usize, ) .collect(), } } pub(crate) fn check_action( &self, action: &TextureInitTrackerAction, ) -> Option { let mut mip_range_start = usize::MAX; let mut mip_range_end = usize::MIN; let mut layer_range_start = u32::MAX; let mut layer_range_end = u32::MIN; for (i, mip_tracker) in self .mips .iter() .enumerate() .take(action.range.mip_range.end as usize) .skip(action.range.mip_range.start as usize) { if let Some(uninitialized_layer_range) = mip_tracker.check(action.range.layer_range.clone()) { mip_range_start = mip_range_start.min(i); mip_range_end = i + 1; layer_range_start = layer_range_start.min(uninitialized_layer_range.start); layer_range_end = layer_range_end.max(uninitialized_layer_range.end); }; } if mip_range_start < mip_range_end && layer_range_start < layer_range_end { Some(TextureInitTrackerAction { texture: action.texture.clone(), range: TextureInitRange { mip_range: mip_range_start as u32..mip_range_end as u32, layer_range: layer_range_start..layer_range_end, }, kind: action.kind, }) } else { None } } pub(crate) fn discard(&mut self, mip_level: u32, layer: u32) { self.mips[mip_level as usize].discard(layer); } } ================================================ FILE: wgpu-core/src/instance.rs ================================================ use alloc::{ borrow::{Cow, ToOwned as _}, boxed::Box, string::String, sync::Arc, vec, vec::Vec, }; use hashbrown::HashMap; use thiserror::Error; use wgt::error::{ErrorType, WebGpuError}; use crate::{ api_log, api_log_debug, device::{queue::Queue, resource::Device, DeviceDescriptor, DeviceError}, global::Global, id::{markers, AdapterId, DeviceId, QueueId, SurfaceId}, lock::{rank, Mutex}, present::Presentation, resource::ResourceType, resource_log, timestamp_normalization::TimestampNormalizerInitError, DOWNLEVEL_WARNING_MESSAGE, }; use wgt::{Backend, Backends, PowerPreference}; pub type RequestAdapterOptions = wgt::RequestAdapterOptions; #[derive(Clone, Debug, Error)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[error("Limit '{name}' value {requested} is better than allowed {allowed}")] pub struct FailedLimit { name: Cow<'static, str>, requested: u64, allowed: u64, } impl WebGpuError for FailedLimit { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } } fn check_limits(requested: &wgt::Limits, allowed: &wgt::Limits) -> Vec { let mut failed = Vec::new(); requested.check_limits_with_fail_fn(allowed, false, |name, requested, allowed| { failed.push(FailedLimit { name: Cow::Borrowed(name), requested, allowed, }) }); failed } #[test] fn downlevel_default_limits_less_than_default_limits() { let res = check_limits(&wgt::Limits::downlevel_defaults(), &wgt::Limits::default()); assert!( res.is_empty(), "Downlevel limits are greater than default limits", ) } #[derive(Default)] pub struct Instance { _name: String, /// List of instances per `wgpu-hal` backend. /// /// The ordering in this list implies prioritization and needs to be preserved. instance_per_backend: Vec<(Backend, Box)>, /// The backends that were requested by the user. requested_backends: Backends, /// The backends that we could have attempted to obtain from `wgpu-hal` — /// those for which support is compiled in, currently. /// /// The union of this and `requested_backends` is the set of backends that would be used, /// independent of whether accessing the drivers/hardware for them succeeds. /// To obtain the set of backends actually in use by this instance, check /// `instance_per_backend` instead. supported_backends: Backends, pub flags: wgt::InstanceFlags, /// Non-lifetimed [`raw_window_handle::DisplayHandle`], for keepalive and validation purposes in /// [`Self::create_surface()`]. /// /// When used with `winit`, callers are expected to pass its `OwnedDisplayHandle` (created from /// the `EventLoop`) here. display: Option>, } impl Instance { pub fn new( name: &str, mut instance_desc: wgt::InstanceDescriptor, telemetry: Option, ) -> Self { let mut this = Self { _name: name.to_owned(), instance_per_backend: Vec::new(), requested_backends: instance_desc.backends, supported_backends: Backends::empty(), flags: instance_desc.flags, // HACK: We must take ownership of the field here, without being able to pass it into // try_add_hal(). Remove it from the mutable descriptor instead, while try_add_hal() // borrows the handle from `this.display` instead. display: instance_desc.display.take(), }; #[cfg(all(vulkan, not(target_os = "netbsd")))] this.try_add_hal(hal::api::Vulkan, &instance_desc, telemetry); #[cfg(metal)] this.try_add_hal(hal::api::Metal, &instance_desc, telemetry); #[cfg(dx12)] this.try_add_hal(hal::api::Dx12, &instance_desc, telemetry); #[cfg(gles)] this.try_add_hal(hal::api::Gles, &instance_desc, telemetry); #[cfg(feature = "noop")] this.try_add_hal(hal::api::Noop, &instance_desc, telemetry); this } /// Helper for `Instance::new()`; attempts to add a single `wgpu-hal` backend to this instance. fn try_add_hal( &mut self, _: A, instance_desc: &wgt::InstanceDescriptor, telemetry: Option, ) { // Whether or not the backend was requested, and whether or not it succeeds, // note that we *could* try it. self.supported_backends |= A::VARIANT.into(); if !instance_desc.backends.contains(A::VARIANT.into()) { log::trace!("Instance::new: backend {:?} not requested", A::VARIANT); return; } // If this was Some, it was moved into self assert!(instance_desc.display.is_none()); let hal_desc = hal::InstanceDescriptor { name: "wgpu", flags: self.flags, memory_budget_thresholds: instance_desc.memory_budget_thresholds, backend_options: instance_desc.backend_options.clone(), telemetry, // Pass a borrow, the core instance here keeps the owned handle alive already // WARNING: Using self here, not instance_desc! display: self.display.as_ref().map(|hdh| { hdh.display_handle() .expect("Implementation did not provide a DisplayHandle") }), }; use hal::Instance as _; // SAFETY: ??? match unsafe { A::Instance::init(&hal_desc) } { Ok(instance) => { log::debug!("Instance::new: created {:?} backend", A::VARIANT); self.instance_per_backend .push((A::VARIANT, Box::new(instance))); } Err(err) => { log::debug!( "Instance::new: failed to create {:?} backend: {:?}", A::VARIANT, err ); } } } pub(crate) fn from_hal_instance( name: String, hal_instance: ::Instance, ) -> Self { Self { _name: name, instance_per_backend: vec![(A::VARIANT, Box::new(hal_instance))], requested_backends: A::VARIANT.into(), supported_backends: A::VARIANT.into(), flags: wgt::InstanceFlags::default(), display: None, // TODO: Extract display from HAL instance if available? } } pub fn raw(&self, backend: Backend) -> Option<&dyn hal::DynInstance> { self.instance_per_backend .iter() .find_map(|(instance_backend, instance)| { (*instance_backend == backend).then(|| instance.as_ref()) }) } /// # Safety /// /// - The raw instance handle returned must not be manually destroyed. pub unsafe fn as_hal(&self) -> Option<&A::Instance> { self.raw(A::VARIANT).map(|instance| { instance .as_any() .downcast_ref() // This should be impossible. It would mean that backend instance and enum type are mismatching. .expect("Stored instance is not of the correct type") }) } /// Creates a new surface targeting the given display/window handles. /// /// Internally attempts to create hal surfaces for all enabled backends. /// /// Fails only if creation for surfaces for all enabled backends fails in which case /// the error for each enabled backend is listed. /// Vice versa, if creation for any backend succeeds, success is returned. /// Surface creation errors are logged to the debug log in any case. /// /// # Safety /// /// - `display_handle` must be a valid object to create a surface upon, /// falls back to the instance display handle otherwise. /// - `window_handle` must remain valid as long as the returned /// [`SurfaceId`] is being used. pub unsafe fn create_surface( &self, display_handle: Option, window_handle: raw_window_handle::RawWindowHandle, ) -> Result { profiling::scope!("Instance::create_surface"); let instance_display_handle = self.display.as_ref().map(|d| { d.display_handle() .expect("Implementation did not provide a DisplayHandle") .as_raw() }); let display_handle = match (instance_display_handle, display_handle) { (Some(a), Some(b)) => { if a != b { return Err(CreateSurfaceError::MismatchingDisplayHandle); } a } (Some(hnd), None) => hnd, (None, Some(hnd)) => hnd, (None, None) => return Err(CreateSurfaceError::MissingDisplayHandle), }; let mut errors = HashMap::default(); let mut surface_per_backend = HashMap::default(); for (backend, instance) in &self.instance_per_backend { match unsafe { instance .as_ref() .create_surface(display_handle, window_handle) } { Ok(raw) => { surface_per_backend.insert(*backend, raw); } Err(err) => { log::debug!( "Instance::create_surface: failed to create surface for {backend:?}: {err:?}" ); errors.insert(*backend, err); } } } if surface_per_backend.is_empty() { Err(CreateSurfaceError::FailedToCreateSurfaceForAnyBackend( errors, )) } else { let surface = Surface { presentation: Mutex::new(rank::SURFACE_PRESENTATION, None), surface_per_backend, }; Ok(surface) } } /// Creates a new surface from the given drm configuration. /// /// # Safety /// /// - All parameters must point to valid DRM values. /// /// # Platform Support /// /// This function is only available on non-apple Unix-like platforms (Linux, FreeBSD) and /// currently only works with the Vulkan backend. #[cfg(all( unix, not(target_vendor = "apple"), not(target_family = "wasm"), not(target_os = "netbsd") ))] #[cfg_attr(not(vulkan), expect(unused_variables))] pub unsafe fn create_surface_from_drm( &self, fd: i32, plane: u32, connector_id: u32, width: u32, height: u32, refresh_rate: u32, ) -> Result { profiling::scope!("Instance::create_surface_from_drm"); let mut errors = HashMap::default(); let mut surface_per_backend: HashMap> = HashMap::default(); #[cfg(all(vulkan, not(target_os = "netbsd")))] { let instance = unsafe { self.as_hal::() } .ok_or(CreateSurfaceError::BackendNotEnabled(Backend::Vulkan))?; // Safety must be upheld by the caller match unsafe { instance.create_surface_from_drm( fd, plane, connector_id, width, height, refresh_rate, ) } { Ok(surface) => { surface_per_backend.insert(Backend::Vulkan, Box::new(surface)); } Err(err) => { errors.insert(Backend::Vulkan, err); } } } if surface_per_backend.is_empty() { Err(CreateSurfaceError::FailedToCreateSurfaceForAnyBackend( errors, )) } else { let surface = Surface { presentation: Mutex::new(rank::SURFACE_PRESENTATION, None), surface_per_backend, }; Ok(surface) } } /// # Safety /// /// `layer` must be a valid pointer. #[cfg(metal)] pub unsafe fn create_surface_metal( &self, layer: *mut core::ffi::c_void, ) -> Result { profiling::scope!("Instance::create_surface_metal"); let instance = unsafe { self.as_hal::() } .ok_or(CreateSurfaceError::BackendNotEnabled(Backend::Metal))?; let layer = layer.cast(); // SAFETY: We do this cast and deref. (rather than using `metal` to get the // object we want) to avoid direct coupling on the `metal` crate. // // To wit, this pointer… // // - …is properly aligned. // - …is dereferenceable to a `MetalLayerRef` as an invariant of the `metal` // field. // - …points to an _initialized_ `MetalLayerRef`. // - …is only ever aliased via an immutable reference that lives within this // lexical scope. let layer = unsafe { &*layer }; let raw_surface: Box = Box::new(instance.create_surface_from_layer(layer)); let surface = Surface { presentation: Mutex::new(rank::SURFACE_PRESENTATION, None), surface_per_backend: core::iter::once((Backend::Metal, raw_surface)).collect(), }; Ok(surface) } #[cfg(dx12)] fn create_surface_dx12( &self, create_surface_func: impl FnOnce(&hal::dx12::Instance) -> hal::dx12::Surface, ) -> Result { let instance = unsafe { self.as_hal::() } .ok_or(CreateSurfaceError::BackendNotEnabled(Backend::Dx12))?; let surface: Box = Box::new(create_surface_func(instance)); let surface = Surface { presentation: Mutex::new(rank::SURFACE_PRESENTATION, None), surface_per_backend: core::iter::once((Backend::Dx12, surface)).collect(), }; Ok(surface) } #[cfg(dx12)] /// # Safety /// /// The visual must be valid and able to be used to make a swapchain with. pub unsafe fn create_surface_from_visual( &self, visual: *mut core::ffi::c_void, ) -> Result { profiling::scope!("Instance::instance_create_surface_from_visual"); self.create_surface_dx12(|inst| unsafe { inst.create_surface_from_visual(visual) }) } #[cfg(dx12)] /// # Safety /// /// The surface_handle must be valid and able to be used to make a swapchain with. pub unsafe fn create_surface_from_surface_handle( &self, surface_handle: *mut core::ffi::c_void, ) -> Result { profiling::scope!("Instance::instance_create_surface_from_surface_handle"); self.create_surface_dx12(|inst| unsafe { inst.create_surface_from_surface_handle(surface_handle) }) } #[cfg(dx12)] /// # Safety /// /// The swap_chain_panel must be valid and able to be used to make a swapchain with. pub unsafe fn create_surface_from_swap_chain_panel( &self, swap_chain_panel: *mut core::ffi::c_void, ) -> Result { profiling::scope!("Instance::instance_create_surface_from_swap_chain_panel"); self.create_surface_dx12(|inst| unsafe { inst.create_surface_from_swap_chain_panel(swap_chain_panel) }) } pub fn enumerate_adapters(&self, backends: Backends) -> Vec { profiling::scope!("Instance::enumerate_adapters"); api_log!("Instance::enumerate_adapters"); let mut adapters = Vec::new(); for (_backend, instance) in self .instance_per_backend .iter() .filter(|(backend, _)| backends.contains(Backends::from(*backend))) { // NOTE: We might be using `profiling` without any features. The empty backend of this // macro emits no code, so unused code linting changes depending on the backend. profiling::scope!("enumerating", &*alloc::format!("{_backend:?}")); let hal_adapters = unsafe { instance.enumerate_adapters(None) }; for raw in hal_adapters { let adapter = Adapter::new(raw); api_log_debug!("Adapter {:?}", adapter.raw.info); adapters.push(adapter); } } adapters } pub fn request_adapter( &self, desc: &wgt::RequestAdapterOptions<&Surface>, backends: Backends, ) -> Result { profiling::scope!("Instance::request_adapter"); api_log!("Instance::request_adapter"); let mut adapters = Vec::new(); let mut incompatible_surface_backends = Backends::empty(); let mut no_fallback_backends = Backends::empty(); let mut no_adapter_backends = Backends::empty(); for &(backend, ref instance) in self .instance_per_backend .iter() .filter(|&&(backend, _)| backends.contains(Backends::from(backend))) { let compatible_hal_surface = desc .compatible_surface .and_then(|surface| surface.raw(backend)); let mut backend_adapters = unsafe { instance.enumerate_adapters(compatible_hal_surface) }; if backend_adapters.is_empty() { log::debug!("enabled backend `{backend:?}` has no adapters"); no_adapter_backends |= Backends::from(backend); // by continuing, we avoid setting the further error bits below continue; } if desc.force_fallback_adapter { log::debug!("Filtering `{backend:?}` for `force_fallback_adapter`"); backend_adapters.retain(|exposed| { let keep = exposed.info.device_type == wgt::DeviceType::Cpu; if !keep { log::debug!("* Eliminating adapter `{}`", exposed.info.name); } keep }); if backend_adapters.is_empty() { log::debug!("* Backend `{backend:?}` has no fallback adapters"); no_fallback_backends |= Backends::from(backend); continue; } } if let Some(surface) = desc.compatible_surface { backend_adapters.retain(|exposed| { let capabilities = surface.get_capabilities_with_raw(exposed); if let Err(err) = capabilities { log::debug!( "Adapter {:?} not compatible with surface: {}", exposed.info, err ); incompatible_surface_backends |= Backends::from(backend); false } else { true } }); if backend_adapters.is_empty() { incompatible_surface_backends |= Backends::from(backend); continue; } } adapters.extend(backend_adapters); } match desc.power_preference { PowerPreference::LowPower => { sort(&mut adapters, true); } PowerPreference::HighPerformance => { sort(&mut adapters, false); } PowerPreference::None => {} }; fn sort(adapters: &mut [hal::DynExposedAdapter], prefer_integrated_gpu: bool) { adapters .sort_by_key(|adapter| get_order(adapter.info.device_type, prefer_integrated_gpu)); } fn get_order(device_type: wgt::DeviceType, prefer_integrated_gpu: bool) -> u8 { // Since devices of type "Other" might really be "Unknown" and come // from APIs like OpenGL that don't specify device type, Prefer more // Specific types over Other. // // This means that backends which do provide accurate device types // will be preferred if their device type indicates an actual // hardware GPU (integrated or discrete). match device_type { wgt::DeviceType::DiscreteGpu if prefer_integrated_gpu => 2, wgt::DeviceType::IntegratedGpu if prefer_integrated_gpu => 1, wgt::DeviceType::DiscreteGpu => 1, wgt::DeviceType::IntegratedGpu => 2, wgt::DeviceType::Other => 3, wgt::DeviceType::VirtualGpu => 4, wgt::DeviceType::Cpu => 5, } } // `request_adapter` can be a bit of a black box. // Shine some light on its decision in debug log. if adapters.is_empty() { log::debug!("Request adapter didn't find compatible adapters."); } else { log::debug!( "Found {} compatible adapters. Sorted by preference:", adapters.len() ); for adapter in &adapters { log::debug!("* {:?}", adapter.info); } } if let Some(adapter) = adapters.into_iter().next() { api_log_debug!("Request adapter result {:?}", adapter.info); let adapter = Adapter::new(adapter); Ok(adapter) } else { Err(wgt::RequestAdapterError::NotFound { supported_backends: self.supported_backends, requested_backends: self.requested_backends, active_backends: self.active_backends(), no_fallback_backends, no_adapter_backends, incompatible_surface_backends, }) } } fn active_backends(&self) -> Backends { self.instance_per_backend .iter() .map(|&(backend, _)| Backends::from(backend)) .collect() } } pub struct Surface { pub(crate) presentation: Mutex>, pub surface_per_backend: HashMap>, } impl ResourceType for Surface { const TYPE: &'static str = "Surface"; } impl crate::storage::StorageItem for Surface { type Marker = markers::Surface; } impl Surface { pub fn get_capabilities( &self, adapter: &Adapter, ) -> Result { self.get_capabilities_with_raw(&adapter.raw) } pub fn get_capabilities_with_raw( &self, adapter: &hal::DynExposedAdapter, ) -> Result { let backend = adapter.backend(); let suf = self .raw(backend) .ok_or(GetSurfaceSupportError::NotSupportedByBackend(backend))?; profiling::scope!("surface_capabilities"); let caps = unsafe { adapter.adapter.surface_capabilities(suf) } .ok_or(GetSurfaceSupportError::FailedToRetrieveSurfaceCapabilitiesForAdapter)?; Ok(caps) } pub fn raw(&self, backend: Backend) -> Option<&dyn hal::DynSurface> { self.surface_per_backend .get(&backend) .map(|surface| surface.as_ref()) } } impl Drop for Surface { fn drop(&mut self) { if let Some(present) = self.presentation.lock().take() { for (&backend, surface) in &self.surface_per_backend { if backend == present.device.backend() { unsafe { surface.unconfigure(present.device.raw()) }; } } } } } pub struct Adapter { pub(crate) raw: hal::DynExposedAdapter, } impl Adapter { pub fn new(mut raw: hal::DynExposedAdapter) -> Self { // WebGPU requires this offset alignment as lower bound on all adapters. const MIN_BUFFER_OFFSET_ALIGNMENT_LOWER_BOUND: u32 = 32; let limits = &mut raw.capabilities.limits; limits.min_uniform_buffer_offset_alignment = limits .min_uniform_buffer_offset_alignment .max(MIN_BUFFER_OFFSET_ALIGNMENT_LOWER_BOUND); limits.min_storage_buffer_offset_alignment = limits .min_storage_buffer_offset_alignment .max(MIN_BUFFER_OFFSET_ALIGNMENT_LOWER_BOUND); Self { raw } } /// Returns the backend this adapter is using. pub fn backend(&self) -> Backend { self.raw.backend() } pub fn is_surface_supported(&self, surface: &Surface) -> bool { // If get_capabilities returns Err, then the API does not advertise support for the surface. // // This could occur if the user is running their app on Wayland but Vulkan does not support // VK_KHR_wayland_surface. surface.get_capabilities(self).is_ok() } pub fn get_info(&self) -> wgt::AdapterInfo { self.raw.info.clone() } pub fn features(&self) -> wgt::Features { self.raw.features } pub fn limits(&self) -> wgt::Limits { self.raw.capabilities.limits.clone() } pub fn downlevel_capabilities(&self) -> wgt::DownlevelCapabilities { self.raw.capabilities.downlevel.clone() } pub fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp { unsafe { self.raw.adapter.get_presentation_timestamp() } } pub fn cooperative_matrix_properties(&self) -> Vec { self.raw.capabilities.cooperative_matrix_properties.clone() } pub fn get_texture_format_features( &self, format: wgt::TextureFormat, ) -> wgt::TextureFormatFeatures { use hal::TextureFormatCapabilities as Tfc; let caps = unsafe { self.raw.adapter.texture_format_capabilities(format) }; let mut allowed_usages = wgt::TextureUsages::empty(); allowed_usages.set(wgt::TextureUsages::COPY_SRC, caps.contains(Tfc::COPY_SRC)); allowed_usages.set(wgt::TextureUsages::COPY_DST, caps.contains(Tfc::COPY_DST)); allowed_usages.set( wgt::TextureUsages::TEXTURE_BINDING, caps.contains(Tfc::SAMPLED), ); allowed_usages.set( wgt::TextureUsages::STORAGE_BINDING, caps.intersects( Tfc::STORAGE_WRITE_ONLY | Tfc::STORAGE_READ_ONLY | Tfc::STORAGE_READ_WRITE | Tfc::STORAGE_ATOMIC, ), ); allowed_usages.set( wgt::TextureUsages::RENDER_ATTACHMENT | wgt::TextureUsages::TRANSIENT, caps.intersects(Tfc::COLOR_ATTACHMENT | Tfc::DEPTH_STENCIL_ATTACHMENT), ); allowed_usages.set( wgt::TextureUsages::STORAGE_ATOMIC, caps.contains(Tfc::STORAGE_ATOMIC), ); let mut flags = wgt::TextureFormatFeatureFlags::empty(); flags.set( wgt::TextureFormatFeatureFlags::STORAGE_READ_ONLY, caps.contains(Tfc::STORAGE_READ_ONLY), ); flags.set( wgt::TextureFormatFeatureFlags::STORAGE_WRITE_ONLY, caps.contains(Tfc::STORAGE_WRITE_ONLY), ); flags.set( wgt::TextureFormatFeatureFlags::STORAGE_READ_WRITE, caps.contains(Tfc::STORAGE_READ_WRITE), ); flags.set( wgt::TextureFormatFeatureFlags::STORAGE_ATOMIC, caps.contains(Tfc::STORAGE_ATOMIC), ); flags.set( wgt::TextureFormatFeatureFlags::FILTERABLE, caps.contains(Tfc::SAMPLED_LINEAR), ); flags.set( wgt::TextureFormatFeatureFlags::BLENDABLE, caps.contains(Tfc::COLOR_ATTACHMENT_BLEND), ); flags.set( wgt::TextureFormatFeatureFlags::MULTISAMPLE_X2, caps.contains(Tfc::MULTISAMPLE_X2), ); flags.set( wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4, caps.contains(Tfc::MULTISAMPLE_X4), ); flags.set( wgt::TextureFormatFeatureFlags::MULTISAMPLE_X8, caps.contains(Tfc::MULTISAMPLE_X8), ); flags.set( wgt::TextureFormatFeatureFlags::MULTISAMPLE_X16, caps.contains(Tfc::MULTISAMPLE_X16), ); flags.set( wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE, caps.contains(Tfc::MULTISAMPLE_RESOLVE), ); wgt::TextureFormatFeatures { allowed_usages, flags, } } fn create_device_and_queue_from_hal( self: &Arc, hal_device: hal::DynOpenDevice, desc: &DeviceDescriptor, instance_flags: wgt::InstanceFlags, ) -> Result<(Arc, Arc), RequestDeviceError> { api_log!("Adapter::create_device"); let device = Device::new(hal_device.device, self, desc, instance_flags)?; let device = Arc::new(device); let queue = Queue::new(device.clone(), hal_device.queue, instance_flags)?; let queue = Arc::new(queue); device.set_queue(&queue); device.late_init_resources_with_queue()?; Ok((device, queue)) } pub fn create_device_and_queue( self: &Arc, desc: &DeviceDescriptor, instance_flags: wgt::InstanceFlags, ) -> Result<(Arc, Arc), RequestDeviceError> { // Verify all features were exposed by the adapter if !self.raw.features.contains(desc.required_features) { return Err(RequestDeviceError::UnsupportedFeature( desc.required_features - self.raw.features, )); } // Check if experimental features are permitted to be enabled. if desc .required_features .intersects(wgt::Features::all_experimental_mask()) && !desc.experimental_features.is_enabled() { return Err(RequestDeviceError::ExperimentalFeaturesNotEnabled( desc.required_features .intersection(wgt::Features::all_experimental_mask()), )); } let caps = &self.raw.capabilities; if Backends::PRIMARY.contains(Backends::from(self.backend())) && !caps.downlevel.is_webgpu_compliant() { let missing_flags = wgt::DownlevelFlags::compliant() - caps.downlevel.flags; log::warn!("Missing downlevel flags: {missing_flags:?}\n{DOWNLEVEL_WARNING_MESSAGE}"); log::warn!("{:#?}", caps.downlevel); } // Verify feature preconditions if desc .required_features .contains(wgt::Features::MAPPABLE_PRIMARY_BUFFERS) && self.raw.info.device_type == wgt::DeviceType::DiscreteGpu { log::warn!( "Feature MAPPABLE_PRIMARY_BUFFERS enabled on a discrete gpu. \ This is a massive performance footgun and likely not what you wanted" ); } if let Some(failed) = check_limits(&desc.required_limits, &caps.limits).pop() { return Err(RequestDeviceError::LimitsExceeded(failed)); } let open = unsafe { self.raw.adapter.open( desc.required_features, &desc.required_limits, &desc.memory_hints, ) } .map_err(DeviceError::from_hal)?; self.create_device_and_queue_from_hal(open, desc, instance_flags) } } crate::impl_resource_type!(Adapter); crate::impl_storage_item!(Adapter); #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum GetSurfaceSupportError { #[error("Surface is not supported for the specified backend {0}")] NotSupportedByBackend(Backend), #[error("Failed to retrieve surface capabilities for the specified adapter.")] FailedToRetrieveSurfaceCapabilitiesForAdapter, } #[derive(Clone, Debug, Error)] /// Error when requesting a device from the adapter #[non_exhaustive] pub enum RequestDeviceError { #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] LimitsExceeded(#[from] FailedLimit), #[error("Failed to initialize Timestamp Normalizer")] TimestampNormalizerInitFailed(#[from] TimestampNormalizerInitError), #[error("Unsupported features were requested: {0}")] UnsupportedFeature(wgt::Features), #[error( "Some experimental features, {0}, were requested, but experimental features are not enabled" )] ExperimentalFeaturesNotEnabled(wgt::Features), } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateSurfaceError { #[error("The backend {0} was not enabled on the instance.")] BackendNotEnabled(Backend), #[error("Failed to create surface for any enabled backend: {0:?}")] FailedToCreateSurfaceForAnyBackend(HashMap), #[error("The display handle used to create this Instance does not match the one used to create a surface on it")] MismatchingDisplayHandle, #[error( "No `DisplayHandle` is available to create this surface with. When creating a surface with `create_surface()` \ you must specify a display handle in `InstanceDescriptor::display`. \ Rarely, if you need to create surfaces from different `DisplayHandle`s (ex. different Wayland or X11 connections), \ you must use `create_surface_unsafe()`." )] MissingDisplayHandle, } impl Global { /// Creates a new surface targeting the given display/window handles. /// /// Internally attempts to create hal surfaces for all enabled backends. /// /// Fails only if creation for surfaces for all enabled backends fails in which case /// the error for each enabled backend is listed. /// Vice versa, if creation for any backend succeeds, success is returned. /// Surface creation errors are logged to the debug log in any case. /// /// id_in: /// - If `Some`, the id to assign to the surface. A new one will be generated otherwise. /// /// # Safety /// /// - `display_handle` must be a valid object to create a surface upon, /// falls back to the instance display handle otherwise. /// - `window_handle` must remain valid as long as the returned /// [`SurfaceId`] is being used. pub unsafe fn instance_create_surface( &self, display_handle: Option, window_handle: raw_window_handle::RawWindowHandle, id_in: Option, ) -> Result { let surface = unsafe { self.instance.create_surface(display_handle, window_handle) }?; let id = self.surfaces.prepare(id_in).assign(Arc::new(surface)); Ok(id) } /// Creates a new surface from the given drm configuration. /// /// # Safety /// /// - All parameters must point to valid DRM values. /// /// # Platform Support /// /// This function is only available on non-apple Unix-like platforms (Linux, FreeBSD) and /// currently only works with the Vulkan backend. #[cfg(all( unix, not(target_vendor = "apple"), not(target_family = "wasm"), not(target_os = "netbsd") ))] pub unsafe fn instance_create_surface_from_drm( &self, fd: i32, plane: u32, connector_id: u32, width: u32, height: u32, refresh_rate: u32, id_in: Option, ) -> Result { let surface = unsafe { self.instance.create_surface_from_drm( fd, plane, connector_id, width, height, refresh_rate, ) }?; let id = self.surfaces.prepare(id_in).assign(Arc::new(surface)); Ok(id) } /// # Safety /// /// `layer` must be a valid pointer. #[cfg(metal)] pub unsafe fn instance_create_surface_metal( &self, layer: *mut core::ffi::c_void, id_in: Option, ) -> Result { let surface = unsafe { self.instance.create_surface_metal(layer) }?; let id = self.surfaces.prepare(id_in).assign(Arc::new(surface)); Ok(id) } #[cfg(dx12)] /// # Safety /// /// The visual must be valid and able to be used to make a swapchain with. pub unsafe fn instance_create_surface_from_visual( &self, visual: *mut core::ffi::c_void, id_in: Option, ) -> Result { let surface = unsafe { self.instance.create_surface_from_visual(visual) }?; let id = self.surfaces.prepare(id_in).assign(Arc::new(surface)); Ok(id) } #[cfg(dx12)] /// # Safety /// /// The surface_handle must be valid and able to be used to make a swapchain with. pub unsafe fn instance_create_surface_from_surface_handle( &self, surface_handle: *mut core::ffi::c_void, id_in: Option, ) -> Result { let surface = unsafe { self.instance .create_surface_from_surface_handle(surface_handle) }?; let id = self.surfaces.prepare(id_in).assign(Arc::new(surface)); Ok(id) } #[cfg(dx12)] /// # Safety /// /// The swap_chain_panel must be valid and able to be used to make a swapchain with. pub unsafe fn instance_create_surface_from_swap_chain_panel( &self, swap_chain_panel: *mut core::ffi::c_void, id_in: Option, ) -> Result { let surface = unsafe { self.instance .create_surface_from_swap_chain_panel(swap_chain_panel) }?; let id = self.surfaces.prepare(id_in).assign(Arc::new(surface)); Ok(id) } pub fn surface_drop(&self, id: SurfaceId) { profiling::scope!("Surface::drop"); api_log!("Surface::drop {id:?}"); self.surfaces.remove(id); } pub fn enumerate_adapters(&self, backends: Backends) -> Vec { let adapters = self.instance.enumerate_adapters(backends); adapters .into_iter() .map(|adapter| self.hub.adapters.prepare(None).assign(Arc::new(adapter))) .collect() } pub fn request_adapter( &self, desc: &RequestAdapterOptions, backends: Backends, id_in: Option, ) -> Result { let compatible_surface = desc.compatible_surface.map(|id| self.surfaces.get(id)); let desc = wgt::RequestAdapterOptions { power_preference: desc.power_preference, force_fallback_adapter: desc.force_fallback_adapter, compatible_surface: compatible_surface.as_deref(), }; let adapter = self.instance.request_adapter(&desc, backends)?; let id = self.hub.adapters.prepare(id_in).assign(Arc::new(adapter)); Ok(id) } /// # Safety /// /// `hal_adapter` must be created from this global internal instance handle. pub unsafe fn create_adapter_from_hal( &self, hal_adapter: hal::DynExposedAdapter, input: Option, ) -> AdapterId { profiling::scope!("Instance::create_adapter_from_hal"); let fid = self.hub.adapters.prepare(input); let id = fid.assign(Arc::new(Adapter::new(hal_adapter))); resource_log!("Created Adapter {:?}", id); id } pub fn adapter_get_info(&self, adapter_id: AdapterId) -> wgt::AdapterInfo { let adapter = self.hub.adapters.get(adapter_id); adapter.get_info() } pub fn adapter_get_texture_format_features( &self, adapter_id: AdapterId, format: wgt::TextureFormat, ) -> wgt::TextureFormatFeatures { let adapter = self.hub.adapters.get(adapter_id); adapter.get_texture_format_features(format) } pub fn adapter_features(&self, adapter_id: AdapterId) -> wgt::Features { let adapter = self.hub.adapters.get(adapter_id); adapter.features() } pub fn adapter_limits(&self, adapter_id: AdapterId) -> wgt::Limits { let adapter = self.hub.adapters.get(adapter_id); adapter.limits() } pub fn adapter_downlevel_capabilities( &self, adapter_id: AdapterId, ) -> wgt::DownlevelCapabilities { let adapter = self.hub.adapters.get(adapter_id); adapter.downlevel_capabilities() } pub fn adapter_get_presentation_timestamp( &self, adapter_id: AdapterId, ) -> wgt::PresentationTimestamp { let adapter = self.hub.adapters.get(adapter_id); adapter.get_presentation_timestamp() } pub fn adapter_cooperative_matrix_properties( &self, adapter_id: AdapterId, ) -> Vec { let adapter = self.hub.adapters.get(adapter_id); adapter.cooperative_matrix_properties() } pub fn adapter_drop(&self, adapter_id: AdapterId) { profiling::scope!("Adapter::drop"); api_log!("Adapter::drop {adapter_id:?}"); self.hub.adapters.remove(adapter_id); } } impl Global { pub fn adapter_request_device( &self, adapter_id: AdapterId, desc: &DeviceDescriptor, device_id_in: Option, queue_id_in: Option, ) -> Result<(DeviceId, QueueId), RequestDeviceError> { profiling::scope!("Adapter::request_device"); api_log!("Adapter::request_device"); let device_fid = self.hub.devices.prepare(device_id_in); let queue_fid = self.hub.queues.prepare(queue_id_in); let adapter = self.hub.adapters.get(adapter_id); let (device, queue) = adapter.create_device_and_queue(desc, self.instance.flags)?; let device_id = device_fid.assign(device); resource_log!("Created Device {:?}", device_id); let queue_id = queue_fid.assign(queue); resource_log!("Created Queue {:?}", queue_id); Ok((device_id, queue_id)) } /// # Safety /// /// - `hal_device` must be created from `adapter_id` or its internal handle. /// - `desc` must be a subset of `hal_device` features and limits. pub unsafe fn create_device_from_hal( &self, adapter_id: AdapterId, hal_device: hal::DynOpenDevice, desc: &DeviceDescriptor, device_id_in: Option, queue_id_in: Option, ) -> Result<(DeviceId, QueueId), RequestDeviceError> { profiling::scope!("Global::create_device_from_hal"); let devices_fid = self.hub.devices.prepare(device_id_in); let queues_fid = self.hub.queues.prepare(queue_id_in); let adapter = self.hub.adapters.get(adapter_id); let (device, queue) = adapter.create_device_and_queue_from_hal(hal_device, desc, self.instance.flags)?; let device_id = devices_fid.assign(device); resource_log!("Created Device {:?}", device_id); let queue_id = queues_fid.assign(queue); resource_log!("Created Queue {:?}", queue_id); Ok((device_id, queue_id)) } } ================================================ FILE: wgpu-core/src/lib.rs ================================================ //! This library safely implements WebGPU on native platforms. //! It is designed for integration into browsers, as well as wrapping //! into other language-specific user-friendly libraries. //! //! ## Feature flags #![doc = document_features::document_features!()] //! #![no_std] // When we have no backends, we end up with a lot of dead or otherwise unreachable code. #![cfg_attr( all( not(all(feature = "vulkan", not(target_arch = "wasm32"))), not(all(feature = "metal", any(target_vendor = "apple"))), not(all(feature = "dx12", windows)), not(feature = "gles"), ), allow(unused, clippy::let_and_return) )] #![cfg_attr(docsrs, feature(doc_cfg))] #![allow( // It is much clearer to assert negative conditions with eq! false clippy::bool_assert_comparison, // We don't use syntax sugar where it's not necessary. clippy::match_like_matches_macro, // Redundant matching is more explicit. clippy::redundant_pattern_matching, // Explicit lifetimes are often easier to reason about. clippy::needless_lifetimes, // No need for defaults in the internal types. clippy::new_without_default, // Needless updates are more scalable, easier to play with features. clippy::needless_update, // Need many arguments for some core functions to be able to re-use code in many situations. clippy::too_many_arguments, // It gets in the way a lot and does not prevent bugs in practice. clippy::pattern_type_mismatch, // `wgpu-core` isn't entirely user-facing, so it's useful to document internal items. rustdoc::private_intra_doc_links, )] #![warn( clippy::alloc_instead_of_core, clippy::ptr_as_ptr, clippy::std_instead_of_alloc, clippy::std_instead_of_core, trivial_casts, trivial_numeric_casts, unsafe_op_in_unsafe_fn, unused_extern_crates, unused_qualifications )] // We use `Arc` in wgpu-core, but on wasm (unless opted out via `fragile-send-sync-non-atomic-wasm`) // wgpu-hal resources are not Send/Sync, causing a clippy warning for unnecessary `Arc`s. // We could use `Rc`s in this case as recommended, but unless atomics are enabled // this doesn't make a difference. // Therefore, this is only really a concern for users targeting WebGL // (the only reason to use wgpu-core on the web in the first place) that have atomics enabled. // // NOTE: Keep this in sync with `wgpu`. #![cfg_attr(not(send_sync), allow(clippy::arc_with_non_send_sync))] extern crate alloc; #[cfg(feature = "std")] extern crate std; extern crate wgpu_hal as hal; extern crate wgpu_types as wgt; mod as_hal; pub mod binding_model; pub mod command; mod conv; pub mod device; pub mod error; pub mod global; mod hash_utils; pub mod hub; pub mod id; pub mod identity; mod indirect_validation; mod init_tracker; pub mod instance; mod lock; pub mod pipeline; mod pipeline_cache; mod pool; pub mod present; pub mod ray_tracing; pub mod registry; pub mod resource; mod snatch; pub mod storage; mod timestamp_normalization; mod track; mod weak_vec; // This is public for users who pre-compile shaders while still wanting to // preserve all run-time checks that `wgpu-core` does. // See , after which this can be // made private again. mod scratch; pub mod validation; pub use validation::{map_storage_format_from_naga, map_storage_format_to_naga}; pub use hal::{api, MAX_BIND_GROUPS, MAX_COLOR_ATTACHMENTS, MAX_VERTEX_BUFFERS}; pub use naga; use alloc::{ borrow::{Cow, ToOwned as _}, string::String, }; pub(crate) use hash_utils::*; /// The index of a queue submission. /// /// These are the values stored in `Device::fence`. pub type SubmissionIndex = hal::FenceValue; type Index = u32; type Epoch = u32; pub type RawString = *const core::ffi::c_char; pub type Label<'a> = Option>; trait LabelHelpers<'a> { fn to_hal(&'a self, flags: wgt::InstanceFlags) -> Option<&'a str>; fn to_string(&self) -> String; } impl<'a> LabelHelpers<'a> for Label<'a> { fn to_hal(&'a self, flags: wgt::InstanceFlags) -> Option<&'a str> { if flags.contains(wgt::InstanceFlags::DISCARD_HAL_LABELS) { return None; } self.as_deref() } fn to_string(&self) -> String { self.as_deref().map(str::to_owned).unwrap_or_default() } } pub fn hal_label>(opt: Option, flags: wgt::InstanceFlags) -> Option { if flags.contains(wgt::InstanceFlags::DISCARD_HAL_LABELS) { return None; } opt } const DOWNLEVEL_WARNING_MESSAGE: &str = concat!( "The underlying API or device in use does not ", "support enough features to be a fully compliant implementation of WebGPU. ", "A subset of the features can still be used. ", "If you are running this program on native and not in a browser and wish to limit ", "the features you use to the supported subset, ", "call Adapter::downlevel_properties or Device::downlevel_properties to get ", "a listing of the features the current ", "platform supports." ); const DOWNLEVEL_ERROR_MESSAGE: &str = concat!( "This is not an invalid use of WebGPU: the underlying API or device does not ", "support enough features to be a fully compliant implementation. ", "A subset of the features can still be used. ", "If you are running this program on native and not in a browser ", "and wish to work around this issue, call ", "Adapter::downlevel_properties or Device::downlevel_properties ", "to get a listing of the features the current platform supports." ); #[cfg(feature = "api_log_info")] macro_rules! api_log { ($($arg:tt)+) => (log::info!($($arg)+)) } #[cfg(not(feature = "api_log_info"))] macro_rules! api_log { ($($arg:tt)+) => (log::trace!($($arg)+)) } #[cfg(feature = "api_log_info")] macro_rules! api_log_debug { ($($arg:tt)+) => (log::info!($($arg)+)) } #[cfg(not(feature = "api_log_info"))] macro_rules! api_log_debug { ($($arg:tt)+) => (log::debug!($($arg)+)) } pub(crate) use api_log; pub(crate) use api_log_debug; #[cfg(feature = "resource_log_info")] macro_rules! resource_log { ($($arg:tt)+) => (log::info!($($arg)+)) } #[cfg(not(feature = "resource_log_info"))] macro_rules! resource_log { ($($arg:tt)+) => (log::trace!($($arg)+)) } pub(crate) use resource_log; #[inline] pub(crate) fn get_lowest_common_denom(a: u32, b: u32) -> u32 { let gcd = if a >= b { get_greatest_common_divisor(a, b) } else { get_greatest_common_divisor(b, a) }; a * b / gcd } #[inline] pub(crate) fn get_greatest_common_divisor(mut a: u32, mut b: u32) -> u32 { assert!(a >= b); loop { let c = a % b; if c == 0 { return b; } else { a = b; b = c; } } } #[cfg(not(feature = "std"))] use core::cell::OnceCell as OnceCellOrLock; #[cfg(feature = "std")] use std::sync::OnceLock as OnceCellOrLock; #[cfg(test)] mod tests { use super::*; #[test] fn test_lcd() { assert_eq!(get_lowest_common_denom(2, 2), 2); assert_eq!(get_lowest_common_denom(2, 3), 6); assert_eq!(get_lowest_common_denom(6, 4), 12); } #[test] fn test_gcd() { assert_eq!(get_greatest_common_divisor(5, 1), 1); assert_eq!(get_greatest_common_divisor(4, 2), 2); assert_eq!(get_greatest_common_divisor(6, 4), 2); assert_eq!(get_greatest_common_divisor(7, 7), 7); } } ================================================ FILE: wgpu-core/src/lock/mod.rs ================================================ //! Instrumented lock types. //! //! This module defines a set of instrumented wrappers for the lock //! types used in `wgpu-core` ([`Mutex`], [`RwLock`], and //! [`SnatchLock`]) that help us understand and validate `wgpu-core` //! synchronization. //! //! - The [`ranked`] module defines lock types that perform run-time //! checks to ensure that each thread acquires locks only in a //! specific order, to prevent deadlocks. //! //! - The [`observing`] module defines lock types that record //! `wgpu-core`'s lock acquisition activity to disk, for later //! analysis by the `lock-analyzer` binary. //! //! - The [`vanilla`] module defines lock types that are //! uninstrumented, no-overhead wrappers around the standard lock //! types. //! //! If the `wgpu_validate_locks` config is set (for example, with //! `RUSTFLAGS='--cfg wgpu_validate_locks'`), `wgpu-core` uses the //! [`ranked`] module's locks. We hope to make this the default for //! debug builds soon. //! //! If the `observe_locks` feature is enabled, `wgpu-core` uses the //! [`observing`] module's locks. //! //! Otherwise, `wgpu-core` uses the [`vanilla`] module's locks. //! //! [`Mutex`]: parking_lot::Mutex //! [`RwLock`]: parking_lot::RwLock //! [`SnatchLock`]: crate::snatch::SnatchLock pub mod rank; #[cfg(feature = "std")] // requires thread-locals to work #[cfg_attr(not(wgpu_validate_locks), allow(dead_code))] mod ranked; #[cfg(feature = "observe_locks")] mod observing; #[cfg_attr(any(wgpu_validate_locks, feature = "observe_locks"), allow(dead_code))] mod vanilla; #[cfg(wgpu_validate_locks)] use ranked as chosen; #[cfg(feature = "observe_locks")] use observing as chosen; #[cfg(not(any(wgpu_validate_locks, feature = "observe_locks")))] use vanilla as chosen; pub use chosen::{Mutex, MutexGuard, RankData, RwLock, RwLockReadGuard, RwLockWriteGuard}; ================================================ FILE: wgpu-core/src/lock/observing.rs ================================================ //! Lock types that observe lock acquisition order. //! //! This module's [`Mutex`] type is instrumented to observe the //! nesting of `wgpu-core` lock acquisitions. Whenever `wgpu-core` //! acquires one lock while it is already holding another, we note //! that nesting pair. This tells us what the [`LockRank::followers`] //! set for each lock would need to include to accommodate //! `wgpu-core`'s observed behavior. //! //! When `wgpu-core`'s `observe_locks` feature is enabled, if the //! `WGPU_CORE_LOCK_OBSERVE_DIR` environment variable is set to the //! path of an existing directory, then every thread that acquires a //! lock in `wgpu-core` will write its own log file to that directory. //! You can then run the `wgpu` workspace's `lock-analyzer` binary to //! read those files and summarize the results. The output from //! `lock-analyzer` has the same form as the lock ranks given in //! [`lock/rank.rs`]. //! //! If the `WGPU_CORE_LOCK_OBSERVE_DIR` environment variable is not //! set, then no instrumentation takes place, and the locks behave //! normally. //! //! To make sure we capture all acquisitions regardless of when the //! program exits, each thread writes events directly to its log file //! as they occur. A `write` system call is generally just a copy from //! userspace into the kernel's buffer, so hopefully this approach //! will still have tolerable performance. //! //! [`lock/rank.rs`]: ../../../src/wgpu_core/lock/rank.rs.html use alloc::{format, string::String}; use core::{cell::RefCell, panic::Location}; use std::{ fs::File, path::{Path, PathBuf}, }; use super::rank::{LockRank, LockRankSet}; use crate::FastHashSet; pub type RankData = Option; /// A `Mutex` instrumented for lock acquisition order observation. /// /// This is just a wrapper around a [`parking_lot::Mutex`], along with /// its rank in the `wgpu_core` lock ordering. /// /// For details, see [the module documentation][self]. pub struct Mutex { inner: parking_lot::Mutex, rank: LockRank, } /// A guard produced by locking [`Mutex`]. /// /// This is just a wrapper around a [`parking_lot::MutexGuard`], along /// with the state needed to track lock acquisition. /// /// For details, see [the module documentation][self]. pub struct MutexGuard<'a, T> { inner: parking_lot::MutexGuard<'a, T>, _state: LockStateGuard, } impl Mutex { pub fn new(rank: LockRank, value: T) -> Mutex { Mutex { inner: parking_lot::Mutex::new(value), rank, } } #[track_caller] pub fn lock(&self) -> MutexGuard<'_, T> { let saved = acquire(self.rank, Location::caller()); MutexGuard { inner: self.inner.lock(), _state: LockStateGuard { saved }, } } pub fn into_inner(self) -> T { self.inner.into_inner() } } impl<'a, T> core::ops::Deref for MutexGuard<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { self.inner.deref() } } impl<'a, T> core::ops::DerefMut for MutexGuard<'a, T> { fn deref_mut(&mut self) -> &mut Self::Target { self.inner.deref_mut() } } impl core::fmt::Debug for Mutex { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.inner.fmt(f) } } /// An `RwLock` instrumented for lock acquisition order observation. /// /// This is just a wrapper around a [`parking_lot::RwLock`], along with /// its rank in the `wgpu_core` lock ordering. /// /// For details, see [the module documentation][self]. pub struct RwLock { inner: parking_lot::RwLock, rank: LockRank, } /// A read guard produced by locking [`RwLock`] for reading. /// /// This is just a wrapper around a [`parking_lot::RwLockReadGuard`], along with /// the state needed to track lock acquisition. /// /// For details, see [the module documentation][self]. pub struct RwLockReadGuard<'a, T> { inner: parking_lot::RwLockReadGuard<'a, T>, _state: LockStateGuard, } /// A write guard produced by locking [`RwLock`] for writing. /// /// This is just a wrapper around a [`parking_lot::RwLockWriteGuard`], along /// with the state needed to track lock acquisition. /// /// For details, see [the module documentation][self]. pub struct RwLockWriteGuard<'a, T> { inner: parking_lot::RwLockWriteGuard<'a, T>, _state: LockStateGuard, } impl RwLock { pub fn new(rank: LockRank, value: T) -> RwLock { RwLock { inner: parking_lot::RwLock::new(value), rank, } } #[track_caller] pub fn read(&self) -> RwLockReadGuard<'_, T> { let saved = acquire(self.rank, Location::caller()); RwLockReadGuard { inner: self.inner.read(), _state: LockStateGuard { saved }, } } #[track_caller] pub fn write(&self) -> RwLockWriteGuard<'_, T> { let saved = acquire(self.rank, Location::caller()); RwLockWriteGuard { inner: self.inner.write(), _state: LockStateGuard { saved }, } } /// Force an read-unlock operation on this lock. /// /// Safety: /// - A read lock must be held which is not held by a guard. pub unsafe fn force_unlock_read(&self, data: RankData) { release(data); unsafe { self.inner.force_unlock_read() }; } } impl<'a, T> RwLockReadGuard<'a, T> { // Forget the read guard, leaving the lock in a locked state with no guard. // // Equivalent to std::mem::forget, but preserves the information about the lock // rank. pub fn forget(this: Self) -> RankData { core::mem::forget(this.inner); this._state.saved } } impl<'a, T> RwLockWriteGuard<'a, T> { pub fn downgrade(this: Self) -> RwLockReadGuard<'a, T> { RwLockReadGuard { inner: parking_lot::RwLockWriteGuard::downgrade(this.inner), _state: this._state, } } } impl core::fmt::Debug for RwLock { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.inner.fmt(f) } } impl<'a, T> core::ops::Deref for RwLockReadGuard<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { self.inner.deref() } } impl<'a, T> core::ops::Deref for RwLockWriteGuard<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { self.inner.deref() } } impl<'a, T> core::ops::DerefMut for RwLockWriteGuard<'a, T> { fn deref_mut(&mut self) -> &mut Self::Target { self.inner.deref_mut() } } /// A container that restores a prior per-thread lock state when dropped. /// /// This type serves two purposes: /// /// - Operations like `RwLockWriteGuard::downgrade` would like to be able to /// destructure lock guards and reassemble their pieces into new guards, but /// if the guard type itself implements `Drop`, we can't destructure it /// without unsafe code or pointless `Option`s whose state is almost always /// statically known. /// /// - We can just implement `Drop` for this type once, and then use it in lock /// guards, rather than implementing `Drop` separately for each guard type. struct LockStateGuard { /// The youngest lock that was already held when we acquired this /// one, if any. saved: Option, } impl Drop for LockStateGuard { fn drop(&mut self) { release(self.saved) } } /// Check and record the acquisition of a lock with `new_rank`. /// /// Log the acquisition of a lock with `new_rank`, and /// update the per-thread state accordingly. /// /// Return the `Option` state that must be restored when this lock is /// released. fn acquire(new_rank: LockRank, location: &'static Location<'static>) -> Option { LOCK_STATE.with_borrow_mut(|state| match *state { ThreadState::Disabled => None, ThreadState::Initial => { let Ok(dir) = std::env::var("WGPU_CORE_LOCK_OBSERVE_DIR") else { *state = ThreadState::Disabled; return None; }; // Create the observation log file. let mut log = ObservationLog::create(dir) .expect("Failed to open lock observation file (does the dir exist?)"); // Log the full set of lock ranks, so that the analysis can even see // locks that are only acquired in isolation. for rank in LockRankSet::all().iter() { log.write_rank(rank); } // Update our state to reflect that we are logging acquisitions, and // that we have acquired this lock. *state = ThreadState::Enabled { held_lock: Some(HeldLock { rank: new_rank, location, }), log, }; // Since this is the first acquisition on this thread, we know that // there is no prior lock held, and thus nothing to log yet. None } ThreadState::Enabled { ref mut held_lock, ref mut log, } => { if let Some(ref held_lock) = held_lock { log.write_acquisition(held_lock, new_rank, location); } held_lock.replace(HeldLock { rank: new_rank, location, }) } }) } /// Record the release of a lock whose saved state was `saved`. fn release(saved: Option) { LOCK_STATE.with_borrow_mut(|state| { if let ThreadState::Enabled { ref mut held_lock, .. } = *state { *held_lock = saved; } }); } std::thread_local! { static LOCK_STATE: RefCell = const { RefCell::new(ThreadState::Initial) }; } /// Thread-local state for lock observation. enum ThreadState { /// This thread hasn't yet checked the environment variable. Initial, /// This thread checked the environment variable, and it was /// unset, so this thread is not observing lock acquisitions. Disabled, /// Lock observation is enabled for this thread. Enabled { held_lock: Option, log: ObservationLog, }, } /// Information about a currently held lock. #[derive(Debug, Copy, Clone)] pub struct HeldLock { /// The lock's rank. rank: LockRank, /// Where we acquired the lock. location: &'static Location<'static>, } /// A log to which we can write observations of lock activity. struct ObservationLog { /// The file to which we are logging lock observations. log_file: File, /// [`Location`]s we've seen so far. /// /// This is a hashset of raw pointers because raw pointers have /// the [`Eq`] and [`Hash`] relations we want: the pointer value, not /// the contents. There's no unsafe code in this module. locations_seen: FastHashSet<*const Location<'static>>, /// Buffer for serializing events, retained for allocation reuse. buffer: String, } impl ObservationLog { /// Create an observation log in `dir` for the current pid and thread. fn create(dir: impl AsRef) -> Result { let mut path = PathBuf::from(dir.as_ref()); path.push(format!( "locks-{}.{:?}.ron", std::process::id(), std::thread::current().id() )); let log_file = File::create(&path)?; Ok(ObservationLog { log_file, locations_seen: FastHashSet::default(), buffer: String::new(), }) } /// Record the acquisition of one lock while holding another. /// /// Log that we acquired a lock of `new_rank` at `new_location` while still /// holding other locks, the most recently acquired of which has /// `older_rank`. fn write_acquisition( &mut self, older_lock: &HeldLock, new_rank: LockRank, new_location: &'static Location<'static>, ) { self.write_location(older_lock.location); self.write_location(new_location); self.write_action(&Action::Acquisition { older_rank: older_lock.rank.bit.number(), older_location: addr(older_lock.location), newer_rank: new_rank.bit.number(), newer_location: addr(new_location), }); } fn write_location(&mut self, location: &'static Location<'static>) { if self.locations_seen.insert(location) { self.write_action(&Action::Location { address: addr(location), file: location.file(), line: location.line(), column: location.column(), }); } } fn write_rank(&mut self, rank: LockRankSet) { self.write_action(&Action::Rank { bit: rank.number(), member_name: rank.member_name(), const_name: rank.const_name(), }); } fn write_action(&mut self, action: &Action) { use std::io::Write; self.buffer.clear(); ron::ser::to_writer(&mut self.buffer, &action) .expect("error serializing `lock::observing::Action`"); self.buffer.push('\n'); self.log_file .write_all(self.buffer.as_bytes()) .expect("error writing `lock::observing::Action`"); } } /// An action logged by a thread that is observing lock acquisition order. /// /// Each thread's log file is a sequence of these enums, serialized /// using the [`ron`] crate, one action per line. /// /// Lock observation cannot assume that there will be any convenient /// finalization point before the program exits, so in practice, /// actions must be written immediately when they occur. This means we /// can't, say, accumulate tables and write them out when they're /// complete. The `lock-analyzer` binary is then responsible for /// consolidating the data into a single table of observed transitions. #[derive(serde::Serialize)] enum Action { /// A location that we will refer to in later actions. /// /// We write one of these events the first time we see a /// particular `Location`. Treating this as a separate action /// simply lets us avoid repeating the content over and over /// again in every [`Acquisition`] action. /// /// [`Acquisition`]: Action::Acquisition Location { address: usize, file: &'static str, line: u32, column: u32, }, /// A lock rank that we will refer to in later actions. /// /// We write out one these events for every lock rank at the /// beginning of each thread's log file. Treating this as a /// separate action simply lets us avoid repeating the names over /// and over again in every [`Acquisition`] action. /// /// [`Acquisition`]: Action::Acquisition Rank { bit: u32, member_name: &'static str, const_name: &'static str, }, /// An attempt to acquire a lock while holding another lock. Acquisition { /// The number of the already acquired lock's rank. older_rank: u32, /// The source position at which we acquired it. Specifically, /// its `Location`'s address, as an integer. older_location: usize, /// The number of the rank of the lock we are acquiring. newer_rank: u32, /// The source position at which we are acquiring it. /// Specifically, its `Location`'s address, as an integer. newer_location: usize, }, } impl LockRankSet { /// Return the number of this rank's first member. fn number(self) -> u32 { self.bits().trailing_zeros() } } /// Convenience for `core::ptr::from_ref(t) as usize`. fn addr(t: &T) -> usize { core::ptr::from_ref(t) as usize } ================================================ FILE: wgpu-core/src/lock/rank.rs ================================================ //! Ranks for `wgpu-core` locks, restricting acquisition order. //! //! See [`LockRank`]. /// The rank of a lock. /// /// Each [`Mutex`], [`RwLock`], and [`SnatchLock`] in `wgpu-core` has been /// assigned a *rank*: a node in the DAG defined at the bottom of /// `wgpu-core/src/lock/rank.rs`. The rank of the most recently /// acquired lock you are still holding determines which locks you may /// attempt to acquire next. /// /// When you create a lock in `wgpu-core`, you must specify its rank /// by passing in a [`LockRank`] value. This module declares a /// pre-defined set of ranks to cover everything in `wgpu-core`, named /// after the type in which they occur, and the name of the type's /// field that is a lock. For example, [`CommandBuffer::data`] is a /// `Mutex`, and its rank here is the constant /// [`COMMAND_BUFFER_DATA`]. /// /// [`Mutex`]: parking_lot::Mutex /// [`RwLock`]: parking_lot::RwLock /// [`SnatchLock`]: crate::snatch::SnatchLock /// [`CommandBuffer::data`]: crate::command::CommandBuffer::data #[derive(Debug, Copy, Clone)] pub struct LockRank { /// The bit representing this lock. /// /// There should only be a single bit set in this value. pub(super) bit: LockRankSet, /// A bitmask of permitted successor ranks. /// /// If `rank` is the rank of the most recently acquired lock we /// are still holding, then `rank.followers` is the mask of /// locks we are allowed to acquire next. /// /// The `define_lock_ranks!` macro ensures that there are no /// cycles in the graph of lock ranks and their followers. pub(super) followers: LockRankSet, } /// Define a set of lock ranks, and each rank's permitted successors. macro_rules! define_lock_ranks { { $( $( #[ $attr:meta ] )* rank $name:ident $member:literal followed by { $( $follower:ident ),* $(,)? } )* } => { // An enum that assigns a unique number to each rank. #[allow(non_camel_case_types, clippy::upper_case_acronyms)] enum LockRankNumber { $( $name, )* } bitflags::bitflags! { #[derive(Debug, Copy, Clone, Eq, PartialEq)] /// A bitflags type representing a set of lock ranks. pub struct LockRankSet: u64 { $( const $name = 1 << (LockRankNumber:: $name as u64); )* } } impl LockRankSet { pub fn member_name(self) -> &'static str { match self { $( LockRankSet:: $name => $member, )* _ => "", } } #[cfg_attr(not(feature = "observe_locks"), allow(dead_code))] pub fn const_name(self) -> &'static str { match self { $( LockRankSet:: $name => stringify!($name), )* _ => "", } } } $( // If there is any cycle in the ranking, the initializers // for `followers` will be cyclic, and rustc will give us // an error message explaining the cycle. $( #[ $attr ] )* pub const $name: LockRank = LockRank { bit: LockRankSet:: $name, followers: LockRankSet::empty() $( .union($follower.bit) )*, }; )* } } define_lock_ranks! { rank COMMAND_BUFFER_DATA "CommandBuffer::data" followed by { DEVICE_SNATCHABLE_LOCK, DEVICE_USAGE_SCOPES, SHARED_TRACKER_INDEX_ALLOCATOR_INNER, BUFFER_MAP_STATE, } rank DEVICE_SNATCHABLE_LOCK "Device::snatchable_lock" followed by { SHARED_TRACKER_INDEX_ALLOCATOR_INNER, DEVICE_TRACE, BUFFER_MAP_STATE, // Uncomment this to see an interesting cycle. // COMMAND_BUFFER_DATA, } rank BUFFER_MAP_STATE "Buffer::map_state" followed by { QUEUE_PENDING_WRITES, SHARED_TRACKER_INDEX_ALLOCATOR_INNER, DEVICE_TRACE, } rank QUEUE_PENDING_WRITES "Queue::pending_writes" followed by { COMMAND_ALLOCATOR_FREE_ENCODERS, SHARED_TRACKER_INDEX_ALLOCATOR_INNER, QUEUE_LIFE_TRACKER, } rank QUEUE_LIFE_TRACKER "Queue::life_tracker" followed by { COMMAND_ALLOCATOR_FREE_ENCODERS, DEVICE_TRACE, } rank COMMAND_ALLOCATOR_FREE_ENCODERS "CommandAllocator::free_encoders" followed by { SHARED_TRACKER_INDEX_ALLOCATOR_INNER, } rank BUFFER_BIND_GROUPS "Buffer::bind_groups" followed by { } rank BUFFER_INITIALIZATION_STATUS "Buffer::initialization_status" followed by { } rank DEVICE_COMMAND_INDICES "Device::command_indices" followed by {} rank DEVICE_DEFERRED_DESTROY "Device::deferred_destroy" followed by {} rank DEVICE_FENCE "Device::fence" followed by { } rank DEVICE_TRACE "Device::trace" followed by { } rank DEVICE_TRACKERS "Device::trackers" followed by { } rank DEVICE_LOST_CLOSURE "Device::device_lost_closure" followed by { } rank DEVICE_USAGE_SCOPES "Device::usage_scopes" followed by { } rank IDENTITY_MANAGER_VALUES "IdentityManager::values" followed by { } rank REGISTRY_STORAGE "Registry::storage" followed by { } rank RESOURCE_POOL_INNER "ResourcePool::inner" followed by { } rank SHARED_TRACKER_INDEX_ALLOCATOR_INNER "SharedTrackerIndexAllocator::inner" followed by { } rank SURFACE_PRESENTATION "Surface::presentation" followed by { } rank TEXTURE_BIND_GROUPS "Texture::bind_groups" followed by { } rank TEXTURE_INITIALIZATION_STATUS "Texture::initialization_status" followed by { } rank TEXTURE_CLEAR_MODE "Texture::clear_mode" followed by { } rank TEXTURE_VIEWS "Texture::views" followed by { } rank BLAS_BUILT_INDEX "Blas::built_index" followed by { } rank BLAS_COMPACTION_STATE "Blas::compaction_size" followed by { } rank TLAS_BUILT_INDEX "Tlas::built_index" followed by { } rank TLAS_DEPENDENCIES "Tlas::dependencies" followed by { } rank BUFFER_POOL "BufferPool::buffers" followed by { } #[cfg(test)] rank PAWN "pawn" followed by { ROOK, BISHOP } #[cfg(test)] rank ROOK "rook" followed by { KNIGHT } #[cfg(test)] rank KNIGHT "knight" followed by { } #[cfg(test)] rank BISHOP "bishop" followed by { } } ================================================ FILE: wgpu-core/src/lock/ranked.rs ================================================ //! Lock types that enforce well-ranked lock acquisition order. //! //! This module's [`Mutex`] and [`RwLock` types are instrumented to check that //! `wgpu-core` acquires locks according to their rank, to prevent deadlocks. To //! use it, put `--cfg wgpu_validate_locks` in `RUSTFLAGS`. //! //! The [`LockRank`] constants in the [`lock::rank`] module describe edges in a //! directed graph of lock acquisitions: each lock's rank says, if this is the most //! recently acquired lock that you are still holding, then these are the locks you //! are allowed to acquire next. //! //! As long as this graph doesn't have cycles, any number of threads can acquire //! locks along paths through the graph without deadlock: //! //! - Assume that if a thread is holding a lock, then it will either release it, //! or block trying to acquire another one. No thread just sits on its locks //! forever for unrelated reasons. If it did, then that would be a source of //! deadlock "outside the system" that we can't do anything about. //! //! - This module asserts that threads acquire and release locks in a stack-like //! order: a lock is dropped only when it is the *most recently acquired* lock //! *still held* - call this the "youngest" lock. This stack-like ordering //! isn't a Rust requirement; Rust lets you drop guards in any order you like. //! This is a restriction we impose. //! //! - Consider the directed graph whose nodes are locks, and whose edges go from //! each lock to its permitted followers, the locks in its [`LockRank::followers`] //! set. The definition of the [`lock::rank`] module's [`LockRank`] constants //! ensures that this graph has no cycles, including trivial cycles from a node to //! itself. //! //! - This module then asserts that each thread attempts to acquire a lock only if //! it is among its youngest lock's permitted followers. Thus, as a thread //! acquires locks, it must be traversing a path through the graph along its //! edges. //! //! - Because there are no cycles in the graph, whenever one thread is blocked //! waiting to acquire a lock, that lock must be held by a different thread: if //! you were allowed to acquire a lock you already hold, that would be a cycle in //! the graph. //! //! - Furthermore, because the graph has no cycles, as we work our way from each //! thread to the thread it is blocked waiting for, we must eventually reach an //! end point: there must be some thread that is able to acquire its next lock, or //! that is about to release a lock. //! //! Thus, the system as a whole is always able to make progress: it is free of //! deadlocks. //! //! Note that this validation only monitors each thread's behavior in isolation: //! there's only thread-local state, nothing communicated between threads. So we //! don't detect deadlocks, per se, only the potential to cause deadlocks. This //! means that the validation is conservative, but more reproducible, since it's not //! dependent on any particular interleaving of execution. //! //! [`lock::rank`]: crate::lock::rank use core::{cell::Cell, fmt, ops, panic::Location}; use super::rank::LockRank; pub use LockState as RankData; /// A `Mutex` instrumented for deadlock prevention. /// /// This is just a wrapper around a [`parking_lot::Mutex`], along with /// its rank in the `wgpu_core` lock ordering. /// /// For details, see [the module documentation][self]. pub struct Mutex { inner: parking_lot::Mutex, rank: LockRank, } /// A guard produced by locking [`Mutex`]. /// /// This is just a wrapper around a [`parking_lot::MutexGuard`], along /// with the state needed to track lock acquisition. /// /// For details, see [the module documentation][self]. pub struct MutexGuard<'a, T> { inner: parking_lot::MutexGuard<'a, T>, saved: LockStateGuard, } std::thread_local! { static LOCK_STATE: Cell = const { Cell::new(LockState::INITIAL) }; } /// Per-thread state for the deadlock checker. #[derive(Debug, Copy, Clone)] pub struct LockState { /// The last lock we acquired, and where. last_acquired: Option<(LockRank, &'static Location<'static>)>, /// The number of locks currently held. /// /// This is used to enforce stack-like lock acquisition and release. depth: u32, } impl LockState { const INITIAL: LockState = LockState { last_acquired: None, depth: 0, }; } /// A container that restores a [`LockState`] when dropped. /// /// This type serves two purposes: /// /// - Operations like `RwLockWriteGuard::downgrade` would like to be able to /// destructure lock guards and reassemble their pieces into new guards, but /// if the guard type itself implements `Drop`, we can't destructure it /// without unsafe code or pointless `Option`s whose state is almost always /// statically known. /// /// - We can just implement `Drop` for this type once, and then use it in lock /// guards, rather than implementing `Drop` separately for each guard type. struct LockStateGuard(LockState); impl Drop for LockStateGuard { fn drop(&mut self) { release(self.0) } } /// Check and record the acquisition of a lock with `new_rank`. /// /// Check that acquiring a lock with `new_rank` is permitted at this point, and /// update the per-thread state accordingly. /// /// Return the `LockState` that must be restored when this thread is released. fn acquire(new_rank: LockRank, location: &'static Location<'static>) -> LockState { let state = LOCK_STATE.get(); // Initially, it's fine to acquire any lock. So we only // need to check when `last_acquired` is `Some`. if let Some((ref last_rank, ref last_location)) = state.last_acquired { assert!( last_rank.followers.contains(new_rank.bit), "Attempt to acquire nested mutexes in wrong order:\n\ last locked {:<35} at {}\n\ now locking {:<35} at {}\n\ Locking {} after locking {} is not permitted.", last_rank.bit.member_name(), last_location, new_rank.bit.member_name(), location, new_rank.bit.member_name(), last_rank.bit.member_name(), ); } LOCK_STATE.set(LockState { last_acquired: Some((new_rank, location)), depth: state.depth + 1, }); state } /// Record the release of a lock whose saved state was `saved`. /// /// Check that locks are being acquired in stacking order, and update the /// per-thread state accordingly. fn release(saved: LockState) { let prior = LOCK_STATE.replace(saved); // Although Rust allows mutex guards to be dropped in any // order, this analysis requires that locks be acquired and // released in stack order: the next lock to be released must be // the most recently acquired lock still held. assert_eq!( prior.depth, saved.depth + 1, "Lock not released in stacking order" ); } impl Mutex { pub fn new(rank: LockRank, value: T) -> Mutex { Mutex { inner: parking_lot::Mutex::new(value), rank, } } #[track_caller] pub fn lock(&self) -> MutexGuard<'_, T> { let saved = acquire(self.rank, Location::caller()); MutexGuard { inner: self.inner.lock(), saved: LockStateGuard(saved), } } } impl<'a, T> ops::Deref for MutexGuard<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { self.inner.deref() } } impl<'a, T> ops::DerefMut for MutexGuard<'a, T> { fn deref_mut(&mut self) -> &mut Self::Target { self.inner.deref_mut() } } impl fmt::Debug for Mutex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner.fmt(f) } } /// An `RwLock` instrumented for deadlock prevention. /// /// This is just a wrapper around a [`parking_lot::RwLock`], along with /// its rank in the `wgpu_core` lock ordering. /// /// For details, see [the module documentation][self]. pub struct RwLock { inner: parking_lot::RwLock, rank: LockRank, } /// A read guard produced by locking [`RwLock`] for reading. /// /// This is just a wrapper around a [`parking_lot::RwLockReadGuard`], along with /// the state needed to track lock acquisition. /// /// For details, see [the module documentation][self]. pub struct RwLockReadGuard<'a, T> { inner: parking_lot::RwLockReadGuard<'a, T>, saved: LockStateGuard, } /// A write guard produced by locking [`RwLock`] for writing. /// /// This is just a wrapper around a [`parking_lot::RwLockWriteGuard`], along /// with the state needed to track lock acquisition. /// /// For details, see [the module documentation][self]. pub struct RwLockWriteGuard<'a, T> { inner: parking_lot::RwLockWriteGuard<'a, T>, saved: LockStateGuard, } impl RwLock { pub fn new(rank: LockRank, value: T) -> RwLock { RwLock { inner: parking_lot::RwLock::new(value), rank, } } #[track_caller] pub fn read(&self) -> RwLockReadGuard<'_, T> { let saved = acquire(self.rank, Location::caller()); RwLockReadGuard { inner: self.inner.read(), saved: LockStateGuard(saved), } } #[track_caller] pub fn write(&self) -> RwLockWriteGuard<'_, T> { let saved = acquire(self.rank, Location::caller()); RwLockWriteGuard { inner: self.inner.write(), saved: LockStateGuard(saved), } } /// Force an read-unlock operation on this lock. /// /// Safety: /// - A read lock must be held which is not held by a guard. pub unsafe fn force_unlock_read(&self, data: RankData) { release(data); unsafe { self.inner.force_unlock_read() }; } } impl<'a, T> RwLockReadGuard<'a, T> { // Forget the read guard, leaving the lock in a locked state with no guard. // // Equivalent to std::mem::forget, but preserves the information about the lock // rank. pub fn forget(this: Self) -> RankData { core::mem::forget(this.inner); this.saved.0 } } impl<'a, T> RwLockWriteGuard<'a, T> { pub fn downgrade(this: Self) -> RwLockReadGuard<'a, T> { RwLockReadGuard { inner: parking_lot::RwLockWriteGuard::downgrade(this.inner), saved: this.saved, } } } impl fmt::Debug for RwLock { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner.fmt(f) } } impl<'a, T> ops::Deref for RwLockReadGuard<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { self.inner.deref() } } impl<'a, T> ops::Deref for RwLockWriteGuard<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { self.inner.deref() } } impl<'a, T> ops::DerefMut for RwLockWriteGuard<'a, T> { fn deref_mut(&mut self) -> &mut Self::Target { self.inner.deref_mut() } } /// Locks can be acquired in the order indicated by their ranks. #[test] fn permitted() { use super::rank; let lock1 = Mutex::new(rank::PAWN, ()); let lock2 = Mutex::new(rank::ROOK, ()); let _guard1 = lock1.lock(); let _guard2 = lock2.lock(); } /// Locks can only be acquired in the order indicated by their ranks. #[test] #[should_panic(expected = "Locking pawn after locking rook")] fn forbidden_unrelated() { use super::rank; let lock1 = Mutex::new(rank::ROOK, ()); let lock2 = Mutex::new(rank::PAWN, ()); let _guard1 = lock1.lock(); let _guard2 = lock2.lock(); } /// Lock acquisitions can't skip ranks. /// /// These two locks *could* be acquired in this order, but only if other locks /// are acquired in between them. Skipping ranks isn't allowed. #[test] #[should_panic(expected = "Locking knight after locking pawn")] fn forbidden_skip() { use super::rank; let lock1 = Mutex::new(rank::PAWN, ()); let lock2 = Mutex::new(rank::KNIGHT, ()); let _guard1 = lock1.lock(); let _guard2 = lock2.lock(); } /// Locks can be acquired and released in a stack-like order. #[test] fn stack_like() { use super::rank; let lock1 = Mutex::new(rank::PAWN, ()); let lock2 = Mutex::new(rank::ROOK, ()); let lock3 = Mutex::new(rank::BISHOP, ()); let guard1 = lock1.lock(); let guard2 = lock2.lock(); drop(guard2); let guard3 = lock3.lock(); drop(guard3); drop(guard1); } /// Locks can only be acquired and released in a stack-like order. #[test] #[should_panic(expected = "Lock not released in stacking order")] fn non_stack_like() { use super::rank; let lock1 = Mutex::new(rank::PAWN, ()); let lock2 = Mutex::new(rank::ROOK, ()); let guard1 = lock1.lock(); let guard2 = lock2.lock(); // Avoid a double panic from dropping this while unwinding due to the panic // we're testing for. core::mem::forget(guard2); drop(guard1); } ================================================ FILE: wgpu-core/src/lock/vanilla.rs ================================================ //! Plain, uninstrumented wrappers around [`parking_lot`] lock types. //! //! These definitions are used when no particular lock instrumentation //! Cargo feature is selected. use core::{fmt, ops}; use crate::lock::rank::LockRank; pub struct RankData; /// A plain wrapper around [`parking_lot::Mutex`]. /// /// This is just like [`parking_lot::Mutex`], except that our [`new`] /// method takes a rank, indicating where the new mutex should sit in /// `wgpu-core`'s lock ordering. The rank is ignored. /// /// See the [`lock`] module documentation for other wrappers. /// /// [`new`]: Mutex::new /// [`lock`]: crate::lock pub struct Mutex(parking_lot::Mutex); /// A guard produced by locking [`Mutex`]. /// /// This is just a wrapper around a [`parking_lot::MutexGuard`]. pub struct MutexGuard<'a, T>(parking_lot::MutexGuard<'a, T>); impl Mutex { pub fn new(_rank: LockRank, value: T) -> Mutex { Mutex(parking_lot::Mutex::new(value)) } pub fn lock(&self) -> MutexGuard<'_, T> { MutexGuard(self.0.lock()) } pub fn into_inner(self) -> T { self.0.into_inner() } } impl<'a, T> ops::Deref for MutexGuard<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { self.0.deref() } } impl<'a, T> ops::DerefMut for MutexGuard<'a, T> { fn deref_mut(&mut self) -> &mut Self::Target { self.0.deref_mut() } } impl fmt::Debug for Mutex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } /// A plain wrapper around [`parking_lot::RwLock`]. /// /// This is just like [`parking_lot::RwLock`], except that our [`new`] /// method takes a rank, indicating where the new mutex should sit in /// `wgpu-core`'s lock ordering. The rank is ignored. /// /// See the [`lock`] module documentation for other wrappers. /// /// [`new`]: RwLock::new /// [`lock`]: crate::lock pub struct RwLock(parking_lot::RwLock); /// A read guard produced by locking [`RwLock`] as a reader. /// /// This is just a wrapper around a [`parking_lot::RwLockReadGuard`]. pub struct RwLockReadGuard<'a, T>(parking_lot::RwLockReadGuard<'a, T>); /// A write guard produced by locking [`RwLock`] as a writer. /// /// This is just a wrapper around a [`parking_lot::RwLockWriteGuard`]. pub struct RwLockWriteGuard<'a, T>(parking_lot::RwLockWriteGuard<'a, T>); impl RwLock { pub fn new(_rank: LockRank, value: T) -> RwLock { RwLock(parking_lot::RwLock::new(value)) } pub fn read(&self) -> RwLockReadGuard<'_, T> { RwLockReadGuard(self.0.read()) } pub fn write(&self) -> RwLockWriteGuard<'_, T> { RwLockWriteGuard(self.0.write()) } /// Force an read-unlock operation on this lock. /// /// Safety: /// - A read lock must be held which is not held by a guard. pub unsafe fn force_unlock_read(&self, _data: RankData) { unsafe { self.0.force_unlock_read() }; } } impl<'a, T> RwLockReadGuard<'a, T> { // Forget the read guard, leaving the lock in a locked state with no guard. // // Equivalent to std::mem::forget, but preserves the information about the lock // rank. pub fn forget(this: Self) -> RankData { core::mem::forget(this.0); RankData } } impl<'a, T> RwLockWriteGuard<'a, T> { pub fn downgrade(this: Self) -> RwLockReadGuard<'a, T> { RwLockReadGuard(parking_lot::RwLockWriteGuard::downgrade(this.0)) } } impl fmt::Debug for RwLock { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl<'a, T> ops::Deref for RwLockReadGuard<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { self.0.deref() } } impl<'a, T> ops::Deref for RwLockWriteGuard<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { self.0.deref() } } impl<'a, T> ops::DerefMut for RwLockWriteGuard<'a, T> { fn deref_mut(&mut self) -> &mut Self::Target { self.0.deref_mut() } } ================================================ FILE: wgpu-core/src/pipeline.rs ================================================ use alloc::{ borrow::Cow, boxed::Box, string::{String, ToString as _}, sync::Arc, vec::Vec, }; use core::{marker::PhantomData, mem::ManuallyDrop, num::NonZeroU32}; use arrayvec::ArrayVec; use naga::error::ShaderError; use thiserror::Error; use wgt::error::{ErrorType, WebGpuError}; pub use crate::pipeline_cache::PipelineCacheValidationError; use crate::{ binding_model::{ BindGroupLayout, CreateBindGroupLayoutError, CreatePipelineLayoutError, GetBindGroupLayoutError, PipelineLayout, }, command::ColorAttachmentError, device::{Device, DeviceError, MissingDownlevelFlags, MissingFeatures, RenderPassContext}, id::{PipelineCacheId, PipelineLayoutId, ShaderModuleId}, resource::{InvalidResourceError, Labeled, TrackingData}, resource_log, validation, Label, }; /// Information about buffer bindings, which /// is validated against the shader (and pipeline) /// at draw time as opposed to initialization time. #[derive(Debug, Default)] pub(crate) struct LateSizedBufferGroup { // The order has to match `BindGroup::late_buffer_binding_sizes`. pub(crate) shader_sizes: Vec, } #[allow(clippy::large_enum_variant)] pub enum ShaderModuleSource<'a> { #[cfg(feature = "wgsl")] Wgsl(Cow<'a, str>), #[cfg(feature = "glsl")] Glsl(Cow<'a, str>, naga::front::glsl::Options), #[cfg(feature = "spirv")] SpirV(Cow<'a, [u32]>, naga::front::spv::Options), Naga(Cow<'static, naga::Module>), /// Dummy variant because `Naga` doesn't have a lifetime and without enough active features it /// could be the last one active. #[doc(hidden)] Dummy(PhantomData<&'a ()>), } #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ShaderModuleDescriptor<'a> { pub label: Label<'a>, #[cfg_attr(feature = "serde", serde(default))] pub runtime_checks: wgt::ShaderRuntimeChecks, } pub type ShaderModuleDescriptorPassthrough<'a> = wgt::CreateShaderModuleDescriptorPassthrough<'a, Label<'a>>; #[derive(Debug)] pub struct ShaderModule { pub(crate) raw: ManuallyDrop>, pub(crate) device: Arc, pub(crate) interface: Option, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, } impl Drop for ShaderModule { fn drop(&mut self) { resource_log!("Destroy raw {}", self.error_ident()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; unsafe { self.device.raw().destroy_shader_module(raw); } } } crate::impl_resource_type!(ShaderModule); crate::impl_labeled!(ShaderModule); crate::impl_parent_device!(ShaderModule); crate::impl_storage_item!(ShaderModule); impl ShaderModule { pub(crate) fn raw(&self) -> &dyn hal::DynShaderModule { self.raw.as_ref() } pub(crate) fn finalize_entry_point_name( &self, stage: naga::ShaderStage, entry_point: Option<&str>, ) -> Result { match &self.interface { Some(interface) => interface.finalize_entry_point_name(stage, entry_point), None => entry_point .map(|ep| ep.to_string()) .ok_or(validation::StageError::NoEntryPointFound), } } } //Note: `Clone` would require `WithSpan: Clone`. #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateShaderModuleError { #[cfg(feature = "wgsl")] #[error(transparent)] Parsing(#[from] ShaderError), #[cfg(feature = "glsl")] #[error(transparent)] ParsingGlsl(#[from] ShaderError), #[cfg(feature = "spirv")] #[error(transparent)] ParsingSpirV(#[from] ShaderError), #[error("Failed to generate the backend-specific code")] Generation, #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] Validation(#[from] ShaderError>), #[error(transparent)] MissingFeatures(#[from] MissingFeatures), #[error( "Shader global {bind:?} uses a group index {group} that exceeds the max_bind_groups limit of {limit}." )] InvalidGroupIndex { bind: naga::ResourceBinding, group: u32, limit: u32, }, #[error("Generic shader passthrough does not contain any code compatible with this backend.")] NotCompiledForBackend, } impl WebGpuError for CreateShaderModuleError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::MissingFeatures(e) => e.webgpu_error_type(), Self::Generation => ErrorType::Internal, Self::Validation(..) | Self::InvalidGroupIndex { .. } => ErrorType::Validation, #[cfg(feature = "wgsl")] Self::Parsing(..) => ErrorType::Validation, #[cfg(feature = "glsl")] Self::ParsingGlsl(..) => ErrorType::Validation, #[cfg(feature = "spirv")] Self::ParsingSpirV(..) => ErrorType::Validation, Self::NotCompiledForBackend => ErrorType::Validation, } } } /// Describes a programmable pipeline stage. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ProgrammableStageDescriptor<'a, SM = ShaderModuleId> { /// The compiled shader module for this stage. pub module: SM, /// The name of the entry point in the compiled shader. The name is selected using the /// following logic: /// /// * If `Some(name)` is specified, there must be a function with this name in the shader. /// * If a single entry point associated with this stage must be in the shader, then proceed as /// if `Some(…)` was specified with that entry point's name. pub entry_point: Option>, /// Specifies the values of pipeline-overridable constants in the shader module. /// /// If an `@id` attribute was specified on the declaration, /// the key must be the pipeline constant ID as a decimal ASCII number; if not, /// the key must be the constant's identifier name. /// /// The value may represent any of WGSL's concrete scalar types. pub constants: naga::back::PipelineConstants, /// Whether workgroup scoped memory will be initialized with zero values for this stage. /// /// This is required by the WebGPU spec, but may have overhead which can be avoided /// for cross-platform applications pub zero_initialize_workgroup_memory: bool, } /// cbindgen:ignore pub type ResolvedProgrammableStageDescriptor<'a> = ProgrammableStageDescriptor<'a, Arc>; /// Number of implicit bind groups derived at pipeline creation. pub type ImplicitBindGroupCount = u8; #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum ImplicitLayoutError { #[error("Unable to reflect the shader {0:?} interface")] ReflectionError(wgt::ShaderStages), #[error(transparent)] BindGroup(#[from] CreateBindGroupLayoutError), #[error(transparent)] Pipeline(#[from] CreatePipelineLayoutError), #[error("Unable to create implicit pipeline layout from passthrough shader stage: {0:?}")] Passthrough(wgt::ShaderStages), } impl WebGpuError for ImplicitLayoutError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::ReflectionError(_) => ErrorType::Validation, Self::BindGroup(e) => e.webgpu_error_type(), Self::Pipeline(e) => e.webgpu_error_type(), Self::Passthrough(_) => ErrorType::Validation, } } } /// Describes a compute pipeline. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ComputePipelineDescriptor< 'a, PLL = PipelineLayoutId, SM = ShaderModuleId, PLC = PipelineCacheId, > { pub label: Label<'a>, /// The layout of bind groups for this pipeline. pub layout: Option, /// The compiled compute stage and its entry point. pub stage: ProgrammableStageDescriptor<'a, SM>, /// The pipeline cache to use when creating this pipeline. pub cache: Option, } /// cbindgen:ignore pub type ResolvedComputePipelineDescriptor<'a> = ComputePipelineDescriptor<'a, Arc, Arc, Arc>; #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateComputePipelineError { #[error(transparent)] Device(#[from] DeviceError), #[error("Unable to derive an implicit layout")] Implicit(#[from] ImplicitLayoutError), #[error("Error matching shader requirements against the pipeline")] Stage(#[from] validation::StageError), #[error("Internal error: {0}")] Internal(String), #[error("Pipeline constant error: {0}")] PipelineConstants(String), #[error(transparent)] MissingDownlevelFlags(#[from] MissingDownlevelFlags), #[error(transparent)] InvalidResource(#[from] InvalidResourceError), } impl WebGpuError for CreateComputePipelineError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::InvalidResource(e) => e.webgpu_error_type(), Self::MissingDownlevelFlags(e) => e.webgpu_error_type(), Self::Implicit(e) => e.webgpu_error_type(), Self::Stage(e) => e.webgpu_error_type(), Self::Internal(_) => ErrorType::Internal, Self::PipelineConstants(_) => ErrorType::Validation, } } } #[derive(Debug)] pub struct ComputePipeline { pub(crate) raw: ManuallyDrop>, pub(crate) layout: Arc, pub(crate) device: Arc, pub(crate) _shader_module: Arc, pub(crate) late_sized_buffer_groups: ArrayVec, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, } impl Drop for ComputePipeline { fn drop(&mut self) { resource_log!("Destroy raw {}", self.error_ident()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; unsafe { self.device.raw().destroy_compute_pipeline(raw); } } } crate::impl_resource_type!(ComputePipeline); crate::impl_labeled!(ComputePipeline); crate::impl_parent_device!(ComputePipeline); crate::impl_storage_item!(ComputePipeline); crate::impl_trackable!(ComputePipeline); impl ComputePipeline { pub(crate) fn raw(&self) -> &dyn hal::DynComputePipeline { self.raw.as_ref() } pub fn get_bind_group_layout( self: &Arc, index: u32, ) -> Result, GetBindGroupLayoutError> { self.layout.get_bind_group_layout(index) } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreatePipelineCacheError { #[error(transparent)] Device(#[from] DeviceError), #[error("Pipeline cache validation failed")] Validation(#[from] PipelineCacheValidationError), #[error(transparent)] MissingFeatures(#[from] MissingFeatures), } impl WebGpuError for CreatePipelineCacheError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::Validation(e) => e.webgpu_error_type(), Self::MissingFeatures(e) => e.webgpu_error_type(), } } } #[derive(Debug)] pub struct PipelineCache { pub(crate) raw: ManuallyDrop>, pub(crate) device: Arc, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, } impl Drop for PipelineCache { fn drop(&mut self) { resource_log!("Destroy raw {}", self.error_ident()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; unsafe { self.device.raw().destroy_pipeline_cache(raw); } } } crate::impl_resource_type!(PipelineCache); crate::impl_labeled!(PipelineCache); crate::impl_parent_device!(PipelineCache); crate::impl_storage_item!(PipelineCache); impl PipelineCache { pub(crate) fn raw(&self) -> &dyn hal::DynPipelineCache { self.raw.as_ref() } } /// Describes how the vertex buffer is interpreted. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct VertexBufferLayout<'a> { /// The stride, in bytes, between elements of this buffer. pub array_stride: wgt::BufferAddress, /// How often this vertex buffer is "stepped" forward. pub step_mode: wgt::VertexStepMode, /// The list of attributes which comprise a single vertex. pub attributes: Cow<'a, [wgt::VertexAttribute]>, } /// A null vertex buffer layout that may be placed in unused slots. impl Default for VertexBufferLayout<'_> { fn default() -> Self { Self { array_stride: Default::default(), step_mode: Default::default(), attributes: Cow::Borrowed(&[]), } } } /// Describes the vertex process in a render pipeline. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct VertexState<'a, SM = ShaderModuleId> { /// The compiled vertex stage and its entry point. pub stage: ProgrammableStageDescriptor<'a, SM>, /// The format of any vertex buffers used with this pipeline. pub buffers: Cow<'a, [VertexBufferLayout<'a>]>, } /// cbindgen:ignore pub type ResolvedVertexState<'a> = VertexState<'a, Arc>; /// Describes fragment processing in a render pipeline. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct FragmentState<'a, SM = ShaderModuleId> { /// The compiled fragment stage and its entry point. pub stage: ProgrammableStageDescriptor<'a, SM>, /// The effect of draw calls on the color aspect of the output target. pub targets: Cow<'a, [Option]>, } /// cbindgen:ignore pub type ResolvedFragmentState<'a> = FragmentState<'a, Arc>; /// Describes the task shader in a mesh shader pipeline. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TaskState<'a, SM = ShaderModuleId> { /// The compiled task stage and its entry point. pub stage: ProgrammableStageDescriptor<'a, SM>, } pub type ResolvedTaskState<'a> = TaskState<'a, Arc>; /// Describes the mesh shader in a mesh shader pipeline. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct MeshState<'a, SM = ShaderModuleId> { /// The compiled mesh stage and its entry point. pub stage: ProgrammableStageDescriptor<'a, SM>, } pub type ResolvedMeshState<'a> = MeshState<'a, Arc>; /// Describes a vertex processor for either a conventional or mesh shading /// pipeline architecture. /// /// This is not a public API. It is for use by `player` only. The public APIs /// are [`VertexState`], [`TaskState`], and [`MeshState`]. /// /// cbindgen:ignore #[doc(hidden)] #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum RenderPipelineVertexProcessor<'a, SM = ShaderModuleId> { Vertex(VertexState<'a, SM>), Mesh(Option>, MeshState<'a, SM>), } /// Describes a render (graphics) pipeline. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct RenderPipelineDescriptor< 'a, PLL = PipelineLayoutId, SM = ShaderModuleId, PLC = PipelineCacheId, > { pub label: Label<'a>, /// The layout of bind groups for this pipeline. pub layout: Option, /// The vertex processing state for this pipeline. pub vertex: VertexState<'a, SM>, /// The properties of the pipeline at the primitive assembly and rasterization level. #[cfg_attr(feature = "serde", serde(default))] pub primitive: wgt::PrimitiveState, /// The effect of draw calls on the depth and stencil aspects of the output target, if any. #[cfg_attr(feature = "serde", serde(default))] pub depth_stencil: Option, /// The multi-sampling properties of the pipeline. #[cfg_attr(feature = "serde", serde(default))] pub multisample: wgt::MultisampleState, /// The fragment processing state for this pipeline. pub fragment: Option>, /// If the pipeline will be used with a multiview render pass, this indicates how many array /// layers the attachments will have. pub multiview_mask: Option, /// The pipeline cache to use when creating this pipeline. pub cache: Option, } /// Describes a mesh shader pipeline. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct MeshPipelineDescriptor< 'a, PLL = PipelineLayoutId, SM = ShaderModuleId, PLC = PipelineCacheId, > { pub label: Label<'a>, /// The layout of bind groups for this pipeline. pub layout: Option, /// The task processing state for this pipeline. pub task: Option>, /// The mesh processing state for this pipeline pub mesh: MeshState<'a, SM>, /// The properties of the pipeline at the primitive assembly and rasterization level. #[cfg_attr(feature = "serde", serde(default))] pub primitive: wgt::PrimitiveState, /// The effect of draw calls on the depth and stencil aspects of the output target, if any. #[cfg_attr(feature = "serde", serde(default))] pub depth_stencil: Option, /// The multi-sampling properties of the pipeline. #[cfg_attr(feature = "serde", serde(default))] pub multisample: wgt::MultisampleState, /// The fragment processing state for this pipeline. pub fragment: Option>, /// If the pipeline will be used with a multiview render pass, this indicates how many array /// layers the attachments will have. pub multiview: Option, /// The pipeline cache to use when creating this pipeline. pub cache: Option, } /// Describes a render (graphics) pipeline, with either conventional or mesh /// shading architecture. /// /// This is not a public API. It is for use by `player` only. The public APIs /// are [`RenderPipelineDescriptor`] and [`MeshPipelineDescriptor`]. /// /// cbindgen:ignore #[doc(hidden)] #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GeneralRenderPipelineDescriptor< 'a, PLL = PipelineLayoutId, SM = ShaderModuleId, PLC = PipelineCacheId, > { pub label: Label<'a>, /// The layout of bind groups for this pipeline. pub layout: Option, /// The vertex processing state for this pipeline. pub vertex: RenderPipelineVertexProcessor<'a, SM>, /// The properties of the pipeline at the primitive assembly and rasterization level. #[cfg_attr(feature = "serde", serde(default))] pub primitive: wgt::PrimitiveState, /// The effect of draw calls on the depth and stencil aspects of the output target, if any. #[cfg_attr(feature = "serde", serde(default))] pub depth_stencil: Option, /// The multi-sampling properties of the pipeline. #[cfg_attr(feature = "serde", serde(default))] pub multisample: wgt::MultisampleState, /// The fragment processing state for this pipeline. pub fragment: Option>, /// If the pipeline will be used with a multiview render pass, this indicates how many array /// layers the attachments will have. pub multiview_mask: Option, /// The pipeline cache to use when creating this pipeline. pub cache: Option, } impl<'a, PLL, SM, PLC> From> for GeneralRenderPipelineDescriptor<'a, PLL, SM, PLC> { fn from(value: RenderPipelineDescriptor<'a, PLL, SM, PLC>) -> Self { Self { label: value.label, layout: value.layout, vertex: RenderPipelineVertexProcessor::Vertex(value.vertex), primitive: value.primitive, depth_stencil: value.depth_stencil, multisample: value.multisample, fragment: value.fragment, multiview_mask: value.multiview_mask, cache: value.cache, } } } impl<'a, PLL, SM, PLC> From> for GeneralRenderPipelineDescriptor<'a, PLL, SM, PLC> { fn from(value: MeshPipelineDescriptor<'a, PLL, SM, PLC>) -> Self { Self { label: value.label, layout: value.layout, vertex: RenderPipelineVertexProcessor::Mesh(value.task, value.mesh), primitive: value.primitive, depth_stencil: value.depth_stencil, multisample: value.multisample, fragment: value.fragment, multiview_mask: value.multiview, cache: value.cache, } } } /// Not a public API. For use by `player` only. /// /// cbindgen:ignore pub type ResolvedGeneralRenderPipelineDescriptor<'a> = GeneralRenderPipelineDescriptor<'a, Arc, Arc, Arc>; #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PipelineCacheDescriptor<'a> { pub label: Label<'a>, pub data: Option>, pub fallback: bool, } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum ColorStateError { #[error("Format {0:?} is not renderable")] FormatNotRenderable(wgt::TextureFormat), #[error("Format {0:?} is not blendable")] FormatNotBlendable(wgt::TextureFormat), #[error("Format {0:?} does not have a color aspect")] FormatNotColor(wgt::TextureFormat), #[error("Sample count {0} is not supported by format {1:?} on this device. The WebGPU spec guarantees {2:?} samples are supported by this format. With the TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES feature your device supports {3:?}.")] InvalidSampleCount(u32, wgt::TextureFormat, Vec, Vec), #[error("Output format {pipeline} is incompatible with the shader {shader}")] IncompatibleFormat { pipeline: validation::NumericType, shader: validation::NumericType, }, #[error("Invalid write mask {0:?}")] InvalidWriteMask(wgt::ColorWrites), #[error("Using the blend factor {factor:?} for render target {target} is not possible. Only the first render target may be used when dual-source blending.")] BlendFactorOnUnsupportedTarget { factor: wgt::BlendFactor, target: u32, }, #[error( "Blend factor {factor:?} for render target {target} is not valid. Blend factor must be `one` when using min/max blend operations." )] InvalidMinMaxBlendFactor { factor: wgt::BlendFactor, target: u32, }, } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum DepthStencilStateError { #[error("Format {0:?} is not renderable")] FormatNotRenderable(wgt::TextureFormat), #[error("Format {0:?} is not a depth/stencil format")] FormatNotDepthOrStencil(wgt::TextureFormat), #[error("Format {0:?} does not have a depth aspect, but depth test/write is enabled")] FormatNotDepth(wgt::TextureFormat), #[error("Format {0:?} does not have a stencil aspect, but stencil test/write is enabled")] FormatNotStencil(wgt::TextureFormat), #[error("Sample count {0} is not supported by format {1:?} on this device. The WebGPU spec guarantees {2:?} samples are supported by this format. With the TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES feature your device supports {3:?}.")] InvalidSampleCount(u32, wgt::TextureFormat, Vec, Vec), #[error("Depth bias is not compatible with non-triangle topology {0:?}")] DepthBiasWithIncompatibleTopology(wgt::PrimitiveTopology), #[error("Depth compare function must be specified for depth format {0:?}")] MissingDepthCompare(wgt::TextureFormat), #[error("Depth write enabled must be specified for depth format {0:?}")] MissingDepthWriteEnabled(wgt::TextureFormat), } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateRenderPipelineError { #[error(transparent)] ColorAttachment(#[from] ColorAttachmentError), #[error(transparent)] Device(#[from] DeviceError), #[error("Unable to derive an implicit layout")] Implicit(#[from] ImplicitLayoutError), #[error("Color state [{0}] is invalid")] ColorState(u8, #[source] ColorStateError), #[error("Depth/stencil state is invalid")] DepthStencilState(#[from] DepthStencilStateError), #[error("Invalid sample count {0}")] InvalidSampleCount(u32), #[error("The number of vertex buffers {given} exceeds the limit {limit}")] TooManyVertexBuffers { given: u32, limit: u32 }, #[error("The total number of vertex attributes {given} exceeds the limit {limit}")] TooManyVertexAttributes { given: u32, limit: u32 }, #[error("Vertex attribute location {given} must be less than limit {limit}")] VertexAttributeLocationTooLarge { given: u32, limit: u32 }, #[error("Vertex buffer {index} stride {given} exceeds the limit {limit}")] VertexStrideTooLarge { index: u32, given: u32, limit: u32 }, #[error("Vertex attribute at location {location} stride {given} exceeds the limit {limit}")] VertexAttributeStrideTooLarge { location: wgt::ShaderLocation, given: u32, limit: u32, }, #[error("Vertex buffer {index} stride {stride} does not respect `VERTEX_ALIGNMENT`")] UnalignedVertexStride { index: u32, stride: wgt::BufferAddress, }, #[error("Vertex attribute at location {location} has invalid offset {offset}")] InvalidVertexAttributeOffset { location: wgt::ShaderLocation, offset: wgt::BufferAddress, }, #[error("Two or more vertex attributes were assigned to the same location in the shader: {0}")] ShaderLocationClash(u32), #[error("Strip index format was not set to None but to {strip_index_format:?} while using the non-strip topology {topology:?}")] StripIndexFormatForNonStripTopology { strip_index_format: Option, topology: wgt::PrimitiveTopology, }, #[error("Conservative Rasterization is only supported for wgt::PolygonMode::Fill")] ConservativeRasterizationNonFillPolygonMode, #[error(transparent)] MissingFeatures(#[from] MissingFeatures), #[error(transparent)] MissingDownlevelFlags(#[from] MissingDownlevelFlags), #[error("Error matching {stage:?} shader requirements against the pipeline")] Stage { stage: wgt::ShaderStages, #[source] error: validation::StageError, }, #[error("Internal error in {stage:?} shader: {error}")] Internal { stage: wgt::ShaderStages, error: String, }, #[error("Pipeline constant error in {stage:?} shader: {error}")] PipelineConstants { stage: wgt::ShaderStages, error: String, }, #[error("In the provided shader, the type given for group {group} binding {binding} has a size of {size}. As the device does not support `DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED`, the type must have a size that is a multiple of 16 bytes.")] UnalignedShader { group: u32, binding: u32, size: u64 }, #[error("Dual-source blending requires exactly one color target, but {count} color targets are present")] DualSourceBlendingWithMultipleColorTargets { count: usize }, #[error("{}", concat!( "At least one color attachment or depth-stencil attachment was expected, ", "but no render target for the pipeline was specified." ))] NoTargetSpecified, #[error(transparent)] InvalidResource(#[from] InvalidResourceError), } impl WebGpuError for CreateRenderPipelineError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::InvalidResource(e) => e.webgpu_error_type(), Self::MissingFeatures(e) => e.webgpu_error_type(), Self::MissingDownlevelFlags(e) => e.webgpu_error_type(), Self::Internal { .. } => ErrorType::Internal, Self::ColorAttachment(_) | Self::Implicit(_) | Self::ColorState(_, _) | Self::DepthStencilState(_) | Self::InvalidSampleCount(_) | Self::TooManyVertexBuffers { .. } | Self::TooManyVertexAttributes { .. } | Self::VertexAttributeLocationTooLarge { .. } | Self::VertexStrideTooLarge { .. } | Self::UnalignedVertexStride { .. } | Self::InvalidVertexAttributeOffset { .. } | Self::ShaderLocationClash(_) | Self::StripIndexFormatForNonStripTopology { .. } | Self::ConservativeRasterizationNonFillPolygonMode | Self::Stage { .. } | Self::UnalignedShader { .. } | Self::DualSourceBlendingWithMultipleColorTargets { .. } | Self::NoTargetSpecified | Self::PipelineConstants { .. } | Self::VertexAttributeStrideTooLarge { .. } => ErrorType::Validation, } } } bitflags::bitflags! { #[repr(transparent)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct PipelineFlags: u32 { const BLEND_CONSTANT = 1 << 0; const STENCIL_REFERENCE = 1 << 1; const WRITES_DEPTH = 1 << 2; const WRITES_STENCIL = 1 << 3; } } /// How a render pipeline will retrieve attributes from a particular vertex buffer. #[derive(Clone, Copy, Debug)] pub struct VertexStep { /// The byte stride in the buffer between one attribute value and the next. pub stride: wgt::BufferAddress, /// The byte size required to fit the last vertex in the stream. pub last_stride: wgt::BufferAddress, /// Whether the buffer is indexed by vertex number or instance number. pub mode: wgt::VertexStepMode, } impl Default for VertexStep { fn default() -> Self { Self { stride: 0, last_stride: 0, mode: wgt::VertexStepMode::Vertex, } } } #[derive(Debug)] pub struct RenderPipeline { pub(crate) raw: ManuallyDrop>, pub(crate) device: Arc, pub(crate) layout: Arc, pub(crate) _shader_modules: ArrayVec, { hal::MAX_CONCURRENT_SHADER_STAGES }>, pub(crate) pass_context: RenderPassContext, pub(crate) flags: PipelineFlags, pub(crate) topology: wgt::PrimitiveTopology, pub(crate) strip_index_format: Option, pub(crate) vertex_steps: Vec, pub(crate) late_sized_buffer_groups: ArrayVec, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, /// Whether this is a mesh shader pipeline pub(crate) is_mesh: bool, } impl Drop for RenderPipeline { fn drop(&mut self) { resource_log!("Destroy raw {}", self.error_ident()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; unsafe { self.device.raw().destroy_render_pipeline(raw); } } } crate::impl_resource_type!(RenderPipeline); crate::impl_labeled!(RenderPipeline); crate::impl_parent_device!(RenderPipeline); crate::impl_storage_item!(RenderPipeline); crate::impl_trackable!(RenderPipeline); impl RenderPipeline { pub(crate) fn raw(&self) -> &dyn hal::DynRenderPipeline { self.raw.as_ref() } pub fn get_bind_group_layout( self: &Arc, index: u32, ) -> Result, GetBindGroupLayoutError> { self.layout.get_bind_group_layout(index) } } ================================================ FILE: wgpu-core/src/pipeline_cache.rs ================================================ use thiserror::Error; use wgt::{ error::{ErrorType, WebGpuError}, AdapterInfo, }; pub const HEADER_LENGTH: usize = size_of::(); #[derive(Debug, PartialEq, Eq, Clone, Error)] #[non_exhaustive] pub enum PipelineCacheValidationError { #[error("The pipeline cache data was truncated")] Truncated, #[error("The pipeline cache data was longer than recorded")] // TODO: Is it plausible that this would happen Extended, #[error("The pipeline cache data was corrupted (e.g. the hash didn't match)")] Corrupted, #[error("The pipeline cacha data was out of date and so cannot be safely used")] Outdated, #[error("The cache data was created for a different device")] DeviceMismatch, #[error("Pipeline cacha data was created for a future version of wgpu")] Unsupported, } impl PipelineCacheValidationError { /// Could the error have been avoided? /// That is, is there a mistake in user code interacting with the cache pub fn was_avoidable(&self) -> bool { match self { PipelineCacheValidationError::DeviceMismatch => true, PipelineCacheValidationError::Truncated | PipelineCacheValidationError::Unsupported | PipelineCacheValidationError::Extended // It's unusual, but not implausible, to be downgrading wgpu | PipelineCacheValidationError::Outdated | PipelineCacheValidationError::Corrupted => false, } } } impl WebGpuError for PipelineCacheValidationError { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } } /// Validate the data in a pipeline cache pub fn validate_pipeline_cache<'d>( cache_data: &'d [u8], adapter: &AdapterInfo, validation_key: [u8; 16], ) -> Result<&'d [u8], PipelineCacheValidationError> { let adapter_key = adapter_key(adapter)?; let Some((header, remaining_data)) = PipelineCacheHeader::read(cache_data) else { return Err(PipelineCacheValidationError::Truncated); }; if header.magic != MAGIC { return Err(PipelineCacheValidationError::Corrupted); } if header.header_version != HEADER_VERSION { return Err(PipelineCacheValidationError::Outdated); } if header.cache_abi != ABI { return Err(PipelineCacheValidationError::Outdated); } if header.backend != adapter.backend as u8 { return Err(PipelineCacheValidationError::DeviceMismatch); } if header.adapter_key != adapter_key { return Err(PipelineCacheValidationError::DeviceMismatch); } if header.validation_key != validation_key { // If the validation key is wrong, that means that this device has changed // in a way where the cache won't be compatible since the cache was made, // so it is outdated return Err(PipelineCacheValidationError::Outdated); } let data_size: usize = header .data_size .try_into() // If the data was previously more than 4GiB, and we're still on a 32 bit system (ABI check, above) // Then the data must be corrupted .map_err(|_| PipelineCacheValidationError::Corrupted)?; if remaining_data.len() < data_size { return Err(PipelineCacheValidationError::Truncated); } if remaining_data.len() > data_size { return Err(PipelineCacheValidationError::Extended); } if header.hash_space != HASH_SPACE_VALUE { return Err(PipelineCacheValidationError::Corrupted); } Ok(remaining_data) } pub fn add_cache_header( in_region: &mut [u8], data: &[u8], adapter: &AdapterInfo, validation_key: [u8; 16], ) { assert_eq!(in_region.len(), HEADER_LENGTH); let header = PipelineCacheHeader { adapter_key: adapter_key(adapter) .expect("Called add_cache_header for an adapter which doesn't support cache data. This is a wgpu internal bug"), backend: adapter.backend as u8, cache_abi: ABI, magic: MAGIC, header_version: HEADER_VERSION, validation_key, hash_space: HASH_SPACE_VALUE, data_size: data .len() .try_into() .expect("Cache larger than u64::MAX bytes"), }; header.write(in_region); } const MAGIC: [u8; 8] = *b"WGPUPLCH"; const HEADER_VERSION: u32 = 1; const ABI: u32 = size_of::<*const ()>() as u32; /// The value used to fill [`PipelineCacheHeader::hash_space`] /// /// If we receive reports of pipeline cache data corruption which is not otherwise caught /// on a real device, it would be worth modifying this /// /// Note that wgpu does not protect against malicious writes to e.g. a file used /// to store a pipeline cache. /// That is the responsibility of the end application, such as by using a /// private space. const HASH_SPACE_VALUE: u64 = 0xFEDCBA9_876543210; #[repr(C)] #[derive(PartialEq, Eq)] struct PipelineCacheHeader { /// The magic header to ensure that we have the right file format /// Has a value of MAGIC, as above magic: [u8; 8], // /// The total size of this header, in bytes // header_size: u32, /// The version of this wgpu header /// Should be equal to HEADER_VERSION above /// /// This must always be the second item, after the value above header_version: u32, /// The number of bytes in the pointers of this ABI, because some drivers /// have previously not distinguished between their 32 bit and 64 bit drivers /// leading to Vulkan data corruption cache_abi: u32, /// The id for the backend in use, from [wgt::Backend] backend: u8, /// The key which identifiers the device/adapter. /// This is used to validate that this pipeline cache (probably) was produced for /// the expected device. /// On Vulkan: it is a combination of vendor ID and device ID adapter_key: [u8; 15], /// A key used to validate that this device is still compatible with the cache /// /// This should e.g. contain driver version and/or intermediate compiler versions validation_key: [u8; 16], /// The length of the data which is sent to/recieved from the backend data_size: u64, /// Space reserved for a hash of the data in future /// /// We assume that your cache storage system will be relatively robust, and so /// do not validate this hash /// /// Therefore, this will always have a value of [`HASH_SPACE_VALUE`] hash_space: u64, } impl PipelineCacheHeader { fn read(data: &[u8]) -> Option<(PipelineCacheHeader, &[u8])> { let mut reader = Reader { data, total_read: 0, }; let magic = reader.read_array()?; let header_version = reader.read_u32()?; let cache_abi = reader.read_u32()?; let backend = reader.read_byte()?; let adapter_key = reader.read_array()?; let validation_key = reader.read_array()?; let data_size = reader.read_u64()?; let data_hash = reader.read_u64()?; assert_eq!(reader.total_read, size_of::()); Some(( PipelineCacheHeader { magic, header_version, cache_abi, backend, adapter_key, validation_key, data_size, hash_space: data_hash, }, reader.data, )) } fn write(&self, into: &mut [u8]) -> Option<()> { let mut writer = Writer { data: into }; writer.write_array(&self.magic)?; writer.write_u32(self.header_version)?; writer.write_u32(self.cache_abi)?; writer.write_byte(self.backend)?; writer.write_array(&self.adapter_key)?; writer.write_array(&self.validation_key)?; writer.write_u64(self.data_size)?; writer.write_u64(self.hash_space)?; assert_eq!(writer.data.len(), 0); Some(()) } } fn adapter_key(adapter: &AdapterInfo) -> Result<[u8; 15], PipelineCacheValidationError> { match adapter.backend { wgt::Backend::Vulkan => { // If these change size, the header format needs to change // We set the type explicitly so this won't compile in that case let v: [u8; 4] = adapter.vendor.to_be_bytes(); let d: [u8; 4] = adapter.device.to_be_bytes(); let adapter = [ 255, 255, 255, v[0], v[1], v[2], v[3], d[0], d[1], d[2], d[3], 255, 255, 255, 255, ]; Ok(adapter) } _ => Err(PipelineCacheValidationError::Unsupported), } } struct Reader<'a> { data: &'a [u8], total_read: usize, } impl<'a> Reader<'a> { fn read_byte(&mut self) -> Option { let res = *self.data.first()?; self.total_read += 1; self.data = &self.data[1..]; Some(res) } fn read_array(&mut self) -> Option<[u8; N]> { // Only greater than because we're indexing fenceposts, not items if N > self.data.len() { return None; } let (start, data) = self.data.split_at(N); self.total_read += N; self.data = data; Some(start.try_into().expect("off-by-one-error in array size")) } // fn read_u16(&mut self) -> Option { // self.read_array().map(u16::from_be_bytes) // } fn read_u32(&mut self) -> Option { self.read_array().map(u32::from_be_bytes) } fn read_u64(&mut self) -> Option { self.read_array().map(u64::from_be_bytes) } } struct Writer<'a> { data: &'a mut [u8], } impl<'a> Writer<'a> { fn write_byte(&mut self, byte: u8) -> Option<()> { self.write_array(&[byte]) } fn write_array(&mut self, array: &[u8; N]) -> Option<()> { // Only greater than because we're indexing fenceposts, not items if N > self.data.len() { return None; } let data = core::mem::take(&mut self.data); let (start, data) = data.split_at_mut(N); self.data = data; start.copy_from_slice(array); Some(()) } // fn write_u16(&mut self, value: u16) -> Option<()> { // self.write_array(&value.to_be_bytes()) // } fn write_u32(&mut self, value: u32) -> Option<()> { self.write_array(&value.to_be_bytes()) } fn write_u64(&mut self, value: u64) -> Option<()> { self.write_array(&value.to_be_bytes()) } } #[cfg(test)] mod tests { use alloc::{string::String, vec::Vec}; use wgt::AdapterInfo; use crate::pipeline_cache::{PipelineCacheValidationError as E, HEADER_LENGTH}; use super::ABI; // Assert the correct size const _: [(); HEADER_LENGTH] = [(); 64]; const ADAPTER: AdapterInfo = AdapterInfo { name: String::new(), vendor: 0x0002_FEED, device: 0xFEFE_FEFE, device_type: wgt::DeviceType::Other, device_pci_bus_id: String::new(), driver: String::new(), driver_info: String::new(), backend: wgt::Backend::Vulkan, subgroup_min_size: 32, subgroup_max_size: 32, transient_saves_memory: true, }; // IMPORTANT: If these tests fail, then you MUST increment HEADER_VERSION const VALIDATION_KEY: [u8; 16] = u128::to_be_bytes(0xFFFFFFFF_FFFFFFFF_88888888_88888888); #[test] fn written_header() { let mut result = [0; HEADER_LENGTH]; super::add_cache_header(&mut result, &[], &ADAPTER, VALIDATION_KEY); let cache: [[u8; 8]; HEADER_LENGTH / 8] = [ *b"WGPUPLCH", // MAGIC [0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI [1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key [0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key 0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key 0x88888888_88888888u64.to_be_bytes(), // Validation key 0x0u64.to_be_bytes(), // Data size 0xFEDCBA9_876543210u64.to_be_bytes(), // Hash ]; let expected = cache.into_iter().flatten().collect::>(); assert_eq!(result.as_slice(), expected.as_slice()); } #[test] fn valid_data() { let cache: [[u8; 8]; HEADER_LENGTH / 8] = [ *b"WGPUPLCH", // MAGIC [0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI [1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key [0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key 0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key 0x88888888_88888888u64.to_be_bytes(), // Validation key 0x0u64.to_be_bytes(), // Data size 0xFEDCBA9_876543210u64.to_be_bytes(), // Hash ]; let cache = cache.into_iter().flatten().collect::>(); let expected: &[u8] = &[]; let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY); assert_eq!(validation_result, Ok(expected)); } #[test] fn invalid_magic() { let cache: [[u8; 8]; HEADER_LENGTH / 8] = [ *b"NOT_WGPU", // (Wrong) MAGIC [0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI [1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key [0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key 0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key 0x88888888_88888888u64.to_be_bytes(), // Validation key 0x0u64.to_be_bytes(), // Data size 0xFEDCBA9_876543210u64.to_be_bytes(), // Hash ]; let cache = cache.into_iter().flatten().collect::>(); let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY); assert_eq!(validation_result, Err(E::Corrupted)); } #[test] fn wrong_version() { let cache: [[u8; 8]; HEADER_LENGTH / 8] = [ *b"WGPUPLCH", // MAGIC [0, 0, 0, 2, 0, 0, 0, ABI as u8], // (wrong) Version and ABI [1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key [0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key 0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key 0x88888888_88888888u64.to_be_bytes(), // Validation key 0x0u64.to_be_bytes(), // Data size 0xFEDCBA9_876543210u64.to_be_bytes(), // Hash ]; let cache = cache.into_iter().flatten().collect::>(); let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY); assert_eq!(validation_result, Err(E::Outdated)); } #[test] fn wrong_abi() { let cache: [[u8; 8]; HEADER_LENGTH / 8] = [ *b"WGPUPLCH", // MAGIC // a 14 bit ABI is improbable [0, 0, 0, 1, 0, 0, 0, 14], // Version and (wrong) ABI [1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key [0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key 0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key 0x88888888_88888888u64.to_be_bytes(), // Validation key 0x0u64.to_be_bytes(), // Data size 0xFEDCBA9_876543210u64.to_be_bytes(), // Header ]; let cache = cache.into_iter().flatten().collect::>(); let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY); assert_eq!(validation_result, Err(E::Outdated)); } #[test] fn wrong_backend() { let cache: [[u8; 8]; HEADER_LENGTH / 8] = [ *b"WGPUPLCH", // MAGIC [0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI [2, 255, 255, 255, 0, 2, 0xFE, 0xED], // (wrong) Backend and Adapter key [0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key 0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key 0x88888888_88888888u64.to_be_bytes(), // Validation key 0x0u64.to_be_bytes(), // Data size 0xFEDCBA9_876543210u64.to_be_bytes(), // Hash ]; let cache = cache.into_iter().flatten().collect::>(); let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY); assert_eq!(validation_result, Err(E::DeviceMismatch)); } #[test] fn wrong_adapter() { let cache: [[u8; 8]; HEADER_LENGTH / 8] = [ *b"WGPUPLCH", // MAGIC [0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI [1, 255, 255, 255, 0, 2, 0xFE, 0x00], // Backend and (wrong) Adapter key [0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key 0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key 0x88888888_88888888u64.to_be_bytes(), // Validation key 0x0u64.to_be_bytes(), // Data size 0xFEDCBA9_876543210u64.to_be_bytes(), // Hash ]; let cache = cache.into_iter().flatten().collect::>(); let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY); assert_eq!(validation_result, Err(E::DeviceMismatch)); } #[test] fn wrong_validation() { let cache: [[u8; 8]; HEADER_LENGTH / 8] = [ *b"WGPUPLCH", // MAGIC [0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI [1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key [0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key 0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key 0x88888888_00000000u64.to_be_bytes(), // (wrong) Validation key 0x0u64.to_be_bytes(), // Data size 0xFEDCBA9_876543210u64.to_be_bytes(), // Hash ]; let cache = cache.into_iter().flatten().collect::>(); let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY); assert_eq!(validation_result, Err(E::Outdated)); } #[test] fn too_little_data() { let cache: [[u8; 8]; HEADER_LENGTH / 8] = [ *b"WGPUPLCH", // MAGIC [0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI [1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key [0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key 0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key 0x88888888_88888888u64.to_be_bytes(), // Validation key 0x064u64.to_be_bytes(), // Data size 0xFEDCBA9_876543210u64.to_be_bytes(), // Hash ]; let cache = cache.into_iter().flatten().collect::>(); let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY); assert_eq!(validation_result, Err(E::Truncated)); } #[test] fn not_no_data() { let cache: [[u8; 8]; HEADER_LENGTH / 8] = [ *b"WGPUPLCH", // MAGIC [0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI [1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key [0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key 0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key 0x88888888_88888888u64.to_be_bytes(), // Validation key 100u64.to_be_bytes(), // Data size 0xFEDCBA9_876543210u64.to_be_bytes(), // Hash ]; let cache = cache .into_iter() .flatten() .chain(core::iter::repeat_n(0u8, 100)) .collect::>(); let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY); let expected: &[u8] = &[0; 100]; assert_eq!(validation_result, Ok(expected)); } #[test] fn too_much_data() { let cache: [[u8; 8]; HEADER_LENGTH / 8] = [ *b"WGPUPLCH", // MAGIC [0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI [1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key [0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key 0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key 0x88888888_88888888u64.to_be_bytes(), // Validation key 0x064u64.to_be_bytes(), // Data size 0xFEDCBA9_876543210u64.to_be_bytes(), // Hash ]; let cache = cache .into_iter() .flatten() .chain(core::iter::repeat_n(0u8, 200)) .collect::>(); let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY); assert_eq!(validation_result, Err(E::Extended)); } #[test] fn wrong_hash() { let cache: [[u8; 8]; HEADER_LENGTH / 8] = [ *b"WGPUPLCH", // MAGIC [0, 0, 0, 1, 0, 0, 0, ABI as u8], // Version and ABI [1, 255, 255, 255, 0, 2, 0xFE, 0xED], // Backend and Adapter key [0xFE, 0xFE, 0xFE, 0xFE, 255, 255, 255, 255], // Backend and Adapter key 0xFFFFFFFF_FFFFFFFFu64.to_be_bytes(), // Validation key 0x88888888_88888888u64.to_be_bytes(), // Validation key 0x0u64.to_be_bytes(), // Data size 0x00000000_00000000u64.to_be_bytes(), // Hash ]; let cache = cache.into_iter().flatten().collect::>(); let validation_result = super::validate_pipeline_cache(&cache, &ADAPTER, VALIDATION_KEY); assert_eq!(validation_result, Err(E::Corrupted)); } } ================================================ FILE: wgpu-core/src/pool.rs ================================================ use alloc::sync::{Arc, Weak}; use core::hash::Hash; use hashbrown::{hash_map::Entry, HashMap}; use once_cell::sync::OnceCell; use crate::lock::{rank, Mutex}; use crate::FastHashMap; type SlotInner = Weak; type ResourcePoolSlot = Arc>>; pub struct ResourcePool { inner: Mutex>>, } impl ResourcePool { pub fn new() -> Self { Self { inner: Mutex::new(rank::RESOURCE_POOL_INNER, HashMap::default()), } } /// Get a resource from the pool with the given entry map, or create a new /// one if it doesn't exist using the given constructor. /// /// Behaves such that only one resource will be created for each unique /// entry map at any one time. pub fn get_or_init(&self, key: K, constructor: F) -> Result, E> where F: FnOnce(K) -> Result, E>, { // We can't prove at compile time that these will only ever be consumed once, // so we need to do the check at runtime. let mut key = Some(key); let mut constructor = Some(constructor); 'race: loop { let mut map_guard = self.inner.lock(); let entry = match map_guard.entry(key.clone().unwrap()) { // An entry exists for this resource. // // We know that either: // - The resource is still alive, and Weak::upgrade will succeed. // - The resource is in the process of being dropped, and Weak::upgrade will fail. // // The entry will never be empty while the BGL is still alive. Entry::Occupied(entry) => Arc::clone(entry.get()), // No entry exists for this resource. // // We know that the resource is not alive, so we can create a new entry. Entry::Vacant(entry) => Arc::clone(entry.insert(Arc::new(OnceCell::new()))), }; drop(map_guard); // Some other thread may beat us to initializing the entry, but OnceCell guarantees that only one thread // will actually initialize the entry. // // We pass the strong reference outside of the closure to keep it alive while we're the only one keeping a reference to it. let mut strong = None; let weak = entry.get_or_try_init(|| { let strong_inner = constructor.take().unwrap()(key.take().unwrap())?; let weak = Arc::downgrade(&strong_inner); strong = Some(strong_inner); Ok(weak) })?; // If strong is Some, that means we just initialized the entry, so we can just return it. if let Some(strong) = strong { return Ok(strong); } // The entry was already initialized by someone else, so we need to try to upgrade it. if let Some(strong) = weak.upgrade() { // We succeed, the resource is still alive, just return that. return Ok(strong); } // The resource is in the process of being dropped, because upgrade failed. // The entry still exists in the map, but it points to nothing. // // We're in a race with the drop implementation of the resource, // so lets just go around again. When we go around again: // - If the entry exists, we might need to go around a few more times. // - If the entry doesn't exist, we'll create a new one. continue 'race; } } /// Remove the given entry map from the pool. /// /// Must *only* be called in the Drop impl of [`BindGroupLayout`]. /// /// [`BindGroupLayout`]: crate::binding_model::BindGroupLayout pub fn remove(&self, key: &K) { let mut map_guard = self.inner.lock(); // Weak::upgrade will be failing long before this code is called. All threads trying to access the resource will be spinning, // waiting for the entry to be removed. It is safe to remove the entry from the map. map_guard.remove(key); } } #[cfg(test)] mod tests { use core::{ sync::atomic::{AtomicU32, Ordering}, time::Duration, }; use std::{eprintln, sync::Barrier, thread}; use super::*; #[test] fn deduplication() { let pool = ResourcePool::::new(); let mut counter = 0_u32; let arc1 = pool .get_or_init::<_, ()>(0, |key| { counter += 1; Ok(Arc::new(key)) }) .unwrap(); assert_eq!(*arc1, 0); assert_eq!(counter, 1); let arc2 = pool .get_or_init::<_, ()>(0, |key| { counter += 1; Ok(Arc::new(key)) }) .unwrap(); assert!(Arc::ptr_eq(&arc1, &arc2)); assert_eq!(*arc2, 0); assert_eq!(counter, 1); drop(arc1); drop(arc2); pool.remove(&0); let arc3 = pool .get_or_init::<_, ()>(0, |key| { counter += 1; Ok(Arc::new(key)) }) .unwrap(); assert_eq!(*arc3, 0); assert_eq!(counter, 2); } // Test name has "2_threads" in the name so nextest reserves two threads for it. #[test] fn concurrent_creation_2_threads() { struct Resources { pool: ResourcePool, counter: AtomicU32, barrier: Barrier, } let resources = Arc::new(Resources { pool: ResourcePool::::new(), counter: AtomicU32::new(0), barrier: Barrier::new(2), }); // Like all races, this is not inherently guaranteed to work, but in practice it should work fine. // // To validate the expected order of events, we've put print statements in the code, indicating when each thread is at a certain point. // The output will look something like this if the test is working as expected: // // ``` // 0: prewait // 1: prewait // 1: postwait // 0: postwait // 1: init // 1: postget // 0: postget // ``` fn thread_inner(idx: u8, resources: &Resources) -> Arc { eprintln!("{idx}: prewait"); // Once this returns, both threads should hit get_or_init at about the same time, // allowing us to actually test concurrent creation. // // Like all races, this is not inherently guaranteed to work, but in practice it should work fine. resources.barrier.wait(); eprintln!("{idx}: postwait"); let ret = resources .pool .get_or_init::<_, ()>(0, |key| { eprintln!("{idx}: init"); // Simulate long running constructor, ensuring that both threads will be in get_or_init. thread::sleep(Duration::from_millis(250)); resources.counter.fetch_add(1, Ordering::SeqCst); Ok(Arc::new(key)) }) .unwrap(); eprintln!("{idx}: postget"); ret } let thread1 = thread::spawn({ let resource_clone = Arc::clone(&resources); move || thread_inner(1, &resource_clone) }); let arc0 = thread_inner(0, &resources); assert_eq!(resources.counter.load(Ordering::Acquire), 1); let arc1 = thread1.join().unwrap(); assert!(Arc::ptr_eq(&arc0, &arc1)); } // Test name has "2_threads" in the name so nextest reserves two threads for it. #[test] fn create_while_drop_2_threads() { struct Resources { pool: ResourcePool, barrier: Barrier, } let resources = Arc::new(Resources { pool: ResourcePool::::new(), barrier: Barrier::new(2), }); // Like all races, this is not inherently guaranteed to work, but in practice it should work fine. // // To validate the expected order of events, we've put print statements in the code, indicating when each thread is at a certain point. // The output will look something like this if the test is working as expected: // // ``` // 0: prewait // 1: prewait // 1: postwait // 0: postwait // 1: postsleep // 1: removal // 0: postget // ``` // // The last two _may_ be flipped. let existing_entry = resources .pool .get_or_init::<_, ()>(0, |key| Ok(Arc::new(key))) .unwrap(); // Drop the entry, but do _not_ remove it from the pool. // This simulates the situation where the resource arc has been dropped, but the Drop implementation // has not yet run, which calls remove. drop(existing_entry); fn thread0_inner(resources: &Resources) { eprintln!("0: prewait"); resources.barrier.wait(); eprintln!("0: postwait"); // We try to create a new entry, but the entry already exists. // // As Arc::upgrade is failing, we will just keep spinning until remove is called. resources .pool .get_or_init::<_, ()>(0, |key| Ok(Arc::new(key))) .unwrap(); eprintln!("0: postget"); } fn thread1_inner(resources: &Resources) { eprintln!("1: prewait"); resources.barrier.wait(); eprintln!("1: postwait"); // We wait a little bit, making sure that thread0_inner has started spinning. thread::sleep(Duration::from_millis(250)); eprintln!("1: postsleep"); // We remove the entry from the pool, allowing thread0_inner to re-create. resources.pool.remove(&0); eprintln!("1: removal"); } let thread1 = thread::spawn({ let resource_clone = Arc::clone(&resources); move || thread1_inner(&resource_clone) }); thread0_inner(&resources); thread1.join().unwrap(); } } ================================================ FILE: wgpu-core/src/present.rs ================================================ /*! Presentation. ## Lifecycle Whenever a submission detects the use of any surface texture, it adds it to the device tracker for the duration of the submission (temporarily, while recording). It's added with `UNINITIALIZED` state and transitioned into `empty()` state. When this texture is presented, we remove it from the device tracker as well as extract it from the hub. !*/ use alloc::{sync::Arc, vec::Vec}; use core::mem::ManuallyDrop; #[cfg(feature = "trace")] use crate::device::trace::{Action, IntoTrace}; use crate::{ conv, device::{Device, DeviceError, MissingDownlevelFlags, WaitIdleError}, global::Global, hal_label, id, instance::Surface, resource, }; use thiserror::Error; use wgt::{ error::{ErrorType, WebGpuError}, SurfaceStatus as Status, }; const FRAME_TIMEOUT_MS: u32 = 1000; #[derive(Debug)] pub(crate) struct Presentation { pub(crate) device: Arc, pub(crate) config: wgt::SurfaceConfiguration>, pub(crate) acquired_texture: Option>, } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum SurfaceError { #[error("Surface is invalid")] Invalid, #[error("Surface is not configured for presentation")] NotConfigured, #[error(transparent)] Device(#[from] DeviceError), #[error("Surface image is already acquired")] AlreadyAcquired, #[error("Texture has been destroyed")] TextureDestroyed, } impl WebGpuError for SurfaceError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::Invalid | Self::NotConfigured | Self::AlreadyAcquired | Self::TextureDestroyed => ErrorType::Validation, } } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum ConfigureSurfaceError { #[error(transparent)] Device(#[from] DeviceError), #[error("Invalid surface")] InvalidSurface, #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")] InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat), #[error(transparent)] MissingDownlevelFlags(#[from] MissingDownlevelFlags), #[error("`SurfaceOutput` must be dropped before a new `Surface` is made")] PreviousOutputExists, #[error("Failed to wait for GPU to come idle before reconfiguring the Surface")] GpuWaitTimeout, #[error("Both `Surface` width and height must be non-zero. Wait to recreate the `Surface` until the window has non-zero area.")] ZeroArea, #[error("`Surface` width and height must be within the maximum supported texture size. Requested was ({width}, {height}), maximum extent for either dimension is {max_texture_dimension_2d}.")] TooLarge { width: u32, height: u32, max_texture_dimension_2d: u32, }, #[error("Surface does not support the adapter's queue family")] UnsupportedQueueFamily, #[error("Requested format {requested:?} is not in list of supported formats: {available:?}")] UnsupportedFormat { requested: wgt::TextureFormat, available: Vec, }, #[error("Requested present mode {requested:?} is not in the list of supported present modes: {available:?}")] UnsupportedPresentMode { requested: wgt::PresentMode, available: Vec, }, #[error("Requested alpha mode {requested:?} is not in the list of supported alpha modes: {available:?}")] UnsupportedAlphaMode { requested: wgt::CompositeAlphaMode, available: Vec, }, #[error("Requested usage {requested:?} is not in the list of supported usages: {available:?}")] UnsupportedUsage { requested: wgt::TextureUses, available: wgt::TextureUses, }, } impl From for ConfigureSurfaceError { fn from(e: WaitIdleError) -> Self { match e { WaitIdleError::Device(d) => ConfigureSurfaceError::Device(d), WaitIdleError::WrongSubmissionIndex(..) => unreachable!(), WaitIdleError::Timeout => ConfigureSurfaceError::GpuWaitTimeout, } } } impl WebGpuError for ConfigureSurfaceError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::MissingDownlevelFlags(e) => e.webgpu_error_type(), Self::InvalidSurface | Self::InvalidViewFormat(..) | Self::PreviousOutputExists | Self::GpuWaitTimeout | Self::ZeroArea | Self::TooLarge { .. } | Self::UnsupportedQueueFamily | Self::UnsupportedFormat { .. } | Self::UnsupportedPresentMode { .. } | Self::UnsupportedAlphaMode { .. } | Self::UnsupportedUsage { .. } => ErrorType::Validation, } } } pub type ResolvedSurfaceOutput = SurfaceOutput>; #[repr(C)] #[derive(Debug)] pub struct SurfaceOutput { pub status: Status, pub texture: Option, } impl Surface { pub fn get_current_texture(&self) -> Result { profiling::scope!("Surface::get_current_texture"); let (device, config) = if let Some(ref present) = *self.presentation.lock() { present.device.check_is_valid()?; (present.device.clone(), present.config.clone()) } else { return Err(SurfaceError::NotConfigured); }; let fence = device.fence.read(); let suf = self.raw(device.backend()).unwrap(); let (texture, status) = match unsafe { suf.acquire_texture( Some(core::time::Duration::from_millis(FRAME_TIMEOUT_MS as u64)), fence.as_ref(), ) } { Ok(ast) => { drop(fence); let texture_desc = wgt::TextureDescriptor { label: hal_label( Some(alloc::borrow::Cow::Borrowed("")), device.instance_flags, ), size: wgt::Extent3d { width: config.width, height: config.height, depth_or_array_layers: 1, }, sample_count: 1, mip_level_count: 1, format: config.format, dimension: wgt::TextureDimension::D2, usage: config.usage, view_formats: config.view_formats, }; let format_features = wgt::TextureFormatFeatures { allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT, flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4 | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE, }; let hal_usage = conv::map_texture_usage( config.usage, config.format.into(), format_features.flags, ); let clear_view_desc = hal::TextureViewDescriptor { label: hal_label( Some("(wgpu internal) clear surface texture view"), device.instance_flags, ), format: config.format, dimension: wgt::TextureViewDimension::D2, usage: wgt::TextureUses::COLOR_TARGET, range: wgt::ImageSubresourceRange::default(), }; let clear_view = unsafe { device .raw() .create_texture_view(ast.texture.as_ref().borrow(), &clear_view_desc) } .map_err(|e| device.handle_hal_error(e))?; let mut presentation = self.presentation.lock(); let present = presentation.as_mut().unwrap(); let texture = resource::Texture::new( &device, resource::TextureInner::Surface { raw: ast.texture }, hal_usage, &texture_desc, format_features, resource::TextureClearMode::Surface { clear_view: ManuallyDrop::new(clear_view), }, true, ); let texture = Arc::new(texture); device .trackers .lock() .textures .insert_single(&texture, wgt::TextureUses::UNINITIALIZED); if present.acquired_texture.is_some() { return Err(SurfaceError::AlreadyAcquired); } present.acquired_texture = Some(texture.clone()); let status = if ast.suboptimal { Status::Suboptimal } else { Status::Good }; (Some(texture), status) } Err(err) => ( None, match err { hal::SurfaceError::Timeout => Status::Timeout, hal::SurfaceError::Occluded => Status::Occluded, hal::SurfaceError::Lost => Status::Lost, hal::SurfaceError::Device(err) => { return Err(device.handle_hal_error(err).into()); } hal::SurfaceError::Outdated => Status::Outdated, hal::SurfaceError::Other(msg) => { log::error!("acquire error: {msg}"); Status::Lost } }, ), }; Ok(ResolvedSurfaceOutput { status, texture }) } pub fn present(&self) -> Result { profiling::scope!("Surface::present"); let mut presentation = self.presentation.lock(); let present = match presentation.as_mut() { Some(present) => present, None => return Err(SurfaceError::NotConfigured), }; let device = &present.device; device.check_is_valid()?; let queue = device.get_queue().unwrap(); let texture = present .acquired_texture .take() .ok_or(SurfaceError::AlreadyAcquired)?; let mut exclusive_snatch_guard = device.snatchable_lock.write(); let inner = texture.inner.snatch(&mut exclusive_snatch_guard); drop(exclusive_snatch_guard); let result = match inner { None => return Err(SurfaceError::TextureDestroyed), Some(resource::TextureInner::Surface { raw }) => { let raw_surface = self.raw(device.backend()).unwrap(); let raw_queue = queue.raw(); let _fence_lock = device.fence.write(); unsafe { raw_queue.present(raw_surface, raw) } } _ => unreachable!(), }; match result { Ok(()) => Ok(Status::Good), Err(err) => match err { hal::SurfaceError::Timeout => Ok(Status::Timeout), hal::SurfaceError::Occluded => Ok(Status::Occluded), hal::SurfaceError::Lost => Ok(Status::Lost), hal::SurfaceError::Device(err) => { Err(SurfaceError::from(device.handle_hal_error(err))) } hal::SurfaceError::Outdated => Ok(Status::Outdated), hal::SurfaceError::Other(msg) => { log::error!("present error: {msg}"); Err(SurfaceError::Invalid) } }, } } pub fn discard(&self) -> Result<(), SurfaceError> { profiling::scope!("Surface::discard"); let mut presentation = self.presentation.lock(); let present = match presentation.as_mut() { Some(present) => present, None => return Err(SurfaceError::NotConfigured), }; let device = &present.device; device.check_is_valid()?; let texture = present .acquired_texture .take() .ok_or(SurfaceError::AlreadyAcquired)?; let mut exclusive_snatch_guard = device.snatchable_lock.write(); let inner = texture.inner.snatch(&mut exclusive_snatch_guard); drop(exclusive_snatch_guard); match inner { None => return Err(SurfaceError::TextureDestroyed), Some(resource::TextureInner::Surface { raw }) => { let raw_surface = self.raw(device.backend()).unwrap(); unsafe { raw_surface.discard_texture(raw) }; } _ => unreachable!(), } Ok(()) } } impl Global { pub fn surface_get_current_texture( &self, surface_id: id::SurfaceId, texture_id_in: Option, ) -> Result { let surface = self.surfaces.get(surface_id); let fid = self.hub.textures.prepare(texture_id_in); let output = surface.get_current_texture()?; #[cfg(feature = "trace")] if let Some(present) = surface.presentation.lock().as_ref() { if let Some(ref mut trace) = *present.device.trace.lock() { if let Some(texture) = present.acquired_texture.as_ref() { trace.add(Action::GetSurfaceTexture { id: texture.to_trace(), parent: surface.to_trace(), }); } } } let status = output.status; let texture_id = output .texture .map(|texture| fid.assign(resource::Fallible::Valid(texture))); Ok(SurfaceOutput { status, texture: texture_id, }) } pub fn surface_present(&self, surface_id: id::SurfaceId) -> Result { let surface = self.surfaces.get(surface_id); #[cfg(feature = "trace")] if let Some(present) = surface.presentation.lock().as_ref() { if let Some(ref mut trace) = *present.device.trace.lock() { trace.add(Action::Present(surface.to_trace())); } } surface.present() } pub fn surface_texture_discard(&self, surface_id: id::SurfaceId) -> Result<(), SurfaceError> { let surface = self.surfaces.get(surface_id); #[cfg(feature = "trace")] if let Some(present) = surface.presentation.lock().as_ref() { if let Some(ref mut trace) = *present.device.trace.lock() { trace.add(Action::DiscardSurfaceTexture(surface.to_trace())); } } surface.discard() } } ================================================ FILE: wgpu-core/src/ray_tracing.rs ================================================ // Ray tracing // Major missing optimizations (no api surface changes needed): // - use custom tracker to track build state // - no forced rebuilt (build mode deduction) // - lazy instance buffer allocation // - maybe share scratch and instance staging buffer allocation // - partial instance buffer uploads (api surface already designed with this in mind) // - Batch BLAS read-backs (if it shows up in performance). // - ([non performance] extract function in build (rust function extraction with guards is a pain)) use alloc::{boxed::Box, sync::Arc, vec::Vec}; #[cfg(feature = "serde")] use macro_rules_attribute::apply; use thiserror::Error; use wgt::{ error::{ErrorType, WebGpuError}, AccelerationStructureGeometryFlags, BufferAddress, IndexFormat, VertexFormat, }; #[cfg(feature = "serde")] use crate::command::serde_object_reference_struct; use crate::{ command::{ArcReferences, EncoderStateError, IdReferences, ReferenceType}, device::{DeviceError, MissingFeatures}, id::{BlasId, BufferId, TlasId}, resource::{ Blas, BlasCompactCallback, BlasPrepareCompactResult, DestroyedResourceError, InvalidResourceError, MissingBufferUsageError, ResourceErrorIdent, Tlas, }, }; #[derive(Clone, Debug, Error)] pub enum CreateBlasError { #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] MissingFeatures(#[from] MissingFeatures), #[error( "Only one of 'index_count' and 'index_format' was provided (either provide both or none)" )] MissingIndexData, #[error("Provided format was not within allowed formats. Provided format: {0:?}. Allowed formats: {1:?}")] InvalidVertexFormat(VertexFormat, Vec), #[error("Limit `max_blas_geometry_count` is {0}, but the BLAS had {1} geometries")] TooManyGeometries(u32, u32), #[error( "Limit `max_blas_primitive_count` is {0}, but the BLAS had a maximum of {1} primitives" )] TooManyPrimitives(u32, u32), } impl WebGpuError for CreateBlasError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::MissingFeatures(e) => e.webgpu_error_type(), Self::MissingIndexData | Self::InvalidVertexFormat(..) | Self::TooManyGeometries(..) | Self::TooManyPrimitives(..) => ErrorType::Validation, } } } #[derive(Clone, Debug, Error)] pub enum CreateTlasError { #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] MissingFeatures(#[from] MissingFeatures), #[error("Flag {0:?} is not allowed on a TLAS")] DisallowedFlag(wgt::AccelerationStructureFlags), #[error("Limit `max_tlas_instance_count` is {0}, but the TLAS had a maximum of {1} instances")] TooManyInstances(u32, u32), } impl WebGpuError for CreateTlasError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::MissingFeatures(e) => e.webgpu_error_type(), Self::DisallowedFlag(..) | Self::TooManyInstances(..) => ErrorType::Validation, } } } /// Error encountered while attempting to do a copy on a command encoder. #[derive(Clone, Debug, Error)] pub enum BuildAccelerationStructureError { #[error(transparent)] EncoderState(#[from] EncoderStateError), #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] InvalidResource(#[from] InvalidResourceError), #[error(transparent)] DestroyedResource(#[from] DestroyedResourceError), #[error(transparent)] MissingBufferUsage(#[from] MissingBufferUsageError), #[error(transparent)] MissingFeatures(#[from] MissingFeatures), #[error( "Buffer {0:?} size is insufficient for provided size information (size: {1}, required: {2}" )] InsufficientBufferSize(ResourceErrorIdent, u64, u64), #[error("Buffer {0:?} associated offset doesn't align with the index type")] UnalignedIndexBufferOffset(ResourceErrorIdent), #[error("Buffer {0:?} associated offset is unaligned")] UnalignedTransformBufferOffset(ResourceErrorIdent), #[error("Buffer {0:?} associated index count not divisible by 3 (count: {1}")] InvalidIndexCount(ResourceErrorIdent, u32), #[error("Buffer {0:?} associated data contains None")] MissingAssociatedData(ResourceErrorIdent), #[error( "Blas {0:?} build sizes to may be greater than the descriptor at build time specified" )] IncompatibleBlasBuildSizes(ResourceErrorIdent), #[error("Blas {0:?} flags are different, creation flags: {1:?}, provided: {2:?}")] IncompatibleBlasFlags( ResourceErrorIdent, AccelerationStructureGeometryFlags, AccelerationStructureGeometryFlags, ), #[error("Blas {0:?} build vertex count is greater than creation count (needs to be less than or equal to), creation: {1:?}, build: {2:?}")] IncompatibleBlasVertexCount(ResourceErrorIdent, u32, u32), #[error("Blas {0:?} vertex formats are different, creation format: {1:?}, provided: {2:?}")] DifferentBlasVertexFormats(ResourceErrorIdent, VertexFormat, VertexFormat), #[error("Blas {0:?} stride was required to be at least {1} but stride given was {2}")] VertexStrideTooSmall(ResourceErrorIdent, u64, u64), #[error("Blas {0:?} stride was required to be a multiple of {1} but stride given was {2}")] VertexStrideUnaligned(ResourceErrorIdent, u64, u64), #[error("Blas {0:?} index count was provided at creation or building, but not the other")] BlasIndexCountProvidedMismatch(ResourceErrorIdent), #[error("Blas {0:?} build index count is greater than creation count (needs to be less than or equal to), creation: {1:?}, build: {2:?}")] IncompatibleBlasIndexCount(ResourceErrorIdent, u32, u32), #[error("Blas {0:?} index formats are different, creation format: {1:?}, provided: {2:?}")] DifferentBlasIndexFormats(ResourceErrorIdent, Option, Option), #[error("Blas {0:?} is compacted and so cannot be built")] CompactedBlas(ResourceErrorIdent), #[error("Blas {0:?} build sizes require index buffer but none was provided")] MissingIndexBuffer(ResourceErrorIdent), #[error( "Tlas {0:?} an associated instances contains an invalid custom index (more than 24bits)" )] TlasInvalidCustomIndex(ResourceErrorIdent), #[error( "Tlas {0:?} has {1} active instances but only {2} are allowed as specified by the descriptor at creation" )] TlasInstanceCountExceeded(ResourceErrorIdent, u32, u32), #[error("Blas {0:?} has flag USE_TRANSFORM but the transform buffer is missing")] TransformMissing(ResourceErrorIdent), #[error("Blas {0:?} is missing the flag USE_TRANSFORM but the transform buffer is set")] UseTransformMissing(ResourceErrorIdent), #[error( "Tlas {0:?} dependent {1:?} is missing AccelerationStructureFlags::ALLOW_RAY_HIT_VERTEX_RETURN" )] TlasDependentMissingVertexReturn(ResourceErrorIdent, ResourceErrorIdent), } impl WebGpuError for BuildAccelerationStructureError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::EncoderState(e) => e.webgpu_error_type(), Self::Device(e) => e.webgpu_error_type(), Self::InvalidResource(e) => e.webgpu_error_type(), Self::DestroyedResource(e) => e.webgpu_error_type(), Self::MissingBufferUsage(e) => e.webgpu_error_type(), Self::MissingFeatures(e) => e.webgpu_error_type(), Self::InsufficientBufferSize(..) | Self::UnalignedIndexBufferOffset(..) | Self::UnalignedTransformBufferOffset(..) | Self::InvalidIndexCount(..) | Self::MissingAssociatedData(..) | Self::IncompatibleBlasBuildSizes(..) | Self::IncompatibleBlasFlags(..) | Self::IncompatibleBlasVertexCount(..) | Self::DifferentBlasVertexFormats(..) | Self::VertexStrideTooSmall(..) | Self::VertexStrideUnaligned(..) | Self::BlasIndexCountProvidedMismatch(..) | Self::IncompatibleBlasIndexCount(..) | Self::DifferentBlasIndexFormats(..) | Self::CompactedBlas(..) | Self::MissingIndexBuffer(..) | Self::TlasInvalidCustomIndex(..) | Self::TlasInstanceCountExceeded(..) | Self::TransformMissing(..) | Self::UseTransformMissing(..) | Self::TlasDependentMissingVertexReturn(..) => ErrorType::Validation, } } } #[derive(Clone, Debug, Error)] pub enum ValidateAsActionsError { #[error(transparent)] DestroyedResource(#[from] DestroyedResourceError), #[error("Tlas {0:?} is used before it is built")] UsedUnbuiltTlas(ResourceErrorIdent), #[error("Blas {0:?} is used before it is built (in Tlas {1:?})")] UsedUnbuiltBlas(ResourceErrorIdent, ResourceErrorIdent), #[error("Blas {0:?} is newer than the containing Tlas {1:?}")] BlasNewerThenTlas(ResourceErrorIdent, ResourceErrorIdent), } impl WebGpuError for ValidateAsActionsError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::DestroyedResource(e) => e.webgpu_error_type(), Self::UsedUnbuiltTlas(..) | Self::UsedUnbuiltBlas(..) | Self::BlasNewerThenTlas(..) => { ErrorType::Validation } } } } #[derive(Debug)] pub struct BlasTriangleGeometry<'a> { pub size: &'a wgt::BlasTriangleGeometrySizeDescriptor, pub vertex_buffer: BufferId, pub index_buffer: Option, pub transform_buffer: Option, pub first_vertex: u32, pub vertex_stride: BufferAddress, pub first_index: Option, pub transform_buffer_offset: Option, } pub enum BlasGeometries<'a> { TriangleGeometries(Box> + 'a>), } pub struct BlasBuildEntry<'a> { pub blas_id: BlasId, pub geometries: BlasGeometries<'a>, } #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TlasBuildEntry { pub tlas_id: TlasId, pub instance_buffer_id: BufferId, pub instance_count: u32, } #[derive(Debug)] pub struct TlasInstance<'a> { pub blas_id: BlasId, pub transform: &'a [f32; 12], pub custom_data: u32, pub mask: u8, } pub struct TlasPackage<'a> { pub tlas_id: TlasId, pub instances: Box>> + 'a>, pub lowest_unmodified: u32, } #[derive(Debug, Clone)] pub(crate) struct TlasBuild { pub tlas: Arc, pub dependencies: Vec>, } #[derive(Debug, Clone, Default)] pub(crate) struct AsBuild { pub blas_s_built: Vec>, pub tlas_s_built: Vec, } impl AsBuild { pub(crate) fn with_capacity(blas: usize, tlas: usize) -> Self { Self { blas_s_built: Vec::with_capacity(blas), tlas_s_built: Vec::with_capacity(tlas), } } } #[derive(Debug, Clone)] pub(crate) enum AsAction { Build(AsBuild), UseTlas(Arc), } /// Like [`BlasTriangleGeometry`], but with owned data. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", apply(serde_object_reference_struct))] pub struct OwnedBlasTriangleGeometry { pub size: wgt::BlasTriangleGeometrySizeDescriptor, pub vertex_buffer: R::Buffer, pub index_buffer: Option, pub transform_buffer: Option, pub first_vertex: u32, pub vertex_stride: BufferAddress, pub first_index: Option, pub transform_buffer_offset: Option, } pub type ArcBlasTriangleGeometry = OwnedBlasTriangleGeometry; pub type TraceBlasTriangleGeometry = OwnedBlasTriangleGeometry; #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", apply(serde_object_reference_struct))] pub enum OwnedBlasGeometries { TriangleGeometries(Vec>), } pub type ArcBlasGeometries = OwnedBlasGeometries; pub type TraceBlasGeometries = OwnedBlasGeometries; #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", apply(serde_object_reference_struct))] pub struct OwnedBlasBuildEntry { pub blas: R::Blas, pub geometries: OwnedBlasGeometries, } pub type ArcBlasBuildEntry = OwnedBlasBuildEntry; pub type TraceBlasBuildEntry = OwnedBlasBuildEntry; #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", apply(serde_object_reference_struct))] pub struct OwnedTlasInstance { pub blas: R::Blas, pub transform: [f32; 12], pub custom_data: u32, pub mask: u8, } pub type ArcTlasInstance = OwnedTlasInstance; pub type TraceTlasInstance = OwnedTlasInstance; #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", apply(serde_object_reference_struct))] pub struct OwnedTlasPackage { pub tlas: R::Tlas, pub instances: Vec>>, pub lowest_unmodified: u32, } pub type TraceTlasPackage = OwnedTlasPackage; pub type ArcTlasPackage = OwnedTlasPackage; /// [`BlasTriangleGeometry`], without the resources. #[derive(Debug, Clone)] pub struct BlasTriangleGeometryInfo { pub size: wgt::BlasTriangleGeometrySizeDescriptor, pub first_vertex: u32, pub vertex_stride: BufferAddress, pub first_index: Option, pub transform_buffer_offset: Option, } #[derive(Clone, Debug, Error)] pub enum BlasPrepareCompactError { #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] InvalidResource(#[from] InvalidResourceError), #[error("Compaction is already being prepared")] CompactionPreparingAlready, #[error("Cannot compact an already compacted BLAS")] DoubleCompaction, #[error("BLAS is not yet built")] NotBuilt, #[error("BLAS does not support compaction (is AccelerationStructureFlags::ALLOW_COMPACTION missing?)")] CompactionUnsupported, } impl WebGpuError for BlasPrepareCompactError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::InvalidResource(e) => e.webgpu_error_type(), Self::CompactionPreparingAlready | Self::DoubleCompaction | Self::NotBuilt | Self::CompactionUnsupported => ErrorType::Validation, } } } #[derive(Clone, Debug, Error)] pub enum CompactBlasError { #[error(transparent)] Encoder(#[from] EncoderStateError), #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] InvalidResource(#[from] InvalidResourceError), #[error(transparent)] DestroyedResource(#[from] DestroyedResourceError), #[error(transparent)] MissingFeatures(#[from] MissingFeatures), #[error("BLAS was not prepared for compaction")] BlasNotReady, } impl WebGpuError for CompactBlasError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Encoder(e) => e.webgpu_error_type(), Self::Device(e) => e.webgpu_error_type(), Self::InvalidResource(e) => e.webgpu_error_type(), Self::DestroyedResource(e) => e.webgpu_error_type(), Self::MissingFeatures(e) => e.webgpu_error_type(), Self::BlasNotReady => ErrorType::Validation, } } } pub type BlasCompactReadyPendingClosure = (Option, BlasPrepareCompactResult); ================================================ FILE: wgpu-core/src/registry.rs ================================================ use alloc::sync::Arc; use crate::{ id::Id, identity::IdentityManager, lock::{rank, RwLock, RwLockReadGuard}, storage::{Element, Storage, StorageItem}, }; #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub struct RegistryReport { pub num_allocated: usize, pub num_kept_from_user: usize, pub num_released_from_user: usize, pub element_size: usize, } impl RegistryReport { pub fn is_empty(&self) -> bool { self.num_allocated + self.num_kept_from_user == 0 } } /// Registry is the primary holder of each resource type /// Every resource is now arcanized so the last arc released /// will in the end free the memory and release the inner raw resource /// /// Registry act as the main entry point to keep resource alive /// when created and released from user land code /// /// A resource may still be alive when released from user land code /// if it's used in active submission or anyway kept alive from /// any other dependent resource /// #[derive(Debug)] pub(crate) struct Registry { // Must only contain an id which has either never been used or has been released from `storage` identity: Arc>, storage: RwLock>, } impl Registry { pub(crate) fn new() -> Self { Self { identity: Arc::new(IdentityManager::new()), storage: RwLock::new(rank::REGISTRY_STORAGE, Storage::new()), } } } #[must_use] pub(crate) struct FutureId<'a, T: StorageItem> { id: Id, data: &'a RwLock>, } impl FutureId<'_, T> { /// Assign a new resource to this ID. /// /// Registers it with the registry. pub fn assign(self, value: T) -> Id { let mut data = self.data.write(); data.insert(self.id, value); self.id } } impl Registry { pub(crate) fn prepare(&self, id_in: Option>) -> FutureId<'_, T> { FutureId { id: match id_in { Some(id_in) => { self.identity.mark_as_used(id_in); id_in } None => self.identity.process(), }, data: &self.storage, } } #[track_caller] pub(crate) fn read<'a>(&'a self) -> RwLockReadGuard<'a, Storage> { self.storage.read() } pub(crate) fn remove(&self, id: Id) -> T { let value = self.storage.write().remove(id); // This needs to happen *after* removing it from the storage, to maintain the // invariant that `self.identity` only contains ids which are actually available // See https://github.com/gfx-rs/wgpu/issues/5372 self.identity.free(id); //Returning None is legal if it's an error ID value } pub(crate) fn generate_report(&self) -> RegistryReport { let storage = self.storage.read(); let mut report = RegistryReport { element_size: size_of::(), ..Default::default() }; report.num_allocated = self.identity.values.lock().count(); for element in storage.map.iter() { match *element { Element::Occupied(..) => report.num_kept_from_user += 1, Element::Vacant => report.num_released_from_user += 1, } } report } } impl Registry { pub(crate) fn get(&self, id: Id) -> T { self.read().get(id) } } #[cfg(test)] mod tests { use super::Registry; use crate::{id::Marker, resource::ResourceType, storage::StorageItem}; use alloc::sync::Arc; struct TestData; struct TestDataId; impl Marker for TestDataId {} impl ResourceType for TestData { const TYPE: &'static str = "TestData"; } impl StorageItem for TestData { type Marker = TestDataId; } #[test] fn simultaneous_registration() { let registry = Registry::new(); std::thread::scope(|s| { for _ in 0..5 { s.spawn(|| { for _ in 0..1000 { let value = Arc::new(TestData); let new_id = registry.prepare(None); let id = new_id.assign(value); registry.remove(id); } }); } }) } } ================================================ FILE: wgpu-core/src/resource.rs ================================================ use alloc::{borrow::Cow, borrow::ToOwned as _, boxed::Box, string::String, sync::Arc, vec::Vec}; use core::{ borrow::Borrow, fmt, mem::{self, size_of, ManuallyDrop}, num::NonZeroU64, ops::Range, ptr::NonNull, }; use smallvec::SmallVec; use thiserror::Error; use wgt::{ error::{ErrorType, WebGpuError}, TextureSelector, }; #[cfg(feature = "trace")] use crate::device::trace; use crate::{ binding_model::{BindGroup, BindingError}, device::{ queue, resource::DeferredDestroy, BufferMapPendingClosure, Device, DeviceError, DeviceMismatch, HostMap, MissingDownlevelFlags, MissingFeatures, }, hal_label, init_tracker::{BufferInitTracker, TextureInitTracker}, lock::{rank, Mutex, RwLock}, ray_tracing::{BlasCompactReadyPendingClosure, BlasPrepareCompactError}, resource_log, snatch::{SnatchGuard, Snatchable}, timestamp_normalization::TimestampNormalizationBindGroup, track::{SharedTrackerIndexAllocator, TrackerIndex}, weak_vec::WeakVec, Label, LabelHelpers, SubmissionIndex, }; /// Information about the wgpu-core resource. /// /// Each type representing a `wgpu-core` resource, like [`Device`], /// [`Buffer`], etc., contains a `ResourceInfo` which contains /// its latest submission index and label. /// /// A resource may need to be retained for any of several reasons: /// and any lifetime logic will be handled by `Arc` refcount /// /// - The user may hold a reference to it (via a `wgpu::Buffer`, say). /// /// - Other resources may depend on it (a texture view's backing /// texture, for example). /// /// - It may be used by commands sent to the GPU that have not yet /// finished execution. /// /// [`Device`]: crate::device::resource::Device /// [`Buffer`]: crate::resource::Buffer #[derive(Debug)] pub(crate) struct TrackingData { tracker_index: TrackerIndex, tracker_indices: Arc, } impl Drop for TrackingData { fn drop(&mut self) { self.tracker_indices.free(self.tracker_index); } } impl TrackingData { pub(crate) fn new(tracker_indices: Arc) -> Self { Self { tracker_index: tracker_indices.alloc(), tracker_indices, } } pub(crate) fn tracker_index(&self) -> TrackerIndex { self.tracker_index } } #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ResourceErrorIdent { r#type: Cow<'static, str>, label: String, } impl fmt::Display for ResourceErrorIdent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{} with '{}' label", self.r#type, self.label) } } pub trait ParentDevice: Labeled { fn device(&self) -> &Arc; fn is_equal(self: &Arc, other: &Arc) -> bool { Arc::ptr_eq(self, other) } fn same_device_as(&self, other: &O) -> Result<(), DeviceError> { if Arc::ptr_eq(self.device(), other.device()) { Ok(()) } else { Err(DeviceError::DeviceMismatch(Box::new(DeviceMismatch { res: self.error_ident(), res_device: self.device().error_ident(), target: Some(other.error_ident()), target_device: other.device().error_ident(), }))) } } fn same_device(&self, device: &Device) -> Result<(), DeviceError> { if core::ptr::eq(&**self.device(), device) { Ok(()) } else { Err(DeviceError::DeviceMismatch(Box::new(DeviceMismatch { res: self.error_ident(), res_device: self.device().error_ident(), target: None, target_device: device.error_ident(), }))) } } } #[macro_export] macro_rules! impl_parent_device { ($ty:ident) => { impl $crate::resource::ParentDevice for $ty { fn device(&self) -> &Arc { &self.device } } }; } /// Allow access to the hal resource as guarded by the `SnatchGuard`. pub trait RawResourceAccess: ParentDevice { type DynResource: hal::DynResource + ?Sized; /// Get access to the raw resource if it is not destroyed. /// /// Returns `None` if the resource has been destroyed. This method /// does not allocate in either case. fn raw<'a>(&'a self, guard: &'a SnatchGuard) -> Option<&'a Self::DynResource>; /// Get access to the raw resource if it is not destroyed. /// /// Returns a full error if the resource has been destroyed. This /// method allocates a label in the error case. fn try_raw<'a>( &'a self, guard: &'a SnatchGuard, ) -> Result<&'a Self::DynResource, DestroyedResourceError> { self.raw(guard) .ok_or_else(|| DestroyedResourceError(self.error_ident())) } } pub trait ResourceType { const TYPE: &'static str; } #[macro_export] macro_rules! impl_resource_type { ($ty:ident) => { impl $crate::resource::ResourceType for $ty { const TYPE: &'static str = stringify!($ty); } }; } pub trait Labeled: ResourceType { /// Returns a string identifying this resource for logging and errors. /// /// It may be a user-provided string or it may be a placeholder from wgpu. /// /// It is non-empty unless the user-provided string was empty. fn label(&self) -> &str; fn error_ident(&self) -> ResourceErrorIdent { ResourceErrorIdent { r#type: Cow::Borrowed(Self::TYPE), label: self.label().to_owned(), } } } #[macro_export] macro_rules! impl_labeled { ($ty:ident) => { impl $crate::resource::Labeled for $ty { fn label(&self) -> &str { &self.label } } }; } pub(crate) trait Trackable { fn tracker_index(&self) -> TrackerIndex; } #[macro_export] macro_rules! impl_trackable { ($ty:ident) => { impl $crate::resource::Trackable for $ty { fn tracker_index(&self) -> $crate::track::TrackerIndex { self.tracking_data.tracker_index() } } }; } #[derive(Debug)] pub(crate) enum BufferMapState { /// Mapped at creation. Init { staging_buffer: StagingBuffer }, /// Waiting for GPU to be done before mapping Waiting(BufferPendingMapping), /// Mapped Active { mapping: hal::BufferMapping, range: hal::MemoryRange, host: HostMap, }, /// Not mapped Idle, } #[cfg(send_sync)] unsafe impl Send for BufferMapState {} #[cfg(send_sync)] unsafe impl Sync for BufferMapState {} #[cfg(send_sync)] pub type BufferMapCallback = Box; #[cfg(not(send_sync))] pub type BufferMapCallback = Box; pub struct BufferMapOperation { pub host: HostMap, pub callback: Option, } impl fmt::Debug for BufferMapOperation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("BufferMapOperation") .field("host", &self.host) .field("callback", &self.callback.as_ref().map(|_| "?")) .finish() } } #[derive(Clone, Debug, Error)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[non_exhaustive] pub enum BufferAccessError { #[error(transparent)] Device(#[from] DeviceError), #[error("Buffer map failed")] Failed, #[error(transparent)] DestroyedResource(#[from] DestroyedResourceError), #[error("Buffer is already mapped")] AlreadyMapped, #[error("Buffer map is pending")] MapAlreadyPending, #[error(transparent)] MissingBufferUsage(#[from] MissingBufferUsageError), #[error("Buffer is not mapped")] NotMapped, #[error( "Buffer map range must start aligned to `MAP_ALIGNMENT` and end to `COPY_BUFFER_ALIGNMENT`" )] UnalignedRange, #[error("Buffer offset invalid: offset {offset} must be multiple of 8")] UnalignedOffset { offset: wgt::BufferAddress }, #[error("Buffer range size invalid: range_size {range_size} must be multiple of 4")] UnalignedRangeSize { range_size: wgt::BufferAddress }, #[error("Buffer access out of bounds: index {index} would underrun the buffer (limit: {min})")] OutOfBoundsStartOffsetUnderrun { index: wgt::BufferAddress, min: wgt::BufferAddress, }, #[error( "Buffer access out of bounds: start offset {index} would overrun the buffer (limit: {max})" )] OutOfBoundsStartOffsetOverrun { index: wgt::BufferAddress, max: wgt::BufferAddress, }, #[error( "Buffer access out of bounds: start offset {index} + size {size} would overrun the buffer (limit: {max})" )] OutOfBoundsEndOffsetOverrun { index: wgt::BufferAddress, size: wgt::BufferAddress, max: wgt::BufferAddress, }, #[error("Buffer map aborted")] MapAborted, #[error(transparent)] InvalidResource(#[from] InvalidResourceError), #[error("Map start offset ({offset}) is out-of-bounds for buffer of size {buffer_size}")] MapStartOffsetOverrun { offset: wgt::BufferAddress, buffer_size: wgt::BufferAddress, }, #[error( "Map end offset (start at {} + size of {}) is out-of-bounds for buffer of size {}", offset, size, buffer_size )] MapEndOffsetOverrun { offset: wgt::BufferAddress, size: wgt::BufferAddress, buffer_size: wgt::BufferAddress, }, } impl WebGpuError for BufferAccessError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::InvalidResource(e) => e.webgpu_error_type(), Self::DestroyedResource(e) => e.webgpu_error_type(), Self::Failed | Self::AlreadyMapped | Self::MapAlreadyPending | Self::MissingBufferUsage(_) | Self::NotMapped | Self::UnalignedRange | Self::UnalignedOffset { .. } | Self::UnalignedRangeSize { .. } | Self::OutOfBoundsStartOffsetUnderrun { .. } | Self::OutOfBoundsStartOffsetOverrun { .. } | Self::OutOfBoundsEndOffsetOverrun { .. } | Self::MapAborted | Self::MapStartOffsetOverrun { .. } | Self::MapEndOffsetOverrun { .. } => ErrorType::Validation, } } } #[derive(Clone, Debug, Error)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[error("Usage flags {actual:?} of {res} do not contain required usage flags {expected:?}")] pub struct MissingBufferUsageError { pub(crate) res: ResourceErrorIdent, pub(crate) actual: wgt::BufferUsages, pub(crate) expected: wgt::BufferUsages, } impl WebGpuError for MissingBufferUsageError { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } } #[derive(Clone, Debug, Error)] #[error("Usage flags {actual:?} of {res} do not contain required usage flags {expected:?}")] pub struct MissingTextureUsageError { pub(crate) res: ResourceErrorIdent, pub(crate) actual: wgt::TextureUsages, pub(crate) expected: wgt::TextureUsages, } impl WebGpuError for MissingTextureUsageError { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } } #[derive(Clone, Debug, Error)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[error("{0} has been destroyed")] pub struct DestroyedResourceError(pub ResourceErrorIdent); impl WebGpuError for DestroyedResourceError { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } } #[derive(Clone, Debug, Error)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[error("{0} is invalid")] pub struct InvalidResourceError(pub ResourceErrorIdent); impl WebGpuError for InvalidResourceError { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } } pub enum Fallible { Valid(Arc), Invalid(Arc), } impl Fallible { pub fn get(self) -> Result, InvalidResourceError> { match self { Fallible::Valid(v) => Ok(v), Fallible::Invalid(label) => Err(InvalidResourceError(ResourceErrorIdent { r#type: Cow::Borrowed(T::TYPE), label: (*label).clone(), })), } } } impl Clone for Fallible { fn clone(&self) -> Self { match self { Self::Valid(v) => Self::Valid(v.clone()), Self::Invalid(l) => Self::Invalid(l.clone()), } } } impl ResourceType for Fallible { const TYPE: &'static str = T::TYPE; } impl crate::storage::StorageItem for Fallible { type Marker = T::Marker; } pub type BufferAccessResult = Result<(), BufferAccessError>; #[derive(Debug)] pub(crate) struct BufferPendingMapping { pub(crate) range: Range, pub(crate) op: BufferMapOperation, // hold the parent alive while the mapping is active pub(crate) _parent_buffer: Arc, } pub type BufferDescriptor<'a> = wgt::BufferDescriptor>; #[derive(Debug)] pub struct Buffer { pub(crate) raw: Snatchable>, pub(crate) device: Arc, pub(crate) usage: wgt::BufferUsages, pub(crate) size: wgt::BufferAddress, pub(crate) initialization_status: RwLock, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, pub(crate) map_state: Mutex, pub(crate) bind_groups: Mutex>, pub(crate) timestamp_normalization_bind_group: Snatchable, pub(crate) indirect_validation_bind_groups: Snatchable, } impl Drop for Buffer { fn drop(&mut self) { if let Some(raw) = self.timestamp_normalization_bind_group.take() { raw.dispose(self.device.raw()); } if let Some(raw) = self.indirect_validation_bind_groups.take() { raw.dispose(self.device.raw()); } if let Some(raw) = self.raw.take() { resource_log!("Destroy raw {}", self.error_ident()); unsafe { self.device.raw().destroy_buffer(raw); } } } } impl RawResourceAccess for Buffer { type DynResource = dyn hal::DynBuffer; fn raw<'a>(&'a self, guard: &'a SnatchGuard) -> Option<&'a Self::DynResource> { self.raw.get(guard).map(|b| b.as_ref()) } } impl Buffer { pub(crate) fn check_destroyed( &self, guard: &SnatchGuard, ) -> Result<(), DestroyedResourceError> { self.raw .get(guard) .map(|_| ()) .ok_or_else(|| DestroyedResourceError(self.error_ident())) } /// Checks that the given buffer usage contains the required buffer usage, /// returns an error otherwise. pub(crate) fn check_usage( &self, expected: wgt::BufferUsages, ) -> Result<(), MissingBufferUsageError> { if self.usage.contains(expected) { Ok(()) } else { Err(MissingBufferUsageError { res: self.error_ident(), actual: self.usage, expected, }) } } /// Resolve the size of a binding for buffer with `offset` and `size`. /// /// If `size` is `None`, then the remainder of the buffer starting from /// `offset` is used. /// /// If the binding would overflow the buffer, then an error is returned. /// /// Zero-size bindings are permitted here for historical reasons. Although /// zero-size bindings are permitted by WebGPU, they are not permitted by /// some backends. See [`Buffer::binding`] and /// [#3170](https://github.com/gfx-rs/wgpu/issues/3170). pub fn resolve_binding_size( &self, offset: wgt::BufferAddress, binding_size: Option, ) -> Result { let buffer_size = self.size; match binding_size { Some(binding_size) => match offset.checked_add(binding_size.get()) { Some(end) if end <= buffer_size => Ok(binding_size.get()), _ => Err(BindingError::BindingRangeTooLarge { buffer: self.error_ident(), offset, binding_size: binding_size.get(), buffer_size, }), }, None => { buffer_size .checked_sub(offset) .ok_or_else(|| BindingError::BindingOffsetTooLarge { buffer: self.error_ident(), offset, buffer_size, }) } } } /// Create a new [`hal::BufferBinding`] for the buffer with `offset` and /// `binding_size`. /// /// If `binding_size` is `None`, then the remainder of the buffer starting /// from `offset` is used. /// /// If the binding would overflow the buffer, then an error is returned. /// /// A zero-size binding at the end of the buffer is permitted here for historical reasons. Although /// zero-size bindings are permitted by WebGPU, they are not permitted by /// some backends. The zero-size binding need to be quashed or remapped to a /// non-zero size, either universally in wgpu-core, or in specific backends /// that do not support them. See /// [#3170](https://github.com/gfx-rs/wgpu/issues/3170). /// /// Although it seems like it would be simpler and safer to use the resolved /// size in the returned [`hal::BufferBinding`], doing this (and removing /// redundant logic in backends to resolve the implicit size) was observed /// to cause problems in certain CTS tests, so an implicit size /// specification is preserved in the output. pub fn binding<'a>( &'a self, offset: wgt::BufferAddress, binding_size: Option, snatch_guard: &'a SnatchGuard, ) -> Result<(hal::BufferBinding<'a, dyn hal::DynBuffer>, u64), BindingError> { let buf_raw = self.try_raw(snatch_guard)?; let resolved_size = self.resolve_binding_size(offset, binding_size)?; // SAFETY: The offset and size passed to hal::BufferBinding::new_unchecked must // define a binding contained within the buffer. Ok(( hal::BufferBinding::new_unchecked(buf_raw, offset, binding_size), resolved_size, )) } /// Returns the mapping callback in case of error so that the callback can be fired outside /// of the locks that are held in this function. pub fn map_async( self: &Arc, offset: wgt::BufferAddress, size: Option, op: BufferMapOperation, ) -> Result { let range_size = if let Some(size) = size { size } else { self.size.saturating_sub(offset) }; if !offset.is_multiple_of(wgt::MAP_ALIGNMENT) { return Err((op, BufferAccessError::UnalignedOffset { offset })); } if !range_size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) { return Err((op, BufferAccessError::UnalignedRangeSize { range_size })); } if offset > self.size { return Err(( op, BufferAccessError::MapStartOffsetOverrun { offset, buffer_size: self.size, }, )); } // NOTE: Should never underflow because of our earlier check. if range_size > self.size - offset { return Err(( op, BufferAccessError::MapEndOffsetOverrun { offset, size: range_size, buffer_size: self.size, }, )); } let end_offset = offset + range_size; if !offset.is_multiple_of(wgt::MAP_ALIGNMENT) || !end_offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) { return Err((op, BufferAccessError::UnalignedRange)); } let (pub_usage, internal_use) = match op.host { HostMap::Read => (wgt::BufferUsages::MAP_READ, wgt::BufferUses::MAP_READ), HostMap::Write => (wgt::BufferUsages::MAP_WRITE, wgt::BufferUses::MAP_WRITE), }; if let Err(e) = self.check_usage(pub_usage) { return Err((op, e.into())); } let device = &self.device; if let Err(e) = device.check_is_valid() { return Err((op, e.into())); } { let snatch_guard = device.snatchable_lock.read(); if let Err(e) = self.check_destroyed(&snatch_guard) { return Err((op, e.into())); } } { let map_state = &mut *self.map_state.lock(); *map_state = match *map_state { BufferMapState::Init { .. } | BufferMapState::Active { .. } => { return Err((op, BufferAccessError::AlreadyMapped)); } BufferMapState::Waiting(_) => { return Err((op, BufferAccessError::MapAlreadyPending)); } BufferMapState::Idle => BufferMapState::Waiting(BufferPendingMapping { range: offset..end_offset, op, _parent_buffer: self.clone(), }), }; } // TODO: we are ignoring the transition here, I think we need to add a barrier // at the end of the submission device .trackers .lock() .buffers .set_single(self, internal_use); let submit_index = if let Some(queue) = device.get_queue() { queue.lock_life().map(self).unwrap_or(0) // '0' means no wait is necessary } else { // We can safely unwrap below since we just set the `map_state` to `BufferMapState::Waiting`. let (mut operation, status) = self.map(&device.snatchable_lock.read()).unwrap(); if let Some(callback) = operation.callback.take() { callback(status); } 0 }; Ok(submit_index) } pub fn get_mapped_range( self: &Arc, offset: wgt::BufferAddress, size: Option, ) -> Result<(NonNull, u64), BufferAccessError> { { let snatch_guard = self.device.snatchable_lock.read(); self.check_destroyed(&snatch_guard)?; } let range_size = if let Some(size) = size { size } else { self.size.saturating_sub(offset) }; if !offset.is_multiple_of(wgt::MAP_ALIGNMENT) { return Err(BufferAccessError::UnalignedOffset { offset }); } if !range_size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) { return Err(BufferAccessError::UnalignedRangeSize { range_size }); } let map_state = &*self.map_state.lock(); match *map_state { BufferMapState::Init { ref staging_buffer } => { if offset > self.size { return Err(BufferAccessError::MapStartOffsetOverrun { offset, buffer_size: self.size, }); } // NOTE: Should never underflow because of our earlier check. if range_size > self.size - offset { return Err(BufferAccessError::MapEndOffsetOverrun { offset, size: range_size, buffer_size: self.size, }); } let ptr = unsafe { staging_buffer.ptr() }; let ptr = unsafe { NonNull::new_unchecked(ptr.as_ptr().offset(offset as isize)) }; Ok((ptr, range_size)) } BufferMapState::Active { ref mapping, ref range, .. } => { if offset > range.end { return Err(BufferAccessError::OutOfBoundsStartOffsetOverrun { index: offset, max: range.end, }); } if offset < range.start { return Err(BufferAccessError::OutOfBoundsStartOffsetUnderrun { index: offset, min: range.start, }); } if range_size > range.end - offset { return Err(BufferAccessError::OutOfBoundsEndOffsetOverrun { index: offset, size: range_size, max: range.end, }); } // ptr points to the beginning of the range we mapped in map_async // rather than the beginning of the buffer. let relative_offset = (offset - range.start) as isize; unsafe { Ok(( NonNull::new_unchecked(mapping.ptr.as_ptr().offset(relative_offset)), range_size, )) } } BufferMapState::Idle | BufferMapState::Waiting(_) => Err(BufferAccessError::NotMapped), } } /// This function returns [`None`] only if [`Self::map_state`] is not [`BufferMapState::Waiting`]. #[must_use] pub(crate) fn map(&self, snatch_guard: &SnatchGuard) -> Option { // This _cannot_ be inlined into the match. If it is, the lock will be held // open through the whole match, resulting in a deadlock when we try to re-lock // the buffer back to active. let mapping = mem::replace(&mut *self.map_state.lock(), BufferMapState::Idle); let pending_mapping = match mapping { BufferMapState::Waiting(pending_mapping) => pending_mapping, // Mapping cancelled BufferMapState::Idle => return None, // Mapping queued at least twice by map -> unmap -> map // and was already successfully mapped below BufferMapState::Active { .. } => { *self.map_state.lock() = mapping; return None; } _ => panic!("No pending mapping."), }; let status = if pending_mapping.range.start != pending_mapping.range.end { let host = pending_mapping.op.host; let size = pending_mapping.range.end - pending_mapping.range.start; match crate::device::map_buffer( self, pending_mapping.range.start, size, host, snatch_guard, ) { Ok(mapping) => { *self.map_state.lock() = BufferMapState::Active { mapping, range: pending_mapping.range.clone(), host, }; Ok(()) } Err(e) => Err(e), } } else { *self.map_state.lock() = BufferMapState::Active { mapping: hal::BufferMapping { ptr: NonNull::dangling(), is_coherent: true, }, range: pending_mapping.range, host: pending_mapping.op.host, }; Ok(()) }; Some((pending_mapping.op, status)) } // Note: This must not be called while holding a lock. pub fn unmap(self: &Arc) -> Result<(), BufferAccessError> { if let Some((mut operation, status)) = self.unmap_inner()? { if let Some(callback) = operation.callback.take() { callback(status); } } Ok(()) } fn unmap_inner(self: &Arc) -> Result, BufferAccessError> { let device = &self.device; let snatch_guard = device.snatchable_lock.read(); let raw_buf = self.try_raw(&snatch_guard)?; match mem::replace(&mut *self.map_state.lock(), BufferMapState::Idle) { BufferMapState::Init { staging_buffer } => { #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { use crate::device::trace::{DataKind, IntoTrace}; let data = trace.make_binary(DataKind::Bin, staging_buffer.get_data()); trace.add(trace::Action::WriteBuffer { id: self.to_trace(), data, // NOTE: `self.size` here corresponds to `data`'s actual length. offset: 0, size: self.size, queued: true, }); } let staging_buffer = staging_buffer.flush(); if let Some(queue) = device.get_queue() { let region = wgt::BufferSize::new(self.size).map(|size| hal::BufferCopy { src_offset: 0, dst_offset: 0, size, }); let transition_src = hal::BufferBarrier { buffer: staging_buffer.raw(), usage: hal::StateTransition { from: wgt::BufferUses::MAP_WRITE, to: wgt::BufferUses::COPY_SRC, }, }; let transition_dst = hal::BufferBarrier:: { buffer: raw_buf, usage: hal::StateTransition { from: wgt::BufferUses::empty(), to: wgt::BufferUses::COPY_DST, }, }; let mut pending_writes = queue.pending_writes.lock(); let encoder = pending_writes.activate(); unsafe { encoder.transition_buffers(&[transition_src, transition_dst]); if self.size > 0 { encoder.copy_buffer_to_buffer( staging_buffer.raw(), raw_buf, region.as_slice(), ); } } pending_writes.consume(staging_buffer); pending_writes.insert_buffer(self); } } BufferMapState::Idle => { return Err(BufferAccessError::NotMapped); } BufferMapState::Waiting(pending) => { return Ok(Some((pending.op, Err(BufferAccessError::MapAborted)))); } BufferMapState::Active { mapping, range, host, } => { if host == HostMap::Write { #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { use crate::device::trace::{DataKind, IntoTrace}; let size = range.end - range.start; let data = trace.make_binary(DataKind::Bin, unsafe { core::slice::from_raw_parts(mapping.ptr.as_ptr(), size as usize) }); trace.add(trace::Action::WriteBuffer { id: self.to_trace(), data, offset: range.start, size, queued: false, }); } if !mapping.is_coherent { unsafe { device.raw().flush_mapped_ranges(raw_buf, &[range]) }; } } unsafe { device.raw().unmap_buffer(raw_buf) }; } } Ok(None) } pub fn destroy(self: &Arc) { let device = &self.device; let temp = { let mut snatch_guard = device.snatchable_lock.write(); let raw = match self.raw.snatch(&mut snatch_guard) { Some(raw) => raw, None => { // Per spec, it is valid to call `destroy` multiple times. return; } }; let timestamp_normalization_bind_group = self .timestamp_normalization_bind_group .snatch(&mut snatch_guard); let indirect_validation_bind_groups = self .indirect_validation_bind_groups .snatch(&mut snatch_guard); drop(snatch_guard); let bind_groups = { let mut guard = self.bind_groups.lock(); mem::take(&mut *guard) }; queue::TempResource::DestroyedBuffer(DestroyedBuffer { raw: ManuallyDrop::new(raw), device: Arc::clone(&self.device), label: self.label().to_owned(), bind_groups, timestamp_normalization_bind_group, indirect_validation_bind_groups, }) }; if let Some(queue) = device.get_queue() { let mut pending_writes = queue.pending_writes.lock(); if pending_writes.contains_buffer(self) { pending_writes.consume_temp(temp); } else { let mut life_lock = queue.lock_life(); let last_submit_index = life_lock.get_buffer_latest_submission_index(self); if let Some(last_submit_index) = last_submit_index { life_lock.schedule_resource_destruction(temp, last_submit_index); } } } } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateBufferError { #[error(transparent)] Device(#[from] DeviceError), #[error("Failed to map buffer while creating: {0}")] AccessError(#[from] BufferAccessError), #[error("Buffers that are mapped at creation have to be aligned to `COPY_BUFFER_ALIGNMENT`")] UnalignedSize, #[error("Invalid usage flags {0:?}")] InvalidUsage(wgt::BufferUsages), #[error("`MAP` usage can only be combined with the opposite `COPY`, requested {0:?}")] UsageMismatch(wgt::BufferUsages), #[error("Buffer size {requested} is greater than the maximum buffer size ({maximum})")] MaxBufferSize { requested: u64, maximum: u64 }, #[error(transparent)] MissingDownlevelFlags(#[from] MissingDownlevelFlags), #[error(transparent)] MissingFeatures(#[from] MissingFeatures), #[error("Failed to create bind group for indirect buffer validation: {0}")] IndirectValidationBindGroup(DeviceError), } crate::impl_resource_type!(Buffer); crate::impl_labeled!(Buffer); crate::impl_parent_device!(Buffer); crate::impl_storage_item!(Buffer); crate::impl_trackable!(Buffer); impl WebGpuError for CreateBufferError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::AccessError(e) => e.webgpu_error_type(), Self::MissingDownlevelFlags(e) => e.webgpu_error_type(), Self::IndirectValidationBindGroup(e) => e.webgpu_error_type(), Self::MissingFeatures(e) => e.webgpu_error_type(), Self::UnalignedSize | Self::InvalidUsage(_) | Self::UsageMismatch(_) | Self::MaxBufferSize { .. } => ErrorType::Validation, } } } /// A buffer that has been marked as destroyed and is staged for actual deletion soon. #[derive(Debug)] pub struct DestroyedBuffer { raw: ManuallyDrop>, device: Arc, label: String, bind_groups: WeakVec, timestamp_normalization_bind_group: Option, indirect_validation_bind_groups: Option, } impl DestroyedBuffer { pub fn label(&self) -> &dyn fmt::Debug { &self.label } } impl Drop for DestroyedBuffer { fn drop(&mut self) { let mut deferred = self.device.deferred_destroy.lock(); deferred.push(DeferredDestroy::BindGroups(mem::take( &mut self.bind_groups, ))); drop(deferred); if let Some(raw) = self.timestamp_normalization_bind_group.take() { raw.dispose(self.device.raw()); } if let Some(raw) = self.indirect_validation_bind_groups.take() { raw.dispose(self.device.raw()); } resource_log!("Destroy raw Buffer (destroyed) {:?}", self.label()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; unsafe { hal::DynDevice::destroy_buffer(self.device.raw(), raw); } } } #[cfg(send_sync)] unsafe impl Send for StagingBuffer {} #[cfg(send_sync)] unsafe impl Sync for StagingBuffer {} /// A temporary buffer, consumed by the command that uses it. /// /// A [`StagingBuffer`] is designed for one-shot uploads of data to the GPU. It /// is always created mapped, and the command that uses it destroys the buffer /// when it is done. /// /// [`StagingBuffer`]s can be created with [`queue_create_staging_buffer`] and /// used with [`queue_write_staging_buffer`]. They are also used internally by /// operations like [`queue_write_texture`] that need to upload data to the GPU, /// but that don't belong to any particular wgpu command buffer. /// /// Used `StagingBuffer`s are accumulated in [`Device::pending_writes`], to be /// freed once their associated operation's queue submission has finished /// execution. /// /// [`queue_create_staging_buffer`]: crate::global::Global::queue_create_staging_buffer /// [`queue_write_staging_buffer`]: crate::global::Global::queue_write_staging_buffer /// [`queue_write_texture`]: crate::global::Global::queue_write_texture /// [`Device::pending_writes`]: crate::device::Device #[derive(Debug)] pub struct StagingBuffer { raw: Box, device: Arc, pub(crate) size: wgt::BufferSize, is_coherent: bool, ptr: NonNull, } impl StagingBuffer { pub(crate) fn new(device: &Arc, size: wgt::BufferSize) -> Result { profiling::scope!("StagingBuffer::new"); let stage_desc = hal::BufferDescriptor { label: hal_label(Some("(wgpu internal) Staging"), device.instance_flags), size: size.get(), usage: wgt::BufferUses::MAP_WRITE | wgt::BufferUses::COPY_SRC, memory_flags: hal::MemoryFlags::TRANSIENT, }; let raw = unsafe { device.raw().create_buffer(&stage_desc) } .map_err(|e| device.handle_hal_error(e))?; let mapping = unsafe { device.raw().map_buffer(raw.as_ref(), 0..size.get()) } .map_err(|e| device.handle_hal_error(e))?; let staging_buffer = StagingBuffer { raw, device: device.clone(), size, is_coherent: mapping.is_coherent, ptr: mapping.ptr, }; Ok(staging_buffer) } /// SAFETY: You must not call any functions of `self` /// until you stopped using the returned pointer. pub(crate) unsafe fn ptr(&self) -> NonNull { self.ptr } #[cfg(feature = "trace")] pub(crate) fn get_data(&self) -> &[u8] { unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), self.size.get() as usize) } } pub(crate) fn write_zeros(&mut self) { unsafe { core::ptr::write_bytes(self.ptr.as_ptr(), 0, self.size.get() as usize) }; } pub(crate) fn write(&mut self, data: &[u8]) { assert!(data.len() >= self.size.get() as usize); // SAFETY: With the assert above, all of `copy_nonoverlapping`'s // requirements are satisfied. unsafe { core::ptr::copy_nonoverlapping( data.as_ptr(), self.ptr.as_ptr(), self.size.get() as usize, ); } } /// SAFETY: The offsets and size must be in-bounds. pub(crate) unsafe fn write_with_offset( &mut self, data: &[u8], src_offset: isize, dst_offset: isize, size: usize, ) { unsafe { debug_assert!( (src_offset + size as isize) as usize <= data.len(), "src_offset + size must be in-bounds: src_offset = {}, size = {}, data.len() = {}", src_offset, size, data.len() ); core::ptr::copy_nonoverlapping( data.as_ptr().offset(src_offset), self.ptr.as_ptr().offset(dst_offset), size, ); } } pub(crate) fn flush(self) -> FlushedStagingBuffer { let device = self.device.raw(); if !self.is_coherent { #[allow(clippy::single_range_in_vec_init)] unsafe { device.flush_mapped_ranges(self.raw.as_ref(), &[0..self.size.get()]) }; } unsafe { device.unmap_buffer(self.raw.as_ref()) }; let StagingBuffer { raw, device, size, .. } = self; FlushedStagingBuffer { raw: ManuallyDrop::new(raw), device, size, } } } crate::impl_resource_type!(StagingBuffer); crate::impl_storage_item!(StagingBuffer); #[derive(Debug)] pub struct FlushedStagingBuffer { raw: ManuallyDrop>, device: Arc, pub(crate) size: wgt::BufferSize, } impl FlushedStagingBuffer { pub(crate) fn raw(&self) -> &dyn hal::DynBuffer { self.raw.as_ref() } } impl Drop for FlushedStagingBuffer { fn drop(&mut self) { resource_log!("Destroy raw StagingBuffer"); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; unsafe { self.device.raw().destroy_buffer(raw) }; } } pub type TextureDescriptor<'a> = wgt::TextureDescriptor, Vec>; #[derive(Debug)] pub(crate) enum TextureInner { Native { raw: Box, }, Surface { raw: Box, }, } impl TextureInner { pub(crate) fn raw(&self) -> &dyn hal::DynTexture { match self { Self::Native { raw } => raw.as_ref(), Self::Surface { raw, .. } => raw.as_ref().borrow(), } } } #[derive(Debug)] pub enum TextureClearMode { BufferCopy, // View for clear via RenderPass for every subsurface (mip/layer/slice) RenderPass { clear_views: SmallVec<[ManuallyDrop>; 1]>, is_color: bool, }, Surface { clear_view: ManuallyDrop>, }, // Texture can't be cleared, attempting to do so will cause panic. // (either because it is impossible for the type of texture or it is being destroyed) None, } #[derive(Debug)] pub struct Texture { pub(crate) inner: Snatchable, pub(crate) device: Arc, pub(crate) desc: wgt::TextureDescriptor<(), Vec>, pub(crate) _hal_usage: wgt::TextureUses, pub(crate) format_features: wgt::TextureFormatFeatures, pub(crate) initialization_status: RwLock, pub(crate) full_range: TextureSelector, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, pub(crate) clear_mode: RwLock, pub(crate) views: Mutex>, pub(crate) bind_groups: Mutex>, } impl Texture { pub(crate) fn new( device: &Arc, inner: TextureInner, hal_usage: wgt::TextureUses, desc: &TextureDescriptor, format_features: wgt::TextureFormatFeatures, clear_mode: TextureClearMode, init: bool, ) -> Self { Texture { inner: Snatchable::new(inner), device: device.clone(), desc: desc.map_label(|_| ()), _hal_usage: hal_usage, format_features, initialization_status: RwLock::new( rank::TEXTURE_INITIALIZATION_STATUS, if init { TextureInitTracker::new(desc.mip_level_count, desc.array_layer_count()) } else { TextureInitTracker::new(desc.mip_level_count, 0) }, ), full_range: TextureSelector { mips: 0..desc.mip_level_count, layers: 0..desc.array_layer_count(), }, label: desc.label.to_string(), tracking_data: TrackingData::new(device.tracker_indices.textures.clone()), clear_mode: RwLock::new(rank::TEXTURE_CLEAR_MODE, clear_mode), views: Mutex::new(rank::TEXTURE_VIEWS, WeakVec::new()), bind_groups: Mutex::new(rank::TEXTURE_BIND_GROUPS, WeakVec::new()), } } /// Checks that the given texture usage contains the required texture usage, /// returns an error otherwise. pub(crate) fn check_usage( &self, expected: wgt::TextureUsages, ) -> Result<(), MissingTextureUsageError> { if self.desc.usage.contains(expected) { Ok(()) } else { Err(MissingTextureUsageError { res: self.error_ident(), actual: self.desc.usage, expected, }) } } } impl Drop for Texture { fn drop(&mut self) { match *self.clear_mode.write() { TextureClearMode::Surface { ref mut clear_view, .. } => { // SAFETY: We are in the Drop impl and we don't use clear_view anymore after this point. let raw = unsafe { ManuallyDrop::take(clear_view) }; unsafe { self.device.raw().destroy_texture_view(raw); } } TextureClearMode::RenderPass { ref mut clear_views, .. } => { clear_views.iter_mut().for_each(|clear_view| { // SAFETY: We are in the Drop impl and we don't use clear_view anymore after this point. let raw = unsafe { ManuallyDrop::take(clear_view) }; unsafe { self.device.raw().destroy_texture_view(raw); } }); } _ => {} }; if let Some(TextureInner::Native { raw }) = self.inner.take() { resource_log!("Destroy raw {}", self.error_ident()); unsafe { self.device.raw().destroy_texture(raw); } } } } impl RawResourceAccess for Texture { type DynResource = dyn hal::DynTexture; fn raw<'a>(&'a self, guard: &'a SnatchGuard) -> Option<&'a Self::DynResource> { self.inner.get(guard).map(|t| t.raw()) } } impl Texture { pub(crate) fn try_inner<'a>( &'a self, guard: &'a SnatchGuard, ) -> Result<&'a TextureInner, DestroyedResourceError> { self.inner .get(guard) .ok_or_else(|| DestroyedResourceError(self.error_ident())) } pub(crate) fn check_destroyed( &self, guard: &SnatchGuard, ) -> Result<(), DestroyedResourceError> { self.inner .get(guard) .map(|_| ()) .ok_or_else(|| DestroyedResourceError(self.error_ident())) } pub(crate) fn get_clear_view<'a>( clear_mode: &'a TextureClearMode, desc: &'a wgt::TextureDescriptor<(), Vec>, mip_level: u32, depth_or_layer: u32, ) -> &'a dyn hal::DynTextureView { match *clear_mode { TextureClearMode::BufferCopy => { panic!("Given texture is cleared with buffer copies, not render passes") } TextureClearMode::None => { panic!("Given texture can't be cleared") } TextureClearMode::Surface { ref clear_view, .. } => clear_view.as_ref(), TextureClearMode::RenderPass { ref clear_views, .. } => { let index = if desc.dimension == wgt::TextureDimension::D3 { (0..mip_level).fold(0, |acc, mip| { acc + (desc.size.depth_or_array_layers >> mip).max(1) }) } else { mip_level * desc.size.depth_or_array_layers } + depth_or_layer; clear_views[index as usize].as_ref() } } } pub fn destroy(self: &Arc) { let device = &self.device; let temp = { let raw = match self.inner.snatch(&mut device.snatchable_lock.write()) { Some(TextureInner::Native { raw }) => raw, Some(TextureInner::Surface { .. }) => { return; } None => { // Per spec, it is valid to call `destroy` multiple times. return; } }; let views = { let mut guard = self.views.lock(); mem::take(&mut *guard) }; let bind_groups = { let mut guard = self.bind_groups.lock(); mem::take(&mut *guard) }; queue::TempResource::DestroyedTexture(DestroyedTexture { raw: ManuallyDrop::new(raw), views, clear_mode: mem::replace(&mut *self.clear_mode.write(), TextureClearMode::None), bind_groups, device: Arc::clone(&self.device), label: self.label().to_owned(), }) }; if let Some(queue) = device.get_queue() { let mut pending_writes = queue.pending_writes.lock(); if pending_writes.contains_texture(self) { pending_writes.consume_temp(temp); } else { let mut life_lock = queue.lock_life(); let last_submit_index = life_lock.get_texture_latest_submission_index(self); if let Some(last_submit_index) = last_submit_index { life_lock.schedule_resource_destruction(temp, last_submit_index); } } } } } /// A texture that has been marked as destroyed and is staged for actual deletion soon. #[derive(Debug)] pub struct DestroyedTexture { raw: ManuallyDrop>, views: WeakVec, clear_mode: TextureClearMode, bind_groups: WeakVec, device: Arc, label: String, } impl DestroyedTexture { pub fn label(&self) -> &dyn fmt::Debug { &self.label } } impl Drop for DestroyedTexture { fn drop(&mut self) { let device = &self.device; let mut deferred = device.deferred_destroy.lock(); deferred.push(DeferredDestroy::TextureViews(mem::take(&mut self.views))); deferred.push(DeferredDestroy::BindGroups(mem::take( &mut self.bind_groups, ))); drop(deferred); match mem::replace(&mut self.clear_mode, TextureClearMode::None) { TextureClearMode::RenderPass { clear_views, .. } => { for clear_view in clear_views { let raw = ManuallyDrop::into_inner(clear_view); unsafe { self.device.raw().destroy_texture_view(raw) }; } } TextureClearMode::Surface { clear_view } => { let raw = ManuallyDrop::into_inner(clear_view); unsafe { self.device.raw().destroy_texture_view(raw) }; } _ => (), } resource_log!("Destroy raw Texture (destroyed) {:?}", self.label()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; unsafe { self.device.raw().destroy_texture(raw); } } } #[derive(Clone, Copy, Debug)] pub enum TextureErrorDimension { X, Y, Z, } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum TextureDimensionError { #[error("Dimension {0:?} is zero")] Zero(TextureErrorDimension), #[error("Dimension {dim:?} value {given} exceeds the limit of {limit}")] LimitExceeded { dim: TextureErrorDimension, given: u32, limit: u32, }, #[error("Sample count {0} is invalid")] InvalidSampleCount(u32), #[error("Width {width} is not a multiple of {format:?}'s block width ({block_width})")] NotMultipleOfBlockWidth { width: u32, block_width: u32, format: wgt::TextureFormat, }, #[error("Height {height} is not a multiple of {format:?}'s block height ({block_height})")] NotMultipleOfBlockHeight { height: u32, block_height: u32, format: wgt::TextureFormat, }, #[error( "Width {width} is not a multiple of {format:?}'s width multiple requirement ({multiple})" )] WidthNotMultipleOf { width: u32, multiple: u32, format: wgt::TextureFormat, }, #[error("Height {height} is not a multiple of {format:?}'s height multiple requirement ({multiple})")] HeightNotMultipleOf { height: u32, multiple: u32, format: wgt::TextureFormat, }, #[error("Multisampled texture depth or array layers must be 1, got {0}")] MultisampledDepthOrArrayLayer(u32), } impl WebGpuError for TextureDimensionError { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateTextureError { #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] CreateTextureView(#[from] CreateTextureViewError), #[error("Invalid usage flags {0:?}")] InvalidUsage(wgt::TextureUsages), #[error("Texture usage {0:?} is not compatible with texture usage {1:?}")] IncompatibleUsage(wgt::TextureUsages, wgt::TextureUsages), #[error(transparent)] InvalidDimension(#[from] TextureDimensionError), #[error("Depth texture ({1:?}) can't be created as {0:?}")] InvalidDepthDimension(wgt::TextureDimension, wgt::TextureFormat), #[error("Compressed texture ({1:?}) can't be created as {0:?}")] InvalidCompressedDimension(wgt::TextureDimension, wgt::TextureFormat), #[error( "Texture descriptor mip level count {requested} is invalid, maximum allowed is {maximum}" )] InvalidMipLevelCount { requested: u32, maximum: u32 }, #[error( "Texture usages {0:?} are not allowed on a texture of type {1:?}{downlevel_suffix}", downlevel_suffix = if *.2 { " due to downlevel restrictions" } else { "" } )] InvalidFormatUsages(wgt::TextureUsages, wgt::TextureFormat, bool), #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")] InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat), #[error("Texture usages {0:?} are not allowed on a texture of dimensions {1:?}")] InvalidDimensionUsages(wgt::TextureUsages, wgt::TextureDimension), #[error("Texture usage STORAGE_BINDING is not allowed for multisampled textures")] InvalidMultisampledStorageBinding, #[error("Format {0:?} does not support multisampling")] InvalidMultisampledFormat(wgt::TextureFormat), #[error("Sample count {0} is not supported by format {1:?} on this device. The WebGPU spec guarantees {2:?} samples are supported by this format. With the TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES feature your device supports {3:?}.")] InvalidSampleCount(u32, wgt::TextureFormat, Vec, Vec), #[error("Multisampled textures must have RENDER_ATTACHMENT usage")] MultisampledNotRenderAttachment, #[error("Texture format {0:?} can't be used due to missing features")] MissingFeatures(wgt::TextureFormat, #[source] MissingFeatures), #[error(transparent)] MissingDownlevelFlags(#[from] MissingDownlevelFlags), } crate::impl_resource_type!(Texture); crate::impl_labeled!(Texture); crate::impl_parent_device!(Texture); crate::impl_storage_item!(Texture); crate::impl_trackable!(Texture); impl Borrow for Texture { fn borrow(&self) -> &TextureSelector { &self.full_range } } impl WebGpuError for CreateTextureError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::CreateTextureView(e) => e.webgpu_error_type(), Self::InvalidDimension(e) => e.webgpu_error_type(), Self::MissingFeatures(_, e) => e.webgpu_error_type(), Self::MissingDownlevelFlags(e) => e.webgpu_error_type(), Self::InvalidUsage(_) | Self::IncompatibleUsage(_, _) | Self::InvalidDepthDimension(_, _) | Self::InvalidCompressedDimension(_, _) | Self::InvalidMipLevelCount { .. } | Self::InvalidFormatUsages(_, _, _) | Self::InvalidViewFormat(_, _) | Self::InvalidDimensionUsages(_, _) | Self::InvalidMultisampledStorageBinding | Self::InvalidMultisampledFormat(_) | Self::InvalidSampleCount(..) | Self::MultisampledNotRenderAttachment => ErrorType::Validation, } } } /// Describes a [`TextureView`]. #[derive(Clone, Debug, Default, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct TextureViewDescriptor<'a> { /// Debug label of the texture view. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// Format of the texture view, or `None` for the same format as the texture /// itself. /// /// At this time, it must be the same the underlying format of the texture. pub format: Option, /// The dimension of the texture view. /// /// - For 1D textures, this must be `D1`. /// - For 2D textures it must be one of `D2`, `D2Array`, `Cube`, or `CubeArray`. /// - For 3D textures it must be `D3`. pub dimension: Option, /// The allowed usage(s) for the texture view. Must be a subset of the usage flags of the texture. /// If not provided, defaults to the full set of usage flags of the texture. pub usage: Option, /// Range within the texture that is accessible via this view. pub range: wgt::ImageSubresourceRange, } #[derive(Debug)] pub(crate) struct HalTextureViewDescriptor { pub texture_format: wgt::TextureFormat, pub format: wgt::TextureFormat, pub usage: wgt::TextureUsages, pub dimension: wgt::TextureViewDimension, pub range: wgt::ImageSubresourceRange, } impl HalTextureViewDescriptor { pub fn aspects(&self) -> hal::FormatAspects { hal::FormatAspects::new(self.texture_format, self.range.aspect) } } #[derive(Debug, Copy, Clone, Error)] pub enum TextureViewNotRenderableReason { #[error("The texture this view references doesn't include the RENDER_ATTACHMENT usage. Provided usages: {0:?}")] Usage(wgt::TextureUsages), #[error("The dimension of this texture view is not 2D. View dimension: {0:?}")] Dimension(wgt::TextureViewDimension), #[error("This texture view has more than one mipmap level. View mipmap levels: {0:?}")] MipLevelCount(u32), #[error("This texture view has more than one array layer. View array layers: {0:?}")] ArrayLayerCount(u32), #[error( "The aspects of this texture view are a subset of the aspects in the original texture. Aspects: {0:?}" )] Aspects(hal::FormatAspects), } #[derive(Debug)] pub struct TextureView { pub(crate) raw: Snatchable>, // if it's a surface texture - it's none pub(crate) parent: Arc, pub(crate) device: Arc, pub(crate) desc: HalTextureViewDescriptor, pub(crate) format_features: wgt::TextureFormatFeatures, /// This is `Err` only if the texture view is not renderable pub(crate) render_extent: Result, pub(crate) samples: u32, pub(crate) selector: TextureSelector, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, } impl Drop for TextureView { fn drop(&mut self) { if let Some(raw) = self.raw.take() { resource_log!("Destroy raw {}", self.error_ident()); unsafe { self.device.raw().destroy_texture_view(raw); } } } } impl RawResourceAccess for TextureView { type DynResource = dyn hal::DynTextureView; fn raw<'a>(&'a self, guard: &'a SnatchGuard) -> Option<&'a Self::DynResource> { self.raw.get(guard).map(|it| it.as_ref()) } fn try_raw<'a>( &'a self, guard: &'a SnatchGuard, ) -> Result<&'a Self::DynResource, DestroyedResourceError> { self.parent.check_destroyed(guard)?; self.raw(guard) .ok_or_else(|| DestroyedResourceError(self.error_ident())) } } impl TextureView { /// Checks that the given texture usage contains the required texture usage, /// returns an error otherwise. pub(crate) fn check_usage( &self, expected: wgt::TextureUsages, ) -> Result<(), MissingTextureUsageError> { if self.desc.usage.contains(expected) { Ok(()) } else { Err(MissingTextureUsageError { res: self.error_ident(), actual: self.desc.usage, expected, }) } } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateTextureViewError { #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] DestroyedResource(#[from] DestroyedResourceError), #[error("Invalid texture view dimension `{view:?}` with texture of dimension `{texture:?}`")] InvalidTextureViewDimension { view: wgt::TextureViewDimension, texture: wgt::TextureDimension, }, #[error("Texture view format `{0:?}` cannot be used as a render attachment. Make sure the format supports RENDER_ATTACHMENT usage and required device features are enabled.")] TextureViewFormatNotRenderable(wgt::TextureFormat), #[error("Texture view format `{0:?}` cannot be used as a storage binding. Make sure the format supports STORAGE usage and required device features are enabled.")] TextureViewFormatNotStorage(wgt::TextureFormat), #[error("Texture view usages (`{view:?}`) must be a subset of the texture's original usages (`{texture:?}`)")] InvalidTextureViewUsage { view: wgt::TextureUsages, texture: wgt::TextureUsages, }, #[error("Texture view dimension `{0:?}` cannot be used with a multisampled texture")] InvalidMultisampledTextureViewDimension(wgt::TextureViewDimension), #[error( "TextureView has an arrayLayerCount of {depth}. Views of type `Cube` must have arrayLayerCount of 6." )] InvalidCubemapTextureDepth { depth: u32 }, #[error("TextureView has an arrayLayerCount of {depth}. Views of type `CubeArray` must have an arrayLayerCount that is a multiple of 6.")] InvalidCubemapArrayTextureDepth { depth: u32 }, #[error("Source texture width and height must be equal for a texture view of dimension `Cube`/`CubeArray`")] InvalidCubeTextureViewSize, #[error("Mip level count is 0")] ZeroMipLevelCount, #[error("Array layer count is 0")] ZeroArrayLayerCount, #[error( "TextureView spans mip levels [{base_mip_level}, {end_mip_level}) \ (mipLevelCount {mip_level_count}) but the texture view only has {total} total mip levels", end_mip_level = base_mip_level + mip_level_count )] TooManyMipLevels { base_mip_level: u32, mip_level_count: u32, total: u32, }, #[error( "TextureView spans array layers [{base_array_layer}, {end_array_layer}) \ (arrayLayerCount {array_layer_count}) but the texture view only has {total} total layers", end_array_layer = base_array_layer + array_layer_count )] TooManyArrayLayers { base_array_layer: u32, array_layer_count: u32, total: u32, }, #[error("Requested array layer count {requested} is not valid for the target view dimension {dim:?}")] InvalidArrayLayerCount { requested: u32, dim: wgt::TextureViewDimension, }, #[error( "Aspect {requested_aspect:?} is not a valid aspect of the source texture format {texture_format:?}" )] InvalidAspect { texture_format: wgt::TextureFormat, requested_aspect: wgt::TextureAspect, }, #[error( "Trying to create a view of format {view:?} of a texture with format {texture:?}, \ but this view format is not present in the texture's viewFormat array" )] FormatReinterpretation { texture: wgt::TextureFormat, view: wgt::TextureFormat, }, #[error(transparent)] InvalidResource(#[from] InvalidResourceError), #[error(transparent)] MissingFeatures(#[from] MissingFeatures), } impl WebGpuError for CreateTextureViewError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::InvalidTextureViewDimension { .. } | Self::InvalidResource(_) | Self::InvalidMultisampledTextureViewDimension(_) | Self::InvalidCubemapTextureDepth { .. } | Self::InvalidCubemapArrayTextureDepth { .. } | Self::InvalidCubeTextureViewSize | Self::ZeroMipLevelCount | Self::ZeroArrayLayerCount | Self::TooManyMipLevels { .. } | Self::TooManyArrayLayers { .. } | Self::InvalidArrayLayerCount { .. } | Self::InvalidAspect { .. } | Self::FormatReinterpretation { .. } | Self::DestroyedResource(_) | Self::TextureViewFormatNotRenderable(_) | Self::TextureViewFormatNotStorage(_) | Self::InvalidTextureViewUsage { .. } | Self::MissingFeatures(_) => ErrorType::Validation, } } } crate::impl_resource_type!(TextureView); crate::impl_labeled!(TextureView); crate::impl_parent_device!(TextureView); crate::impl_storage_item!(TextureView); pub type ExternalTextureDescriptor<'a> = wgt::ExternalTextureDescriptor>; #[derive(Debug)] pub struct ExternalTexture { pub(crate) device: Arc, /// Between 1 and 3 (inclusive) planes of texture data. pub(crate) planes: arrayvec::ArrayVec, 3>, /// Buffer containing a [`crate::device::resource::ExternalTextureParams`] /// describing the external texture. pub(crate) params: Arc, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, } impl Drop for ExternalTexture { fn drop(&mut self) { resource_log!("Destroy raw {}", self.error_ident()); } } impl ExternalTexture { pub fn destroy(self: &Arc) { self.params.destroy(); } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateExternalTextureError { #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] MissingFeatures(#[from] MissingFeatures), #[error(transparent)] InvalidResource(#[from] InvalidResourceError), #[error(transparent)] CreateBuffer(#[from] CreateBufferError), #[error(transparent)] QueueWrite(#[from] queue::QueueWriteError), #[error("External texture format {format:?} expects {expected} planes, but given {provided}")] IncorrectPlaneCount { format: wgt::ExternalTextureFormat, expected: usize, provided: usize, }, #[error("External texture planes cannot be multisampled, but given view with samples = {0}")] InvalidPlaneMultisample(u32), #[error("External texture planes expect a filterable float sample type, but given view with format {format:?} (sample type {sample_type:?})")] InvalidPlaneSampleType { format: wgt::TextureFormat, sample_type: wgt::TextureSampleType, }, #[error("External texture planes expect 2D dimension, but given view with dimension = {0:?}")] InvalidPlaneDimension(wgt::TextureViewDimension), #[error(transparent)] MissingTextureUsage(#[from] MissingTextureUsageError), #[error("External texture format {format:?} plane {plane} expects format with {expected} components but given view with format {provided:?} ({} components)", provided.components())] InvalidPlaneFormat { format: wgt::ExternalTextureFormat, plane: usize, expected: u8, provided: wgt::TextureFormat, }, } impl WebGpuError for CreateExternalTextureError { fn webgpu_error_type(&self) -> ErrorType { match self { CreateExternalTextureError::Device(e) => e.webgpu_error_type(), CreateExternalTextureError::MissingFeatures(e) => e.webgpu_error_type(), CreateExternalTextureError::InvalidResource(e) => e.webgpu_error_type(), CreateExternalTextureError::CreateBuffer(e) => e.webgpu_error_type(), CreateExternalTextureError::QueueWrite(e) => e.webgpu_error_type(), CreateExternalTextureError::MissingTextureUsage(e) => e.webgpu_error_type(), CreateExternalTextureError::IncorrectPlaneCount { .. } | CreateExternalTextureError::InvalidPlaneMultisample(_) | CreateExternalTextureError::InvalidPlaneSampleType { .. } | CreateExternalTextureError::InvalidPlaneDimension(_) | CreateExternalTextureError::InvalidPlaneFormat { .. } => ErrorType::Validation, } } } crate::impl_resource_type!(ExternalTexture); crate::impl_labeled!(ExternalTexture); crate::impl_parent_device!(ExternalTexture); crate::impl_storage_item!(ExternalTexture); crate::impl_trackable!(ExternalTexture); /// Describes a [`Sampler`] #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SamplerDescriptor<'a> { /// Debug label of the sampler. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// How to deal with out of bounds accesses in the u (i.e. x) direction pub address_modes: [wgt::AddressMode; 3], /// How to filter the texture when it needs to be magnified (made larger) pub mag_filter: wgt::FilterMode, /// How to filter the texture when it needs to be minified (made smaller) pub min_filter: wgt::FilterMode, /// How to filter between mip map levels pub mipmap_filter: wgt::MipmapFilterMode, /// Minimum level of detail (i.e. mip level) to use pub lod_min_clamp: f32, /// Maximum level of detail (i.e. mip level) to use pub lod_max_clamp: f32, /// If this is enabled, this is a comparison sampler using the given comparison function. pub compare: Option, /// Must be at least 1. If this is not 1, all filter modes must be linear. pub anisotropy_clamp: u16, /// Border color to use when address_mode is /// [`AddressMode::ClampToBorder`](wgt::AddressMode::ClampToBorder) pub border_color: Option, } #[derive(Debug)] pub struct Sampler { pub(crate) raw: ManuallyDrop>, pub(crate) device: Arc, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, /// `true` if this is a comparison sampler pub(crate) comparison: bool, /// `true` if this is a filtering sampler pub(crate) filtering: bool, } impl Drop for Sampler { fn drop(&mut self) { resource_log!("Destroy raw {}", self.error_ident()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; unsafe { self.device.raw().destroy_sampler(raw); } } } impl Sampler { pub(crate) fn raw(&self) -> &dyn hal::DynSampler { self.raw.as_ref() } } #[derive(Copy, Clone)] pub enum SamplerFilterErrorType { MagFilter, MinFilter, MipmapFilter, } impl fmt::Debug for SamplerFilterErrorType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { SamplerFilterErrorType::MagFilter => write!(f, "magFilter"), SamplerFilterErrorType::MinFilter => write!(f, "minFilter"), SamplerFilterErrorType::MipmapFilter => write!(f, "mipmapFilter"), } } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateSamplerError { #[error(transparent)] Device(#[from] DeviceError), #[error("Invalid lodMinClamp: {0}. Must be greater or equal to 0.0")] InvalidLodMinClamp(f32), #[error("Invalid lodMaxClamp: {lod_max_clamp}. Must be greater or equal to lodMinClamp (which is {lod_min_clamp}).")] InvalidLodMaxClamp { lod_min_clamp: f32, lod_max_clamp: f32, }, #[error("Invalid anisotropic clamp: {0}. Must be at least 1.")] InvalidAnisotropy(u16), #[error("Invalid filter mode for {filter_type:?}: {filter_mode:?}. When anistropic clamp is not 1 (it is {anisotropic_clamp}), all filter modes must be linear.")] InvalidFilterModeWithAnisotropy { filter_type: SamplerFilterErrorType, filter_mode: wgt::FilterMode, anisotropic_clamp: u16, }, #[error("Invalid filter mode for {filter_type:?}: {filter_mode:?}. When anistropic clamp is not 1 (it is {anisotropic_clamp}), all filter modes must be linear.")] InvalidMipmapFilterModeWithAnisotropy { filter_type: SamplerFilterErrorType, filter_mode: wgt::MipmapFilterMode, anisotropic_clamp: u16, }, #[error(transparent)] MissingFeatures(#[from] MissingFeatures), } crate::impl_resource_type!(Sampler); crate::impl_labeled!(Sampler); crate::impl_parent_device!(Sampler); crate::impl_storage_item!(Sampler); crate::impl_trackable!(Sampler); impl WebGpuError for CreateSamplerError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::MissingFeatures(e) => e.webgpu_error_type(), Self::InvalidLodMinClamp(_) | Self::InvalidLodMaxClamp { .. } | Self::InvalidAnisotropy(_) | Self::InvalidFilterModeWithAnisotropy { .. } | Self::InvalidMipmapFilterModeWithAnisotropy { .. } => ErrorType::Validation, } } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateQuerySetError { #[error(transparent)] Device(#[from] DeviceError), #[error("QuerySets cannot be made with zero queries")] ZeroCount, #[error("{count} is too many queries for a single QuerySet. QuerySets cannot be made more than {maximum} queries.")] TooManyQueries { count: u32, maximum: u32 }, #[error(transparent)] MissingFeatures(#[from] MissingFeatures), } impl WebGpuError for CreateQuerySetError { fn webgpu_error_type(&self) -> ErrorType { match self { Self::Device(e) => e.webgpu_error_type(), Self::MissingFeatures(e) => e.webgpu_error_type(), Self::TooManyQueries { .. } | Self::ZeroCount => ErrorType::Validation, } } } pub type QuerySetDescriptor<'a> = wgt::QuerySetDescriptor>; #[derive(Debug)] pub struct QuerySet { pub(crate) raw: ManuallyDrop>, pub(crate) device: Arc, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, pub(crate) desc: wgt::QuerySetDescriptor<()>, } impl Drop for QuerySet { fn drop(&mut self) { resource_log!("Destroy raw {}", self.error_ident()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; unsafe { self.device.raw().destroy_query_set(raw); } } } crate::impl_resource_type!(QuerySet); crate::impl_labeled!(QuerySet); crate::impl_parent_device!(QuerySet); crate::impl_storage_item!(QuerySet); crate::impl_trackable!(QuerySet); impl QuerySet { pub(crate) fn raw(&self) -> &dyn hal::DynQuerySet { self.raw.as_ref() } } pub type BlasDescriptor<'a> = wgt::CreateBlasDescriptor>; pub type TlasDescriptor<'a> = wgt::CreateTlasDescriptor>; pub type BlasPrepareCompactResult = Result<(), BlasPrepareCompactError>; #[cfg(send_sync)] pub type BlasCompactCallback = Box; #[cfg(not(send_sync))] pub type BlasCompactCallback = Box; pub(crate) struct BlasPendingCompact { pub(crate) op: Option, // hold the parent alive while the mapping is active pub(crate) _parent_blas: Arc, } impl fmt::Debug for BlasPendingCompact { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("BlasPendingCompact") .field("op", &()) .field("_parent_blas", &self._parent_blas) .finish() } } #[derive(Debug)] pub(crate) enum BlasCompactState { /// Created from a compact operation. Compacted, /// Waiting for GPU to be done before mapping to get compacted size Waiting(BlasPendingCompact), /// Ready to be compacted Ready { size: wgt::BufferAddress }, /// Ready to prepare to compact. Idle, } #[cfg(send_sync)] unsafe impl Send for BlasCompactState {} #[cfg(send_sync)] unsafe impl Sync for BlasCompactState {} #[derive(Debug)] pub struct Blas { pub(crate) raw: Snatchable>, pub(crate) device: Arc, pub(crate) size_info: hal::AccelerationStructureBuildSizes, pub(crate) sizes: wgt::BlasGeometrySizeDescriptors, pub(crate) flags: wgt::AccelerationStructureFlags, pub(crate) update_mode: wgt::AccelerationStructureUpdateMode, pub(crate) built_index: RwLock>, pub(crate) handle: u64, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, pub(crate) compaction_buffer: Option>>, pub(crate) compacted_state: Mutex, } impl Drop for Blas { fn drop(&mut self) { resource_log!("Destroy raw {}", self.error_ident()); // SAFETY: We are in the Drop impl, and we don't use self.raw or self.compaction_buffer anymore after this point. if let Some(raw) = self.raw.take() { unsafe { self.device.raw().destroy_acceleration_structure(raw); } } if let Some(mut raw) = self.compaction_buffer.take() { unsafe { self.device .raw() .destroy_buffer(ManuallyDrop::take(&mut raw)) } } } } impl RawResourceAccess for Blas { type DynResource = dyn hal::DynAccelerationStructure; fn raw<'a>(&'a self, guard: &'a SnatchGuard) -> Option<&'a Self::DynResource> { self.raw.get(guard).map(|it| it.as_ref()) } } impl Blas { pub(crate) fn prepare_compact_async( self: &Arc, op: Option, ) -> Result, BlasPrepareCompactError)> { let device = &self.device; if let Err(e) = device.check_is_valid() { return Err((op, e.into())); } if self.built_index.read().is_none() { return Err((op, BlasPrepareCompactError::NotBuilt)); } if !self .flags .contains(wgt::AccelerationStructureFlags::ALLOW_COMPACTION) { return Err((op, BlasPrepareCompactError::CompactionUnsupported)); } let mut state = self.compacted_state.lock(); *state = match *state { BlasCompactState::Compacted => { return Err((op, BlasPrepareCompactError::DoubleCompaction)) } BlasCompactState::Waiting(_) => { return Err((op, BlasPrepareCompactError::CompactionPreparingAlready)) } BlasCompactState::Ready { .. } => { return Err((op, BlasPrepareCompactError::CompactionPreparingAlready)) } BlasCompactState::Idle => BlasCompactState::Waiting(BlasPendingCompact { op, _parent_blas: self.clone(), }), }; let submit_index = if let Some(queue) = device.get_queue() { queue.lock_life().prepare_compact(self).unwrap_or(0) // '0' means no wait is necessary } else { // We can safely unwrap below since we just set the `compacted_state` to `BlasCompactState::Waiting`. let (mut callback, status) = self.read_back_compact_size().unwrap(); if let Some(callback) = callback.take() { callback(status); } 0 }; Ok(submit_index) } /// This function returns [`None`] only if [`Self::compacted_state`] is not [`BlasCompactState::Waiting`]. #[must_use] pub(crate) fn read_back_compact_size(&self) -> Option { let mut state = self.compacted_state.lock(); let pending_compact = match mem::replace(&mut *state, BlasCompactState::Idle) { BlasCompactState::Waiting(pending_mapping) => pending_mapping, // Compaction cancelled e.g. by rebuild BlasCompactState::Idle => return None, BlasCompactState::Ready { .. } => { unreachable!("This should be validated out by `prepare_for_compaction`") } _ => panic!("No pending mapping."), }; let status = { let compaction_buffer = self.compaction_buffer.as_ref().unwrap().as_ref(); unsafe { let map_res = self.device.raw().map_buffer( compaction_buffer, 0..size_of::() as wgt::BufferAddress, ); match map_res { Ok(mapping) => { if !mapping.is_coherent { // Clippy complains about this because it might not be intended, but // this is intentional. #[expect(clippy::single_range_in_vec_init)] self.device.raw().invalidate_mapped_ranges( compaction_buffer, &[0..size_of::() as wgt::BufferAddress], ); } let size = core::ptr::read_unaligned( mapping.ptr.as_ptr().cast::(), ); self.device.raw().unmap_buffer(compaction_buffer); if self.size_info.acceleration_structure_size != 0 { debug_assert_ne!(size, 0); } *state = BlasCompactState::Ready { size }; Ok(()) } Err(err) => Err(BlasPrepareCompactError::from(DeviceError::from_hal(err))), } } }; Some((pending_compact.op, status)) } } crate::impl_resource_type!(Blas); crate::impl_labeled!(Blas); crate::impl_parent_device!(Blas); crate::impl_storage_item!(Blas); crate::impl_trackable!(Blas); #[derive(Debug)] pub struct Tlas { pub(crate) raw: Snatchable>, pub(crate) device: Arc, pub(crate) size_info: hal::AccelerationStructureBuildSizes, pub(crate) max_instance_count: u32, pub(crate) flags: wgt::AccelerationStructureFlags, pub(crate) update_mode: wgt::AccelerationStructureUpdateMode, pub(crate) built_index: RwLock>, pub(crate) dependencies: RwLock>>, pub(crate) instance_buffer: ManuallyDrop>, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, } impl Drop for Tlas { fn drop(&mut self) { unsafe { resource_log!("Destroy raw {}", self.error_ident()); if let Some(structure) = self.raw.take() { self.device.raw().destroy_acceleration_structure(structure); } let buffer = ManuallyDrop::take(&mut self.instance_buffer); self.device.raw().destroy_buffer(buffer); } } } impl RawResourceAccess for Tlas { type DynResource = dyn hal::DynAccelerationStructure; fn raw<'a>(&'a self, guard: &'a SnatchGuard) -> Option<&'a Self::DynResource> { self.raw.get(guard).map(|raw| raw.as_ref()) } } crate::impl_resource_type!(Tlas); crate::impl_labeled!(Tlas); crate::impl_parent_device!(Tlas); crate::impl_storage_item!(Tlas); crate::impl_trackable!(Tlas); ================================================ FILE: wgpu-core/src/scratch.rs ================================================ use alloc::{boxed::Box, sync::Arc}; use core::mem::ManuallyDrop; use wgt::BufferUses; use crate::device::{Device, DeviceError}; use crate::{hal_label, resource_log}; #[derive(Debug)] pub struct ScratchBuffer { raw: ManuallyDrop>, device: Arc, } impl ScratchBuffer { pub(crate) fn new(device: &Arc, size: wgt::BufferSize) -> Result { let raw = unsafe { device .raw() .create_buffer(&hal::BufferDescriptor { label: hal_label(Some("(wgpu) scratch buffer"), device.instance_flags), size: size.get(), usage: BufferUses::ACCELERATION_STRUCTURE_SCRATCH, memory_flags: hal::MemoryFlags::empty(), }) .map_err(DeviceError::from_hal)? }; Ok(Self { raw: ManuallyDrop::new(raw), device: device.clone(), }) } pub(crate) fn raw(&self) -> &dyn hal::DynBuffer { self.raw.as_ref() } } impl Drop for ScratchBuffer { fn drop(&mut self) { resource_log!("Destroy raw ScratchBuffer"); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; unsafe { self.device.raw().destroy_buffer(raw) }; } } ================================================ FILE: wgpu-core/src/snatch.rs ================================================ use core::{cell::UnsafeCell, fmt, mem::ManuallyDrop}; use crate::lock::{rank, RankData, RwLock, RwLockReadGuard, RwLockWriteGuard}; /// A guard that provides read access to snatchable data. pub struct SnatchGuard<'a>(RwLockReadGuard<'a, ()>); /// A guard that allows snatching the snatchable data. pub struct ExclusiveSnatchGuard<'a>(#[expect(dead_code)] RwLockWriteGuard<'a, ()>); /// A value that is mostly immutable but can be "snatched" if we need to destroy /// it early. /// /// In order to safely access the underlying data, the device's global snatchable /// lock must be taken. To guarantee it, methods take a read or write guard of that /// special lock. pub struct Snatchable { value: UnsafeCell>, } impl Snatchable { pub fn new(val: T) -> Self { Snatchable { value: UnsafeCell::new(Some(val)), } } #[allow(dead_code)] pub fn empty() -> Self { Snatchable { value: UnsafeCell::new(None), } } /// Get read access to the value. Requires a the snatchable lock's read guard. pub fn get<'a>(&'a self, _guard: &'a SnatchGuard) -> Option<&'a T> { unsafe { (*self.value.get()).as_ref() } } /// Take the value. Requires a the snatchable lock's write guard. pub fn snatch(&self, _guard: &mut ExclusiveSnatchGuard) -> Option { unsafe { (*self.value.get()).take() } } /// Take the value without a guard. This can only be used with exclusive access /// to self, so it does not require locking. /// /// Typically useful in a drop implementation. pub fn take(&mut self) -> Option { self.value.get_mut().take() } } // Can't safely print the contents of a snatchable object without holding // the lock. impl fmt::Debug for Snatchable { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "") } } unsafe impl Sync for Snatchable {} use trace::LockTrace; #[cfg(all(debug_assertions, feature = "std"))] mod trace { use core::{cell::Cell, fmt, panic::Location}; use std::{backtrace::Backtrace, thread}; pub(super) struct LockTrace { purpose: &'static str, caller: &'static Location<'static>, backtrace: Backtrace, } impl fmt::Display for LockTrace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "a {} lock at {}\n{}", self.purpose, self.caller, self.backtrace ) } } impl LockTrace { #[track_caller] pub(super) fn enter(purpose: &'static str) { let new = LockTrace { purpose, caller: Location::caller(), backtrace: Backtrace::capture(), }; if let Some(prev) = SNATCH_LOCK_TRACE.take() { let current = thread::current(); let name = current.name().unwrap_or(""); panic!( "thread '{name}' attempted to acquire a snatch lock recursively.\n\ - Currently trying to acquire {new}\n\ - Previously acquired {prev}", ); } else { SNATCH_LOCK_TRACE.set(Some(new)); } } pub(super) fn exit() { SNATCH_LOCK_TRACE.take(); } } std::thread_local! { static SNATCH_LOCK_TRACE: Cell> = const { Cell::new(None) }; } } #[cfg(not(all(debug_assertions, feature = "std")))] mod trace { pub(super) struct LockTrace { _private: (), } impl LockTrace { pub(super) fn enter(_purpose: &'static str) {} pub(super) fn exit() {} } } /// A Device-global lock for all snatchable data. pub struct SnatchLock { lock: RwLock<()>, } impl SnatchLock { /// The safety of `Snatchable::get` and `Snatchable::snatch` rely on their using of the /// right SnatchLock (the one associated to the same device). This method is unsafe /// to force force sers to think twice about creating a SnatchLock. The only place this /// method should be called is when creating the device. pub unsafe fn new(rank: rank::LockRank) -> Self { SnatchLock { lock: RwLock::new(rank, ()), } } /// Request read access to snatchable resources. #[track_caller] pub fn read(&self) -> SnatchGuard<'_> { LockTrace::enter("read"); SnatchGuard(self.lock.read()) } /// Request write access to snatchable resources. /// /// This should only be called when a resource needs to be snatched. This has /// a high risk of causing lock contention if called concurrently with other /// wgpu work. #[track_caller] pub fn write(&self) -> ExclusiveSnatchGuard<'_> { LockTrace::enter("write"); ExclusiveSnatchGuard(self.lock.write()) } #[track_caller] pub unsafe fn force_unlock_read(&self, data: RankData) { // This is unsafe because it can cause deadlocks if the lock is held. // It should only be used in very specific cases, like when a resource // needs to be snatched in a panic handler. LockTrace::exit(); unsafe { self.lock.force_unlock_read(data) }; } } impl SnatchGuard<'_> { /// Forget the guard, leaving the lock in a locked state with no guard. /// /// This is equivalent to `std::mem::forget`, but preserves the information about the lock /// rank. pub fn forget(this: Self) -> RankData { // Cancel the drop implementation of the current guard. let manually_drop = ManuallyDrop::new(this); // As we are unable to destructure out of this guard due to the drop implementation, // so we manually read the inner value. // SAFETY: This is safe because we never access the original guard again. let inner_guard = unsafe { core::ptr::read(&manually_drop.0) }; RwLockReadGuard::forget(inner_guard) } } impl Drop for SnatchGuard<'_> { fn drop(&mut self) { LockTrace::exit(); } } impl Drop for ExclusiveSnatchGuard<'_> { fn drop(&mut self) { LockTrace::exit(); } } ================================================ FILE: wgpu-core/src/storage.rs ================================================ use alloc::{sync::Arc, vec::Vec}; use core::mem; use crate::id::{Id, Marker}; use crate::resource::ResourceType; use crate::{Epoch, Index}; /// An entry in a `Storage::map` table. #[derive(Debug)] pub(crate) enum Element where T: StorageItem, { /// There are no live ids with this index. Vacant, /// There is one live id with this index, allocated at the given /// epoch. Occupied(T, Epoch), } /// Not a public API. For use only by `player`. #[doc(hidden)] pub trait StorageItem: ResourceType { type Marker: Marker; } impl ResourceType for Arc { const TYPE: &'static str = T::TYPE; } impl StorageItem for Arc { type Marker = T::Marker; } #[macro_export] macro_rules! impl_storage_item { ($ty:ident) => { impl $crate::storage::StorageItem for $ty { type Marker = $crate::id::markers::$ty; } }; } /// A table of `T` values indexed by the id type `I`. /// /// `Storage` implements [`core::ops::Index`], accepting `Id` values as /// indices. /// /// The table is represented as a vector indexed by the ids' index /// values, so you should use an id allocator like `IdentityManager` /// that keeps the index values dense and close to zero. #[derive(Debug)] pub(crate) struct Storage where T: StorageItem, { pub(crate) map: Vec>, kind: &'static str, } impl Storage where T: StorageItem, { pub(crate) fn new() -> Self { Self { map: Vec::new(), kind: T::TYPE, } } } impl Storage where T: StorageItem, { pub(crate) fn insert(&mut self, id: Id, value: T) { let (index, epoch) = id.unzip(); let index = index as usize; if index >= self.map.len() { self.map.resize_with(index + 1, || Element::Vacant); } match mem::replace(&mut self.map[index], Element::Occupied(value, epoch)) { Element::Vacant => {} Element::Occupied(_, storage_epoch) => { assert_ne!( epoch, storage_epoch, "Index {index:?} of {} is already occupied", T::TYPE ); } } } pub(crate) fn remove(&mut self, id: Id) -> T { let (index, epoch) = id.unzip(); let stored = self .map .get_mut(index as usize) .unwrap_or_else(|| panic!("{}[{:?}] does not exist", self.kind, id)); match mem::replace(stored, Element::Vacant) { Element::Occupied(value, storage_epoch) => { assert_eq!(epoch, storage_epoch, "id epoch mismatch"); value } Element::Vacant => panic!("Cannot remove a vacant resource"), } } pub(crate) fn iter(&self) -> impl Iterator, &T)> { self.map .iter() .enumerate() .filter_map(move |(index, x)| match *x { Element::Occupied(ref value, storage_epoch) => { Some((Id::zip(index as Index, storage_epoch), value)) } _ => None, }) } } impl Storage where T: StorageItem + Clone, { /// Get an owned reference to an item. /// Panics if there is an epoch mismatch, the entry is empty or in error. pub(crate) fn get(&self, id: Id) -> T { let (index, epoch) = id.unzip(); let (result, storage_epoch) = match self.map.get(index as usize) { Some(&Element::Occupied(ref v, epoch)) => (v.clone(), epoch), None | Some(&Element::Vacant) => panic!("{}[{:?}] does not exist", self.kind, id), }; assert_eq!( epoch, storage_epoch, "{}[{:?}] is no longer alive", self.kind, id ); result } } ================================================ FILE: wgpu-core/src/timestamp_normalization/common.wgsl ================================================ // Common routines for timestamp normalization. // // This is split out into its own file so that the tests in `tests` can include // it without including the normal endpoints and interface definitions. /// 64-bit unsigned integer type. /// /// We cannot rely on native 64-bit integers, so we define our own 64-bit /// integer type as two 32-bit integers. struct Uint64 { /// Least significant word. low: u32, /// Most significant word. high: u32, } /// 96-bit unsigned integer type. struct Uint96 { /// Least significant word. low: u32, /// Middle word. mid: u32, /// Most significant word. high: u32, } /// Truncates a 96-bit number to a 64-bit number by discarding the upper 32 bits. fn truncate_u96_to_u64(x: Uint96) -> Uint64 { return Uint64( x.low, x.mid, ); } /// Returns the lower 16 bits of a 32-bit integer. fn low(a: u32) -> u32 { return a & 0xFFFF; } /// Returns the upper 16 bits of a 32-bit integer. fn high(a: u32) -> u32 { return a >> 16; } /// Combines two 16bit words into a single 32bit word. /// `w1` is the upper 16 bits and `w0` is the lower 16 bits. /// /// The high 16 bits of each argument are discarded. fn u32_from_u16s(w1: u32, w0: u32) -> u32 { return low(w1) << 16 | low(w0); } // Multiplies a 64-bit number by a 32-bit number and outputs a 96-bit result. // // The number of digits (bits) needed to represent the result of a multiplication // is the sum of the number of input digits (bits). Since we are multiplying a // 64-bit number by a 32-bit number, we need 96 bits to represent the result. fn u64_mul_u32(a: Uint64, b: u32) -> Uint96 { // Does not use any 64-bit operations and we don't have access to `mul(u32, u32) -> u64` // operations, so we operate entirely on `mul(u16, u16) -> u32`. // This implements standard "long multiplication" algorithm using 16-bit words. // Each element in this diagram is a 16-bit word. // // a3 a2 a1 a0 // * b1 b0 // ---------------------------- // i0 = p00 p00 // i1 = p10 p10 // i2 = p20 p20 // i3 = p30 p30 // i4 = p01 p01 // i5 = p11 p11 // i6 = p21 p21 // i7 = p31 p31 // ---------------------------- // r6 r5 r4 r3 r2 r1 r0 // Decompose the 64-bit number into four 16-bit words. let a0 = low(a.low); let a1 = high(a.low); let a2 = low(a.high); let a3 = high(a.high); // Decompose the 32-bit number into two 16-bit words. let b0 = low(b); let b1 = high(b); // Each line represents one row in the diagram above. let i0 = a0 * b0; let i1 = a1 * b0; let i2 = a2 * b0; let i3 = a3 * b0; let i4 = a0 * b1; let i5 = a1 * b1; let i6 = a2 * b1; let i7 = a3 * b1; // Each line represents one column in the diagram above. // // The high 16 bits of each column are the carry to the next column. let r0 = low(i0); let r1 = high(i0) + low(i1) + low(i4) + high(r0); let r2 = high(i1) + low(i2) + high(i4) + low(i5) + high(r1); let r3 = high(i2) + low(i3) + high(i5) + low(i6) + high(r2); let r4 = high(i3) + high(i6) + low(i7) + high(r3); let r5 = high(i7) + high(r4); // The r5 carry will always be zero. let out0 = u32_from_u16s(r1, r0); let out1 = u32_from_u16s(r3, r2); let out2 = u32_from_u16s(r5, r4); return Uint96(out0, out1, out2); } // Shifts a 96-bit number right by a given number of bits. // // The shift is in the range [0, 32]. fn shift_right_96(x: Uint96, shift: u32) -> Uint96 { // Shift wraps around at 32, which breaks the algorithm when // either shift is 32 or inv_shift is 32. if (shift == 0) { return x; } if (shift == 32) { return Uint96(x.mid, x.high, 0); } let inv_shift = 32 - shift; let carry2 = x.high << inv_shift; let carry1 = x.mid << inv_shift; var out: Uint96; out.high = x.high >> shift; out.mid = (x.mid >> shift) | carry2; out.low = (x.low >> shift) | carry1; return out; } ================================================ FILE: wgpu-core/src/timestamp_normalization/mod.rs ================================================ //! Utility for normalizing GPU timestamp queries to have a consistent //! 1GHz period. This uses a compute shader to do the normalization, //! so the timestamps exist in their correct format on the GPU, as //! is required by the WebGPU specification. //! //! ## Algorithm //! //! The fundamental operation is multiplying a u64 timestamp by an f32 //! value. We have neither f64s nor u64s in shaders, so we need to do //! something more complicated. //! //! We first decompose the f32 into a u32 fraction where the denominator //! is a power of two. We do the computation with f64 for ease of computation, //! as those can store u32s losslessly. //! //! Because the denominator is a power of two, this means the shader can evaluate //! this divide by using a shift. Additionally, we always choose the largest denominator //! we can, so that the fraction is as precise as possible. //! //! To evaluate this function, we have two helper operations (both in common.wgsl). //! //! 1. `u64_mul_u32` multiplies a u64 by a u32 and returns a u96. //! 2. `shift_right_u96` shifts a u96 right by a given amount, returning a u96. //! //! See their implementations for more details. //! //! We then multiply the timestamp by the numerator, and shift it right by the //! denominator. This gives us the normalized timestamp. use core::num::NonZeroU64; use alloc::{boxed::Box, string::String, string::ToString, sync::Arc}; use hashbrown::HashMap; use crate::{ device::{Device, DeviceError}, hal_label, pipeline::{CreateComputePipelineError, CreateShaderModuleError}, resource::Buffer, snatch::SnatchGuard, track::BufferTracker, }; pub const TIMESTAMP_NORMALIZATION_BUFFER_USES: wgt::BufferUses = wgt::BufferUses::STORAGE_READ_WRITE; struct InternalState { temporary_bind_group_layout: Box, pipeline_layout: Box, pipeline: Box, } #[derive(Debug, Clone, thiserror::Error)] pub enum TimestampNormalizerInitError { #[error("Failed to initialize bind group layout")] BindGroupLayout(#[source] DeviceError), #[cfg(feature = "wgsl")] #[error("Failed to parse shader")] ParseWgsl(#[source] naga::error::ShaderError), #[error("Failed to validate shader module")] ValidateWgsl(#[source] naga::error::ShaderError>), #[error("Failed to create shader module")] CreateShaderModule(#[from] CreateShaderModuleError), #[error("Failed to create pipeline layout")] PipelineLayout(#[source] DeviceError), #[error("Failed to create compute pipeline")] ComputePipeline(#[from] CreateComputePipelineError), } /// Normalizes GPU timestamps to have a consistent 1GHz period. /// See module documentation for more information. pub struct TimestampNormalizer { state: Option, } impl TimestampNormalizer { /// Creates a new timestamp normalizer. /// /// If the device cannot support automatic timestamp normalization, /// this will return a normalizer that does nothing. /// /// # Errors /// /// If any resources are invalid, this will return an error. pub fn new( device: &Device, timestamp_period: f32, ) -> Result { unsafe { if !device .instance_flags .contains(wgt::InstanceFlags::AUTOMATIC_TIMESTAMP_NORMALIZATION) { return Ok(Self { state: None }); } if !device .downlevel .flags .contains(wgt::DownlevelFlags::COMPUTE_SHADERS) { log::error!("Automatic timestamp normalization was requested, but compute shaders are not supported."); return Ok(Self { state: None }); } if timestamp_period == 1.0 { // If the period is 1, we don't need to do anything to them. return Ok(Self { state: None }); } let temporary_bind_group_layout = device .raw() .create_bind_group_layout(&hal::BindGroupLayoutDescriptor { label: hal_label( Some("(wgpu internal) Timestamp Normalization Bind Group Layout"), device.instance_flags, ), flags: hal::BindGroupLayoutFlags::empty(), entries: &[wgt::BindGroupLayoutEntry { binding: 0, visibility: wgt::ShaderStages::COMPUTE, ty: wgt::BindingType::Buffer { ty: wgt::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: Some(NonZeroU64::new(8).unwrap()), }, count: None, }], }) .map_err(|e| { TimestampNormalizerInitError::BindGroupLayout(device.handle_hal_error(e)) })?; let common_src = include_str!("common.wgsl"); let src = include_str!("timestamp_normalization.wgsl"); let preprocessed_src = alloc::format!("{common_src}\n{src}"); #[cfg(feature = "wgsl")] let module = naga::front::wgsl::parse_str(&preprocessed_src).map_err(|inner| { TimestampNormalizerInitError::ParseWgsl(naga::error::ShaderError { source: preprocessed_src.clone(), label: None, inner: Box::new(inner), }) })?; #[cfg(not(feature = "wgsl"))] #[allow(clippy::diverging_sub_expression)] let module = panic!("Timestamp normalization requires the wgsl feature flag to be enabled!"); let info = crate::device::create_validator( wgt::Features::IMMEDIATES, wgt::DownlevelFlags::empty(), naga::valid::ValidationFlags::all(), ) .validate(&module) .map_err(|inner| { TimestampNormalizerInitError::ValidateWgsl(naga::error::ShaderError { source: preprocessed_src.clone(), label: None, inner: Box::new(inner), }) })?; let hal_shader = hal::ShaderInput::Naga(hal::NagaShader { module: alloc::borrow::Cow::Owned(module), info, debug_source: None, }); let hal_desc = hal::ShaderModuleDescriptor { label: hal_label( Some("(wgpu internal) Timestamp normalizer shader module"), device.instance_flags, ), runtime_checks: wgt::ShaderRuntimeChecks::unchecked(), }; let module = device .raw() .create_shader_module(&hal_desc, hal_shader) .map_err(|error| match error { hal::ShaderError::Device(error) => { CreateShaderModuleError::Device(device.handle_hal_error(error)) } hal::ShaderError::Compilation(ref msg) => { log::error!("Shader error: {msg}"); CreateShaderModuleError::Generation } })?; let pipeline_layout = device .raw() .create_pipeline_layout(&hal::PipelineLayoutDescriptor { label: hal_label( Some("(wgpu internal) Timestamp normalizer pipeline layout"), device.instance_flags, ), bind_group_layouts: &[Some(temporary_bind_group_layout.as_ref())], immediate_size: 8, flags: hal::PipelineLayoutFlags::empty(), }) .map_err(|e| { TimestampNormalizerInitError::PipelineLayout(device.handle_hal_error(e)) })?; let (multiplier, shift) = compute_timestamp_period(timestamp_period); let mut constants = HashMap::with_capacity(2); constants.insert(String::from("TIMESTAMP_PERIOD_MULTIPLY"), multiplier as f64); constants.insert(String::from("TIMESTAMP_PERIOD_SHIFT"), shift as f64); let pipeline_desc = hal::ComputePipelineDescriptor { label: hal_label( Some("(wgpu internal) Timestamp normalizer pipeline"), device.instance_flags, ), layout: pipeline_layout.as_ref(), stage: hal::ProgrammableStage { module: module.as_ref(), entry_point: "main", constants: &constants, zero_initialize_workgroup_memory: false, }, cache: None, }; let pipeline = device .raw() .create_compute_pipeline(&pipeline_desc) .map_err(|err| match err { hal::PipelineError::Device(error) => { CreateComputePipelineError::Device(device.handle_hal_error(error)) } hal::PipelineError::Linkage(_stages, msg) => { CreateComputePipelineError::Internal(msg) } hal::PipelineError::EntryPoint(_stage) => CreateComputePipelineError::Internal( crate::device::ENTRYPOINT_FAILURE_ERROR.to_string(), ), hal::PipelineError::PipelineConstants(_, error) => { CreateComputePipelineError::PipelineConstants(error) } })?; Ok(Self { state: Some(InternalState { temporary_bind_group_layout, pipeline_layout, pipeline, }), }) } } /// Create a bind group for normalizing timestamps in `buffer`. /// /// This function is unsafe because it does not know that `buffer_size` is /// the true size of the buffer. pub unsafe fn create_normalization_bind_group( &self, device: &Device, buffer: &dyn hal::DynBuffer, buffer_label: Option<&str>, buffer_size: wgt::BufferSize, buffer_usages: wgt::BufferUsages, ) -> Result { unsafe { let Some(ref state) = &self.state else { return Ok(TimestampNormalizationBindGroup { raw: None }); }; if !buffer_usages.contains(wgt::BufferUsages::QUERY_RESOLVE) { return Ok(TimestampNormalizationBindGroup { raw: None }); } // If this buffer is large enough that we wouldn't be able to bind the entire thing // at once to normalize the timestamps, we can't use it. We force the buffer to fail // to allocate. The lowest max binding size is 128MB, and query sets must be small // (no more than 4096), so this should never be hit in practice by sane programs. if buffer_size.get() > device.adapter.limits().max_storage_buffer_binding_size { return Err(DeviceError::OutOfMemory); } let bg_label_alloc; let label = match buffer_label { Some(label) => { bg_label_alloc = alloc::format!("Timestamp normalization bind group ({label})"); &*bg_label_alloc } None => "Timestamp normalization bind group", }; let bg = device .raw() .create_bind_group(&hal::BindGroupDescriptor { label: hal_label(Some(label), device.instance_flags), layout: &*state.temporary_bind_group_layout, buffers: &[hal::BufferBinding::new_unchecked(buffer, 0, buffer_size)], samplers: &[], textures: &[], acceleration_structures: &[], external_textures: &[], entries: &[hal::BindGroupEntry { binding: 0, resource_index: 0, count: 1, }], }) .map_err(|e| device.handle_hal_error(e))?; Ok(TimestampNormalizationBindGroup { raw: Some(bg) }) } } pub fn normalize( &self, snatch_guard: &SnatchGuard<'_>, encoder: &mut dyn hal::DynCommandEncoder, tracker: &mut BufferTracker, bind_group: &TimestampNormalizationBindGroup, buffer: &Arc, buffer_offset_bytes: u64, total_timestamps: u32, ) { let Some(ref state) = &self.state else { return; }; let Some(bind_group) = bind_group.raw.as_deref() else { return; }; let buffer_offset_timestamps: u32 = (buffer_offset_bytes / 8).try_into().unwrap(); // Unreachable as MAX_QUERIES is way less than u32::MAX let pending_barrier = tracker.set_single(buffer, wgt::BufferUses::STORAGE_READ_WRITE); let barrier = pending_barrier.map(|pending| pending.into_hal(buffer, snatch_guard)); let needed_workgroups = total_timestamps.div_ceil(64); unsafe { encoder.transition_buffers(barrier.as_slice()); encoder.begin_compute_pass(&hal::ComputePassDescriptor { label: hal_label( Some("(wgpu internal) Timestamp normalization pass"), buffer.device.instance_flags, ), timestamp_writes: None, }); encoder.set_compute_pipeline(&*state.pipeline); encoder.set_bind_group(&*state.pipeline_layout, 0, bind_group, &[]); encoder.set_immediates( &*state.pipeline_layout, 0, &[buffer_offset_timestamps, total_timestamps], ); encoder.dispatch([needed_workgroups, 1, 1]); encoder.end_compute_pass(); } } pub fn dispose(self, device: &dyn hal::DynDevice) { unsafe { let Some(state) = self.state else { return; }; device.destroy_compute_pipeline(state.pipeline); device.destroy_pipeline_layout(state.pipeline_layout); device.destroy_bind_group_layout(state.temporary_bind_group_layout); } } pub fn enabled(&self) -> bool { self.state.is_some() } } #[derive(Debug)] pub struct TimestampNormalizationBindGroup { raw: Option>, } impl TimestampNormalizationBindGroup { pub fn dispose(self, device: &dyn hal::DynDevice) { unsafe { if let Some(raw) = self.raw { device.destroy_bind_group(raw); } } } } fn compute_timestamp_period(input: f32) -> (u32, u32) { let pow2 = input.log2().ceil() as i32; let clamped_pow2 = pow2.clamp(-32, 32).unsigned_abs(); let shift = 32 - clamped_pow2; let denominator = (1u64 << shift) as f64; // float -> int conversions are defined to saturate. let multiplier = (input as f64 * denominator).round() as u32; (multiplier, shift) } #[cfg(test)] mod tests { use core::f64; fn assert_timestamp_case(input: f32) { let (multiplier, shift) = super::compute_timestamp_period(input); let output = multiplier as f64 / (1u64 << shift) as f64; assert!((input as f64 - output).abs() < 0.0000001); } #[test] fn compute_timestamp_period() { assert_timestamp_case(0.01); assert_timestamp_case(0.5); assert_timestamp_case(1.0); assert_timestamp_case(2.0); assert_timestamp_case(2.7); assert_timestamp_case(1000.7); } } ================================================ FILE: wgpu-core/src/timestamp_normalization/timestamp_normalization.wgsl ================================================ // Must have "common.wgsl" preprocessed before this file's contents. // // To compile this locally, you can run: // ``` // cat common.wgsl timestamp_normalization.wgsl | cargo run -p naga-cli -- --stdin-file-path timestamp_normalization.wgsl // ``` // For an explanation of the timestamp normalization process, see // the `mod.rs` file in this folder. // These is the timestamp period turned into a fraction // with an integer numerator and denominator. The denominator // is a power of two, so the division can be done with a shift. override TIMESTAMP_PERIOD_MULTIPLY: u32 = 1; override TIMESTAMP_PERIOD_SHIFT: u32 = 0; @group(0) @binding(0) var timestamps: array; struct ImmediateData { timestamp_offset: u32, timestamp_count: u32, } var im: ImmediateData; @compute @workgroup_size(64) fn main(@builtin(global_invocation_id) id: vec3u) { if id.x >= im.timestamp_count { return; } let index = id.x + im.timestamp_offset; let input_value = timestamps[index]; let tmp1 = u64_mul_u32(input_value, TIMESTAMP_PERIOD_MULTIPLY); let tmp2 = shift_right_96(tmp1, TIMESTAMP_PERIOD_SHIFT); timestamps[index] = truncate_u96_to_u64(tmp2); } ================================================ FILE: wgpu-core/src/track/blas.rs ================================================ use crate::{ resource::{Blas, Trackable}, track::metadata::ResourceMetadata, }; use alloc::sync::Arc; /// A tracker that holds tracks BLASes. /// /// This is mostly a safe shell around [`ResourceMetadata`] #[derive(Debug)] pub(crate) struct BlasTracker { metadata: ResourceMetadata>, size: usize, } impl BlasTracker { pub fn new() -> Self { Self { metadata: ResourceMetadata::new(), size: 0, } } /// Inserts a single resource into the resource tracker. /// /// Returns a reference to the newly inserted resource. /// (This allows avoiding a clone/reference count increase in many cases.) pub fn insert_single(&mut self, resource: Arc) -> &Arc { let index = resource.tracker_index().as_usize(); self.allow_index(index); unsafe { // # SAFETY: we just allowed this resource, which makes the metadata object larger if // it's not in bounds self.metadata.insert(index, resource) } } /// Sets the size of all the vectors inside the tracker. /// /// Must be called with the highest possible Texture ID before /// all unsafe functions are called. pub fn set_size(&mut self, size: usize) { self.size = size; self.metadata.set_size(size) } /// Extend the vectors to let the given index be valid. fn allow_index(&mut self, index: usize) { if index >= self.size { self.set_size(index + 1); } } /// Returns true if the tracker owns the given texture. pub fn contains(&self, blas: &Blas) -> bool { self.metadata.contains(blas.tracker_index().as_usize()) } } ================================================ FILE: wgpu-core/src/track/buffer.rs ================================================ //! Buffer Trackers //! //! Buffers are represented by a single state for the whole resource, //! a 16 bit bitflag of buffer usages. Because there is only ever //! one subresource, they have no selector. use alloc::{ sync::{Arc, Weak}, vec::Vec, }; use hal::BufferBarrier; use wgt::{strict_assert, strict_assert_eq, BufferUses}; use super::{PendingTransition, TrackerIndex}; use crate::{ resource::{Buffer, Trackable}, snatch::SnatchGuard, track::{ invalid_resource_state, skip_barrier, ResourceMetadata, ResourceMetadataProvider, ResourceUsageCompatibilityError, ResourceUses, }, }; impl ResourceUses for BufferUses { const EXCLUSIVE: Self = Self::EXCLUSIVE; type Selector = (); fn bits(self) -> u16 { Self::bits(&self) } fn any_exclusive(self) -> bool { self.intersects(Self::EXCLUSIVE) } } /// Stores a bind group's buffers + their usages (within the bind group). #[derive(Debug)] pub(crate) struct BufferBindGroupState { buffers: Vec<(Arc, BufferUses)>, } impl BufferBindGroupState { pub fn new() -> Self { Self { buffers: Vec::new(), } } /// Optimize the buffer bind group state by sorting it by ID. /// /// When this list of states is merged into a tracker, the memory /// accesses will be in a constant ascending order. pub(crate) fn optimize(&mut self) { self.buffers .sort_unstable_by_key(|(b, _)| b.tracker_index()); } /// Returns a list of all buffers tracked. May contain duplicates. pub fn used_tracker_indices(&self) -> impl Iterator + '_ { self.buffers .iter() .map(|(b, _)| b.tracker_index()) .collect::>() .into_iter() } /// Adds the given resource with the given state. pub fn insert_single(&mut self, buffer: Arc, state: BufferUses) { self.buffers.push((buffer, state)); } } /// Stores all buffer state within a single usage scope. #[derive(Debug)] pub(crate) struct BufferUsageScope { state: Vec, metadata: ResourceMetadata>, ordered_uses_mask: BufferUses, } impl Default for BufferUsageScope { fn default() -> Self { Self { state: Vec::new(), metadata: ResourceMetadata::new(), ordered_uses_mask: BufferUses::empty(), } } } impl BufferUsageScope { fn tracker_assert_in_bounds(&self, index: usize) { strict_assert!(index < self.state.len()); self.metadata.tracker_assert_in_bounds(index); } pub fn clear(&mut self) { self.state.clear(); self.metadata.clear(); } /// Sets the size of all the vectors inside the tracker. /// /// Must be called with the highest possible Buffer ID before /// all unsafe functions are called. pub fn set_size(&mut self, size: usize) { self.state.resize(size, BufferUses::empty()); self.metadata.set_size(size); } pub fn set_ordered_uses_mask(&mut self, ordered_uses_mask: BufferUses) { self.ordered_uses_mask = ordered_uses_mask; } /// Extend the vectors to let the given index be valid. fn allow_index(&mut self, index: usize) { if index >= self.state.len() { self.set_size(index + 1); } } /// Merge the list of buffer states in the given bind group into this usage scope. /// /// If any of the resulting states is invalid, stops the merge and returns a usage /// conflict with the details of the invalid state. /// /// Because bind groups do not check if the union of all their states is valid, /// this method is allowed to return Err on the first bind group bound. /// /// # Safety /// /// [`Self::set_size`] must be called with the maximum possible Buffer ID before this /// method is called. pub unsafe fn merge_bind_group( &mut self, bind_group: &BufferBindGroupState, ) -> Result<(), ResourceUsageCompatibilityError> { for &(ref resource, state) in bind_group.buffers.iter() { let index = resource.tracker_index().as_usize(); unsafe { self.insert_or_merge( index as _, index, BufferStateProvider::Direct { state }, ResourceMetadataProvider::Direct { resource }, )? }; } Ok(()) } /// Merge the list of buffer states in the given usage scope into this UsageScope. /// /// If any of the resulting states is invalid, stops the merge and returns a usage /// conflict with the details of the invalid state. /// /// If the given tracker uses IDs higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. pub fn merge_usage_scope( &mut self, scope: &Self, ) -> Result<(), ResourceUsageCompatibilityError> { let incoming_size = scope.state.len(); if incoming_size > self.state.len() { self.set_size(incoming_size); } for index in scope.metadata.owned_indices() { self.tracker_assert_in_bounds(index); scope.tracker_assert_in_bounds(index); unsafe { self.insert_or_merge( index as u32, index, BufferStateProvider::Indirect { state: &scope.state, }, ResourceMetadataProvider::Indirect { metadata: &scope.metadata, }, )?; }; } Ok(()) } /// Merge a single state into the UsageScope. /// /// If the resulting state is invalid, returns a usage /// conflict with the details of the invalid state. /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. pub fn merge_single( &mut self, buffer: &Arc, new_state: BufferUses, ) -> Result<(), ResourceUsageCompatibilityError> { let index = buffer.tracker_index().as_usize(); self.allow_index(index); self.tracker_assert_in_bounds(index); unsafe { self.insert_or_merge( index as _, index, BufferStateProvider::Direct { state: new_state }, ResourceMetadataProvider::Direct { resource: buffer }, )?; } Ok(()) } /// Does an insertion operation if the index isn't tracked /// in the current metadata, otherwise merges the given state /// with the current state. If the merging would cause /// a conflict, returns that usage conflict. /// /// # Safety /// /// Indexes must be valid indexes into all arrays passed in /// to this function, either directly or via metadata or provider structs. #[inline(always)] unsafe fn insert_or_merge( &mut self, index32: u32, index: usize, state_provider: BufferStateProvider<'_>, metadata_provider: ResourceMetadataProvider<'_, Arc>, ) -> Result<(), ResourceUsageCompatibilityError> { let currently_owned = unsafe { self.metadata.contains_unchecked(index) }; if !currently_owned { unsafe { insert( None, &mut self.state, &mut self.metadata, index, state_provider, None, metadata_provider, ) }; return Ok(()); } unsafe { merge( &mut self.state, index32, index, state_provider, metadata_provider, ) } } /// Removes the indicated usage from the scope. /// /// Note that multiple uses of the same type get merged. It is only /// safe to remove a usage if you are certain you aren't going to /// erase another usage you don't know about. pub fn remove_usage(&mut self, buffer: &Buffer, usage: BufferUses) { let index = buffer.tracker_index().as_usize(); if self.metadata.contains(index) { // SAFETY: If the buffer is part of this usage scope, then the index // is in range. unsafe { *self.state.get_unchecked_mut(index) &= !usage; } } } } /// Stores all buffer state within a command buffer. pub(crate) struct BufferTracker { start: Vec, end: Vec, metadata: ResourceMetadata>, temp: Vec>, ordered_uses_mask: BufferUses, } impl BufferTracker { pub fn new(ordered_uses_mask: BufferUses) -> Self { Self { start: Vec::new(), end: Vec::new(), metadata: ResourceMetadata::new(), temp: Vec::new(), ordered_uses_mask, } } fn tracker_assert_in_bounds(&self, index: usize) { strict_assert!(index < self.start.len()); strict_assert!(index < self.end.len()); self.metadata.tracker_assert_in_bounds(index); } /// Sets the size of all the vectors inside the tracker. /// /// Must be called with the highest possible Buffer ID before /// all unsafe functions are called. pub fn set_size(&mut self, size: usize) { self.start.resize(size, BufferUses::empty()); self.end.resize(size, BufferUses::empty()); self.metadata.set_size(size); } /// Extend the vectors to let the given index be valid. fn allow_index(&mut self, index: usize) { if index >= self.start.len() { self.set_size(index + 1); } } /// Returns true if the given buffer is tracked. pub fn contains(&self, buffer: &Buffer) -> bool { self.metadata.contains(buffer.tracker_index().as_usize()) } /// Returns a list of all buffers tracked. pub fn used_resources(&self) -> impl Iterator> + '_ { self.metadata.owned_resources() } /// Drains all currently pending transitions. pub fn drain_transitions<'a, 'b: 'a>( &'b mut self, snatch_guard: &'a SnatchGuard<'a>, ) -> impl Iterator> { let buffer_barriers = self.temp.drain(..).map(|pending| { let buf = unsafe { self.metadata.get_resource_unchecked(pending.id as _) }; pending.into_hal(buf, snatch_guard) }); buffer_barriers } /// Sets the state of a single buffer. /// /// If a transition is needed to get the buffer into the given state, that transition /// is returned. No more than one transition is needed. /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. pub fn set_single( &mut self, buffer: &Arc, state: BufferUses, ) -> Option> { let index: usize = buffer.tracker_index().as_usize(); self.allow_index(index); self.tracker_assert_in_bounds(index); unsafe { self.insert_or_barrier_update( index, BufferStateProvider::Direct { state }, None, ResourceMetadataProvider::Direct { resource: buffer }, ) }; strict_assert!(self.temp.len() <= 1); self.temp.pop() } /// Sets the given state for all buffers in the given tracker. /// /// If a transition is needed to get the buffers into the needed state, /// those transitions are stored within the tracker. A subsequent /// call to [`Self::drain_transitions`] is needed to get those transitions. /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. pub fn set_from_tracker(&mut self, tracker: &Self) { let incoming_size = tracker.start.len(); if incoming_size > self.start.len() { self.set_size(incoming_size); } for index in tracker.metadata.owned_indices() { self.tracker_assert_in_bounds(index); tracker.tracker_assert_in_bounds(index); unsafe { self.insert_or_barrier_update( index, BufferStateProvider::Indirect { state: &tracker.start, }, Some(BufferStateProvider::Indirect { state: &tracker.end, }), ResourceMetadataProvider::Indirect { metadata: &tracker.metadata, }, ) } } } /// Sets the given state for all buffers in the given UsageScope. /// /// If a transition is needed to get the buffers into the needed state, /// those transitions are stored within the tracker. A subsequent /// call to [`Self::drain_transitions`] is needed to get those transitions. /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. pub fn set_from_usage_scope(&mut self, scope: &BufferUsageScope) { let incoming_size = scope.state.len(); if incoming_size > self.start.len() { self.set_size(incoming_size); } for index in scope.metadata.owned_indices() { self.tracker_assert_in_bounds(index); scope.tracker_assert_in_bounds(index); unsafe { self.insert_or_barrier_update( index, BufferStateProvider::Indirect { state: &scope.state, }, None, ResourceMetadataProvider::Indirect { metadata: &scope.metadata, }, ) } } } /// Iterates through all buffers in the given bind group and adopts /// the state given for those buffers in the UsageScope. It also /// removes all touched buffers from the usage scope. /// /// If a transition is needed to get the buffers into the needed state, /// those transitions are stored within the tracker. A subsequent /// call to [`Self::drain_transitions`] is needed to get those transitions. /// /// This is a really funky method used by Compute Passes to generate /// barriers after a call to dispatch without needing to iterate /// over all elements in the usage scope. We use each the /// a given iterator of ids as a source of which IDs to look at. /// All the IDs must have first been added to the usage scope. /// /// # Panics /// /// If a resource identified by `index_source` is not found in the usage /// scope. pub fn set_and_remove_from_usage_scope_sparse( &mut self, scope: &mut BufferUsageScope, index_source: impl IntoIterator, ) { let incoming_size = scope.state.len(); if incoming_size > self.start.len() { self.set_size(incoming_size); } for index in index_source { let index = index.as_usize(); scope.tracker_assert_in_bounds(index); if unsafe { !scope.metadata.contains_unchecked(index) } { continue; } // SAFETY: we checked that the index is in bounds for the scope, and // called `set_size` to ensure it is valid for `self`. unsafe { self.insert_or_barrier_update( index, BufferStateProvider::Indirect { state: &scope.state, }, None, ResourceMetadataProvider::Indirect { metadata: &scope.metadata, }, ) }; unsafe { scope.metadata.remove(index) }; } } /// If the resource isn't tracked /// - Inserts the given resource. /// - Uses the `start_state_provider` to populate `start_states` /// - Uses either `end_state_provider` or `start_state_provider` /// to populate `current_states`. /// /// If the resource is tracked /// - Inserts barriers from the state in `current_states` /// to the state provided by `start_state_provider`. /// - Updates the `current_states` with either the state from /// `end_state_provider` or `start_state_provider`. /// /// Any barriers are added to the barrier vector. /// /// # Safety /// /// Indexes must be valid indexes into all arrays passed in /// to this function, either directly or via metadata or provider structs. #[inline(always)] unsafe fn insert_or_barrier_update( &mut self, index: usize, start_state_provider: BufferStateProvider<'_>, end_state_provider: Option>, metadata_provider: ResourceMetadataProvider<'_, Arc>, ) { let currently_owned = unsafe { self.metadata.contains_unchecked(index) }; if !currently_owned { unsafe { insert( Some(&mut self.start), &mut self.end, &mut self.metadata, index, start_state_provider, end_state_provider, metadata_provider, ) }; return; } let update_state_provider = end_state_provider.unwrap_or_else(|| start_state_provider.clone()); unsafe { barrier( &mut self.end, index, start_state_provider, &mut self.temp, self.ordered_uses_mask, ) }; unsafe { update(&mut self.end, index, update_state_provider) }; } } /// Stores all buffer state within a device. pub(crate) struct DeviceBufferTracker { current_states: Vec, metadata: ResourceMetadata>, temp: Vec>, ordered_uses_mask: BufferUses, } impl DeviceBufferTracker { pub fn new(ordered_uses_mask: BufferUses) -> Self { Self { current_states: Vec::new(), metadata: ResourceMetadata::new(), temp: Vec::new(), ordered_uses_mask, } } fn tracker_assert_in_bounds(&self, index: usize) { strict_assert!(index < self.current_states.len()); self.metadata.tracker_assert_in_bounds(index); } /// Extend the vectors to let the given index be valid. fn allow_index(&mut self, index: usize) { if index >= self.current_states.len() { self.current_states.resize(index + 1, BufferUses::empty()); self.metadata.set_size(index + 1); } } /// Returns a list of all buffers tracked. pub fn used_resources(&self) -> impl Iterator> + '_ { self.metadata.owned_resources() } /// Inserts a single buffer and its state into the resource tracker. /// /// If the resource already exists in the tracker, it will be overwritten. pub fn insert_single(&mut self, buffer: &Arc, state: BufferUses) { let index = buffer.tracker_index().as_usize(); self.allow_index(index); self.tracker_assert_in_bounds(index); unsafe { insert( None, &mut self.current_states, &mut self.metadata, index, BufferStateProvider::Direct { state }, None, ResourceMetadataProvider::Direct { resource: &Arc::downgrade(buffer), }, ) } } /// Sets the state of a single buffer. /// /// If a transition is needed to get the buffer into the given state, that transition /// is returned. No more than one transition is needed. pub fn set_single( &mut self, buffer: &Arc, state: BufferUses, ) -> Option> { let index: usize = buffer.tracker_index().as_usize(); self.tracker_assert_in_bounds(index); let start_state_provider = BufferStateProvider::Direct { state }; unsafe { barrier( &mut self.current_states, index, start_state_provider.clone(), &mut self.temp, self.ordered_uses_mask, ) }; unsafe { update(&mut self.current_states, index, start_state_provider) }; strict_assert!(self.temp.len() <= 1); self.temp.pop() } /// Sets the given state for all buffers in the given tracker. /// /// If a transition is needed to get the buffers into the needed state, /// those transitions are returned. pub fn set_from_tracker_and_drain_transitions<'a, 'b: 'a>( &'a mut self, tracker: &'a BufferTracker, snatch_guard: &'b SnatchGuard<'b>, ) -> impl Iterator> { for index in tracker.metadata.owned_indices() { self.tracker_assert_in_bounds(index); let start_state_provider = BufferStateProvider::Indirect { state: &tracker.start, }; let end_state_provider = BufferStateProvider::Indirect { state: &tracker.end, }; unsafe { barrier( &mut self.current_states, index, start_state_provider, &mut self.temp, self.ordered_uses_mask, ) }; unsafe { update(&mut self.current_states, index, end_state_provider) }; } self.temp.drain(..).map(|pending| { let buf = unsafe { tracker.metadata.get_resource_unchecked(pending.id as _) }; pending.into_hal(buf, snatch_guard) }) } } /// Source of Buffer State. #[derive(Debug, Clone)] enum BufferStateProvider<'a> { /// Get a state that was provided directly. Direct { state: BufferUses }, /// Get a state from an an array of states. Indirect { state: &'a [BufferUses] }, } impl BufferStateProvider<'_> { /// Gets the state from the provider, given a resource ID index. /// /// # Safety /// /// Index must be in bounds for the indirect source iff this is in the indirect state. #[inline(always)] unsafe fn get_state(&self, index: usize) -> BufferUses { match *self { BufferStateProvider::Direct { state } => state, BufferStateProvider::Indirect { state } => { strict_assert!(index < state.len()); *unsafe { state.get_unchecked(index) } } } } } #[inline(always)] unsafe fn insert( start_states: Option<&mut [BufferUses]>, current_states: &mut [BufferUses], resource_metadata: &mut ResourceMetadata, index: usize, start_state_provider: BufferStateProvider<'_>, end_state_provider: Option>, metadata_provider: ResourceMetadataProvider<'_, T>, ) { let new_start_state = unsafe { start_state_provider.get_state(index) }; let new_end_state = end_state_provider.map_or(new_start_state, |p| unsafe { p.get_state(index) }); // This should only ever happen with a wgpu bug, but let's just double // check that resource states don't have any conflicts. strict_assert_eq!(invalid_resource_state(new_start_state), false); strict_assert_eq!(invalid_resource_state(new_end_state), false); unsafe { if let Some(&mut ref mut start_state) = start_states { *start_state.get_unchecked_mut(index) = new_start_state; } *current_states.get_unchecked_mut(index) = new_end_state; let resource = metadata_provider.get(index); resource_metadata.insert(index, resource.clone()); } } #[inline(always)] unsafe fn merge( current_states: &mut [BufferUses], _index32: u32, index: usize, state_provider: BufferStateProvider<'_>, metadata_provider: ResourceMetadataProvider<'_, Arc>, ) -> Result<(), ResourceUsageCompatibilityError> { let current_state = unsafe { current_states.get_unchecked_mut(index) }; let new_state = unsafe { state_provider.get_state(index) }; let merged_state = *current_state | new_state; if invalid_resource_state(merged_state) { return Err(ResourceUsageCompatibilityError::from_buffer( unsafe { metadata_provider.get(index) }, *current_state, new_state, )); } *current_state = merged_state; Ok(()) } #[inline(always)] unsafe fn barrier( current_states: &mut [BufferUses], index: usize, state_provider: BufferStateProvider<'_>, barriers: &mut Vec>, ordered_uses_mask: BufferUses, ) { let current_state = unsafe { *current_states.get_unchecked(index) }; let new_state = unsafe { state_provider.get_state(index) }; if skip_barrier(current_state, ordered_uses_mask, new_state) { return; } barriers.push(PendingTransition { id: index as _, selector: (), usage: hal::StateTransition { from: current_state, to: new_state, }, }); } #[inline(always)] unsafe fn update( current_states: &mut [BufferUses], index: usize, state_provider: BufferStateProvider<'_>, ) { let current_state = unsafe { current_states.get_unchecked_mut(index) }; let new_state = unsafe { state_provider.get_state(index) }; *current_state = new_state; } ================================================ FILE: wgpu-core/src/track/metadata.rs ================================================ //! The `ResourceMetadata` type. use alloc::vec::Vec; use bit_vec::BitVec; use wgt::strict_assert; /// A set of resources, holding a `Arc` and epoch for each member. /// /// Testing for membership is fast, and iterating over members is /// reasonably fast in practice. Storage consumption is proportional /// to the largest id index of any member, not to the number of /// members, but a bit vector tracks occupancy, so iteration touches /// only occupied elements. #[derive(Debug)] pub(super) struct ResourceMetadata { /// If the resource with index `i` is a member, `owned[i]` is `true`. owned: BitVec, /// A vector holding clones of members' `T`s. resources: Vec>, } impl ResourceMetadata { pub(super) fn new() -> Self { Self { owned: BitVec::default(), resources: Vec::new(), } } pub(super) fn set_size(&mut self, size: usize) { self.resources.resize(size, None); resize_bitvec(&mut self.owned, size); } pub(super) fn clear(&mut self) { self.resources.clear(); self.owned.fill(false); } /// Ensures a given index is in bounds for all arrays and does /// sanity checks of the presence of a refcount. /// /// In release mode this function is completely empty and is removed. pub(super) fn tracker_assert_in_bounds(&self, index: usize) { strict_assert!(index < self.owned.len()); strict_assert!(index < self.resources.len()); strict_assert!(if self.contains(index) { self.resources[index].is_some() } else { true }); } /// Returns true if the tracker owns no resources. /// /// This is a O(n) operation. pub(super) fn is_empty(&self) -> bool { !self.owned.any() } /// Returns true if the set contains the resource with the given index. pub(super) fn contains(&self, index: usize) -> bool { self.owned.get(index).unwrap_or(false) } /// Returns true if the set contains the resource with the given index. /// /// # Safety /// /// The given `index` must be in bounds for this `ResourceMetadata`'s /// existing tables. See `tracker_assert_in_bounds`. #[inline(always)] pub(super) unsafe fn contains_unchecked(&self, index: usize) -> bool { unsafe { self.owned.get(index).unwrap_unchecked() } } /// Insert a resource into the set. /// /// Add the resource with the given index, epoch, and reference count to the /// set. /// /// Returns a reference to the newly inserted resource. /// (This allows avoiding a clone/reference count increase in many cases.) /// /// # Safety /// /// The given `index` must be in bounds for this `ResourceMetadata`'s /// existing tables. See `tracker_assert_in_bounds`. #[inline(always)] pub(super) unsafe fn insert(&mut self, index: usize, resource: T) -> &T { self.owned.set(index, true); let resource_dst = unsafe { self.resources.get_unchecked_mut(index) }; resource_dst.insert(resource) } /// Get the resource with the given index. /// /// # Safety /// /// The given `index` must be in bounds for this `ResourceMetadata`'s /// existing tables. See `tracker_assert_in_bounds`. #[inline(always)] pub(super) unsafe fn get_resource_unchecked(&self, index: usize) -> &T { unsafe { self.resources .get_unchecked(index) .as_ref() .unwrap_unchecked() } } /// Returns an iterator over the resources owned by `self`. pub(super) fn owned_resources(&self) -> impl Iterator + '_ { if !self.owned.is_empty() { self.tracker_assert_in_bounds(self.owned.len() - 1) }; iterate_bitvec_indices(&self.owned).map(move |index| { let resource = unsafe { self.resources.get_unchecked(index) }; resource.as_ref().unwrap() }) } /// Returns an iterator over the indices of all resources owned by `self`. pub(super) fn owned_indices(&self) -> impl Iterator + '_ { if !self.owned.is_empty() { self.tracker_assert_in_bounds(self.owned.len() - 1) }; iterate_bitvec_indices(&self.owned) } /// Remove the resource with the given index from the set. pub(super) unsafe fn remove(&mut self, index: usize) { unsafe { *self.resources.get_unchecked_mut(index) = None; } self.owned.set(index, false); } } /// A source of resource metadata. /// /// This is used to abstract over the various places /// trackers can get new resource metadata from. pub(super) enum ResourceMetadataProvider<'a, T: Clone> { /// Comes directly from explicit values. Direct { resource: &'a T }, /// Comes from another metadata tracker. Indirect { metadata: &'a ResourceMetadata }, } impl ResourceMetadataProvider<'_, T> { /// Get a reference to the resource from this. /// /// # Safety /// /// - The index must be in bounds of the metadata tracker if this uses an indirect source. #[inline(always)] pub(super) unsafe fn get(&self, index: usize) -> &T { match self { ResourceMetadataProvider::Direct { resource } => resource, ResourceMetadataProvider::Indirect { metadata } => { metadata.tracker_assert_in_bounds(index); { let resource = unsafe { metadata.resources.get_unchecked(index) }.as_ref(); unsafe { resource.unwrap_unchecked() } } } } } } /// Resizes the given bitvec to the given size. I'm not sure why this is hard to do but it is. fn resize_bitvec(vec: &mut BitVec, size: usize) { let owned_size_to_grow = size.checked_sub(vec.len()); if let Some(delta) = owned_size_to_grow { if delta != 0 { vec.grow(delta, false); } } else { vec.truncate(size); } } /// Produces an iterator that yields the indexes of all bits that are set in the bitvec. /// /// Will skip entire usize's worth of bits if they are all false. fn iterate_bitvec_indices(ownership: &BitVec) -> impl Iterator + '_ { const BITS_PER_BLOCK: usize = usize::BITS as usize; let size = ownership.len(); ownership .blocks() .enumerate() .filter(|&(_, word)| word != 0) .flat_map(move |(word_index, mut word)| { let bit_start = word_index * BITS_PER_BLOCK; let bit_end = (bit_start + BITS_PER_BLOCK).min(size); (bit_start..bit_end).filter(move |_| { let active = word & 0b1 != 0; word >>= 1; active }) }) } ================================================ FILE: wgpu-core/src/track/mod.rs ================================================ /*! Resource State and Lifetime Trackers These structures are responsible for keeping track of resource state, generating barriers where needednd making sure resources are kept alive until the trackers die. ## General Architecture Tracking is some of the hottest code in the entire codebase, so the trackers are designed to be as cache efficient as possible. They store resource state in flat vectors, storing metadata SOA style, one vector per type of metadata. A lot of the tracker code is deeply unsafe, using unchecked accesses all over to make performance as good as possible. However, for all unsafe accesses, there is a corresponding debug assert the checks if that access is valid. This helps get bugs caught fast, while still letting users not need to pay for the bounds checks. In wgpu, each resource ID includes a bitfield holding an index. Indices are allocated and re-used, so they will always be as low as reasonably possible. This allows us to use IDs to index into an array of tracking information. ## Statefulness There are two main types of trackers, stateful and stateless. Stateful trackers are for buffers and textures. They both have resource state attached to them which needs to be used to generate automatic synchronization. Because of the different requirements of buffers and textures, they have two separate tracking structures. Stateless trackers only store metadata and own the given resource. ## Use Case Within each type of tracker, the trackers are further split into 3 different use cases, Bind Group, Usage Scopend a full Tracker. Bind Group trackers are just a list of different resources, their refcount, and how they are used. Textures are used via a selector and a usage type. Buffers by just a usage type. Stateless resources don't have a usage type. Usage Scope trackers are only for stateful resources. These trackers represent a single [`UsageScope`] in the spec. When a use is added to a usage scope, it is merged with all other uses of that resource in that scope. If there is a usage conflict, merging will fail and an error will be reported. Full trackers represent a before and after state of a resource. These are used for tracking on the device and on command buffers. The before state represents the state the resource is first used as in the command buffer, the after state is the state the command buffer leaves the resource in. These double ended buffers can then be used to generate the needed transitions between command buffers. ## Dense Datastructure with Sparse Data This tracking system is based on having completely dense data, but trackers do not always contain every resource. Some resources (or even most resources) go unused in any given command buffer. So to help speed up the process of iterating through possibly thousands of resources, we use a bit vector to represent if a resource is in the buffer or not. This allows us extremely efficient memory utilizations well as being able to bail out of whole blocks of 32-64 resources with a single usize comparison with zero. In practice this means that merging partially resident buffers is extremely quick. The main advantage of this dense datastructure is that we can do merging of trackers in an extremely efficient fashion that results in us doing linear scans down a couple of buffers. CPUs and their caches absolutely eat this up. ## Stateful Resource Operations All operations on stateful trackers boil down to one of four operations: - `insert(tracker, new_state)` adds a resource with a given state to the tracker for the first time. - `merge(tracker, new_state)` merges this new state with the previous state, checking for usage conflicts. - `barrier(tracker, new_state)` compares the given state to the existing state and generates the needed barriers. - `update(tracker, new_state)` takes the given new state and overrides the old state. This allows us to compose the operations to form the various kinds of tracker merges that need to happen in the codebase. For each resource in the given merger, the following operation applies: ```text UsageScope <- Resource = insert(scope, usage) OR merge(scope, usage) UsageScope <- UsageScope = insert(scope, scope) OR merge(scope, scope) CommandBuffer <- UsageScope = insert(buffer.start, buffer.end, scope) OR barrier(buffer.end, scope) + update(buffer.end, scope) Device <- CommandBuffer = insert(device.start, device.end, buffer.start, buffer.end) OR barrier(device.end, buffer.start) + update(device.end, buffer.end) ``` [`UsageScope`]: https://gpuweb.github.io/gpuweb/#programming-model-synchronization */ mod blas; mod buffer; mod metadata; mod range; mod stateless; mod texture; use crate::{ binding_model, command, lock::{rank, Mutex}, pipeline, resource::{self, Labeled, RawResourceAccess, ResourceErrorIdent}, snatch::SnatchGuard, track::blas::BlasTracker, }; use alloc::{sync::Arc, vec::Vec}; use bitflags::Flags; use core::{fmt, mem, ops}; use thiserror::Error; pub(crate) use buffer::{ BufferBindGroupState, BufferTracker, BufferUsageScope, DeviceBufferTracker, }; use metadata::{ResourceMetadata, ResourceMetadataProvider}; pub(crate) use stateless::StatelessTracker; pub(crate) use texture::{ DeviceTextureTracker, TextureTracker, TextureTrackerSetSingle, TextureUsageScope, TextureViewBindGroupState, }; use wgt::{ error::{ErrorType, WebGpuError}, strict_assert_ne, }; #[repr(transparent)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub(crate) struct TrackerIndex(u32); impl TrackerIndex { pub fn as_usize(self) -> usize { self.0 as usize } } /// wgpu-core internally use some array-like storage for tracking resources. /// To that end, there needs to be a uniquely assigned index for each live resource /// of a certain type. This index is separate from the resource ID for various reasons: /// - There can be multiple resource IDs pointing the the same resource. /// - IDs of dead handles can be recycled while resources are internally held alive (and tracked). /// - The plan is to remove IDs in the long run /// ([#5121](https://github.com/gfx-rs/wgpu/issues/5121)). /// /// In order to produce these tracker indices, there is a shared TrackerIndexAllocator /// per resource type. Indices have the same lifetime as the internal resource they /// are associated to (alloc happens when creating the resource and free is called when /// the resource is dropped). struct TrackerIndexAllocator { unused: Vec, next_index: TrackerIndex, } impl TrackerIndexAllocator { pub fn new() -> Self { TrackerIndexAllocator { unused: Vec::new(), next_index: TrackerIndex(0), } } pub fn alloc(&mut self) -> TrackerIndex { if let Some(index) = self.unused.pop() { return index; } let index = self.next_index; self.next_index.0 += 1; index } pub fn free(&mut self, index: TrackerIndex) { self.unused.push(index); } // This is used to pre-allocate the tracker storage. pub fn size(&self) -> usize { self.next_index.0 as usize } } impl fmt::Debug for TrackerIndexAllocator { fn fmt(&self, _: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { Ok(()) } } /// See TrackerIndexAllocator. #[derive(Debug)] pub(crate) struct SharedTrackerIndexAllocator { inner: Mutex, } impl SharedTrackerIndexAllocator { pub fn new() -> Self { SharedTrackerIndexAllocator { inner: Mutex::new( rank::SHARED_TRACKER_INDEX_ALLOCATOR_INNER, TrackerIndexAllocator::new(), ), } } pub fn alloc(&self) -> TrackerIndex { self.inner.lock().alloc() } pub fn free(&self, index: TrackerIndex) { self.inner.lock().free(index); } pub fn size(&self) -> usize { self.inner.lock().size() } } pub(crate) struct TrackerIndexAllocators { pub buffers: Arc, pub textures: Arc, pub external_textures: Arc, pub samplers: Arc, pub bind_groups: Arc, pub compute_pipelines: Arc, pub render_pipelines: Arc, pub bundles: Arc, pub query_sets: Arc, pub blas_s: Arc, pub tlas_s: Arc, } impl TrackerIndexAllocators { pub fn new() -> Self { TrackerIndexAllocators { buffers: Arc::new(SharedTrackerIndexAllocator::new()), textures: Arc::new(SharedTrackerIndexAllocator::new()), external_textures: Arc::new(SharedTrackerIndexAllocator::new()), samplers: Arc::new(SharedTrackerIndexAllocator::new()), bind_groups: Arc::new(SharedTrackerIndexAllocator::new()), compute_pipelines: Arc::new(SharedTrackerIndexAllocator::new()), render_pipelines: Arc::new(SharedTrackerIndexAllocator::new()), bundles: Arc::new(SharedTrackerIndexAllocator::new()), query_sets: Arc::new(SharedTrackerIndexAllocator::new()), blas_s: Arc::new(SharedTrackerIndexAllocator::new()), tlas_s: Arc::new(SharedTrackerIndexAllocator::new()), } } } /// A structure containing all the information about a particular resource /// transition. User code should be able to generate a pipeline barrier /// based on the contents. #[derive(Debug, PartialEq)] pub(crate) struct PendingTransition { pub id: u32, pub selector: S::Selector, pub usage: hal::StateTransition, } pub(crate) type PendingTransitionList = Vec>; impl PendingTransition { /// Produce the hal barrier corresponding to the transition. pub fn into_hal<'a>( self, buf: &'a resource::Buffer, snatch_guard: &'a SnatchGuard<'a>, ) -> hal::BufferBarrier<'a, dyn hal::DynBuffer> { let buffer = buf.raw(snatch_guard).expect("Buffer is destroyed"); hal::BufferBarrier { buffer, usage: self.usage, } } } impl PendingTransition { /// Produce the hal barrier corresponding to the transition. pub fn into_hal( self, texture: &dyn hal::DynTexture, ) -> hal::TextureBarrier<'_, dyn hal::DynTexture> { // These showing up in a barrier is always a bug strict_assert_ne!(self.usage.from, wgt::TextureUses::UNKNOWN); strict_assert_ne!(self.usage.to, wgt::TextureUses::UNKNOWN); let mip_count = self.selector.mips.end - self.selector.mips.start; strict_assert_ne!(mip_count, 0); let layer_count = self.selector.layers.end - self.selector.layers.start; strict_assert_ne!(layer_count, 0); hal::TextureBarrier { texture, range: wgt::ImageSubresourceRange { aspect: wgt::TextureAspect::All, base_mip_level: self.selector.mips.start, mip_level_count: Some(mip_count), base_array_layer: self.selector.layers.start, array_layer_count: Some(layer_count), }, usage: self.usage, } } } /// The uses that a resource or subresource can be in. pub(crate) trait ResourceUses: fmt::Debug + ops::BitAnd + ops::BitOr + PartialEq + Sized + Copy { /// All flags that are exclusive. const EXCLUSIVE: Self; /// The selector used by this resource. type Selector: fmt::Debug; /// Turn the resource into a pile of bits. fn bits(self) -> u16; /// Returns true if any of the uses are exclusive. fn any_exclusive(self) -> bool; } /// Returns true if the given states violates the usage scope rule /// of any(inclusive) XOR one(exclusive) fn invalid_resource_state(state: T) -> bool { // Is power of two also means "is one bit set". We check for this as if // we're in any exclusive state, we must only be in a single state. state.any_exclusive() && !state.bits().is_power_of_two() } /// Returns true if the transition from one state to another does not require /// a barrier. fn skip_barrier(old_state: F, ordered_uses_mask: F, new_state: F) -> bool { // If the state didn't change and all the usages are ordered, the hardware // will guarantee the order of accesses, so we do not need to issue a barrier at all old_state.bits() == new_state.bits() && ordered_uses_mask.contains(old_state) } #[derive(Clone, Debug, Error)] pub enum ResourceUsageCompatibilityError { #[error("Attempted to use {res} with {invalid_use}.")] Buffer { res: ResourceErrorIdent, invalid_use: InvalidUse, }, #[error( "Attempted to use {res} (mips {mip_levels:?} layers {array_layers:?}) with {invalid_use}." )] Texture { res: ResourceErrorIdent, mip_levels: ops::Range, array_layers: ops::Range, invalid_use: InvalidUse, }, } impl WebGpuError for ResourceUsageCompatibilityError { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } } impl ResourceUsageCompatibilityError { fn from_buffer( buffer: &resource::Buffer, current_state: wgt::BufferUses, new_state: wgt::BufferUses, ) -> Self { Self::Buffer { res: buffer.error_ident(), invalid_use: InvalidUse { current_state, new_state, }, } } fn from_texture( texture: &resource::Texture, selector: wgt::TextureSelector, current_state: wgt::TextureUses, new_state: wgt::TextureUses, ) -> Self { Self::Texture { res: texture.error_ident(), mip_levels: selector.mips, array_layers: selector.layers, invalid_use: InvalidUse { current_state, new_state, }, } } } /// Pretty print helper that shows helpful descriptions of a conflicting usage. #[derive(Clone, Debug, Eq, PartialEq)] pub struct InvalidUse { current_state: T, new_state: T, } impl fmt::Display for InvalidUse { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let current = self.current_state; let new = self.new_state; let current_exclusive = current & T::EXCLUSIVE; let new_exclusive = new & T::EXCLUSIVE; let exclusive = current_exclusive | new_exclusive; // The text starts with "tried to use X resource with {self}" write!( f, "conflicting usages. Current usage {current:?} and new usage {new:?}. \ {exclusive:?} is an exclusive usage and cannot be used with any other \ usages within the usage scope (renderpass or compute dispatch)" ) } } /// All the usages that a bind group contains. The uses are not deduplicated in any way /// and may include conflicting uses. This is fully compliant by the WebGPU spec. /// /// All bind group states are sorted by their ID so that when adding to a tracker, /// they are added in the most efficient order possible (ascending order). #[derive(Debug)] pub(crate) struct BindGroupStates { pub buffers: BufferBindGroupState, pub views: TextureViewBindGroupState, pub external_textures: StatelessTracker, pub samplers: StatelessTracker, pub acceleration_structures: StatelessTracker, } impl BindGroupStates { pub fn new() -> Self { Self { buffers: BufferBindGroupState::new(), views: TextureViewBindGroupState::new(), external_textures: StatelessTracker::new(), samplers: StatelessTracker::new(), acceleration_structures: StatelessTracker::new(), } } /// Optimize the bind group states by sorting them by ID. /// /// When this list of states is merged into a tracker, the memory /// accesses will be in a constant ascending order. pub fn optimize(&mut self) { self.buffers.optimize(); // Views are stateless, however, `TextureViewBindGroupState` // is special as it will be merged with other texture trackers. self.views.optimize(); // Samplers and Tlas's are stateless and don't need to be optimized // since the tracker is never merged with any other tracker. } } /// This is a render bundle specific usage scope. It includes stateless resources /// that are not normally included in a usage scope, but are used by render bundles /// and need to be owned by the render bundles. #[derive(Debug)] pub(crate) struct RenderBundleScope { pub buffers: BufferUsageScope, pub textures: TextureUsageScope, // Don't need to track views and samplers, they are never used directly, only by bind groups. pub bind_groups: StatelessTracker, pub render_pipelines: StatelessTracker, } impl RenderBundleScope { /// Create the render bundle scope and pull the maximum IDs from the hubs. pub fn new() -> Self { Self { buffers: BufferUsageScope::default(), textures: TextureUsageScope::default(), bind_groups: StatelessTracker::new(), render_pipelines: StatelessTracker::new(), } } /// Merge the inner contents of a bind group into the render bundle tracker. /// /// Only stateful things are merged in herell other resources are owned /// indirectly by the bind group. /// /// # Safety /// /// The maximum ID given by each bind group resource must be less than the /// length of the storage given at the call to `new`. pub unsafe fn merge_bind_group( &mut self, bind_group: &BindGroupStates, ) -> Result<(), ResourceUsageCompatibilityError> { unsafe { self.buffers.merge_bind_group(&bind_group.buffers)? }; unsafe { self.textures.merge_bind_group(&bind_group.views)? }; Ok(()) } } /// A pool for storing the memory used by [`UsageScope`]s. We take and store this memory when the /// scope is dropped to avoid reallocating. The memory required only grows and allocation cost is /// significant when a large number of resources have been used. pub(crate) type UsageScopePool = Mutex>; /// A usage scope tracker. Only needs to store stateful resources as stateless /// resources cannot possibly have a usage conflict. #[derive(Debug)] pub(crate) struct UsageScope<'a> { pub pool: &'a UsageScopePool, pub buffers: BufferUsageScope, pub textures: TextureUsageScope, } impl<'a> Drop for UsageScope<'a> { fn drop(&mut self) { // clear vecs and push into pool self.buffers.clear(); self.textures.clear(); self.pool .lock() .push((mem::take(&mut self.buffers), mem::take(&mut self.textures))); } } impl UsageScope<'static> { pub fn new_pooled<'d>( pool: &'d UsageScopePool, tracker_indices: &TrackerIndexAllocators, ordered_buffer_usages: wgt::BufferUses, ordered_texture_usages: wgt::TextureUses, ) -> UsageScope<'d> { let pooled = pool.lock().pop().unwrap_or_default(); let mut scope = UsageScope::<'d> { pool, buffers: pooled.0, textures: pooled.1, }; scope.buffers.set_size(tracker_indices.buffers.size()); scope.buffers.set_ordered_uses_mask(ordered_buffer_usages); scope.textures.set_size(tracker_indices.textures.size()); scope.textures.set_ordered_uses_mask(ordered_texture_usages); scope } } impl<'a> UsageScope<'a> { /// Merge the inner contents of a bind group into the usage scope. /// /// Only stateful things are merged in herell other resources are owned /// indirectly by the bind group. /// /// # Safety /// /// The maximum ID given by each bind group resource must be less than the /// length of the storage given at the call to `new`. pub unsafe fn merge_bind_group( &mut self, bind_group: &BindGroupStates, ) -> Result<(), ResourceUsageCompatibilityError> { unsafe { self.buffers.merge_bind_group(&bind_group.buffers)?; self.textures.merge_bind_group(&bind_group.views)?; } Ok(()) } /// Merge the inner contents of a bind group into the usage scope. /// /// Only stateful things are merged in herell other resources are owned /// indirectly by a bind group or are merged directly into the command buffer tracker. /// /// # Safety /// /// The maximum ID given by each bind group resource must be less than the /// length of the storage given at the call to `new`. pub unsafe fn merge_render_bundle( &mut self, render_bundle: &RenderBundleScope, ) -> Result<(), ResourceUsageCompatibilityError> { self.buffers.merge_usage_scope(&render_bundle.buffers)?; self.textures.merge_usage_scope(&render_bundle.textures)?; Ok(()) } } /// A tracker used by Device. pub(crate) struct DeviceTracker { pub buffers: DeviceBufferTracker, pub textures: DeviceTextureTracker, } impl DeviceTracker { pub fn new( ordered_buffer_usages: wgt::BufferUses, ordered_texture_usages: wgt::TextureUses, ) -> Self { Self { buffers: DeviceBufferTracker::new(ordered_buffer_usages), textures: DeviceTextureTracker::new(ordered_texture_usages), } } } /// A full double sided tracker used by CommandBuffers. pub(crate) struct Tracker { /// Buffers used within this command buffer. /// /// For compute passes, this only includes buffers actually used by the /// pipeline (contrast with the `bind_groups` member). pub buffers: BufferTracker, /// Textures used within this command buffer. /// /// For compute passes, this only includes textures actually used by the /// pipeline (contrast with the `bind_groups` member). pub textures: TextureTracker, pub blas_s: BlasTracker, pub tlas_s: StatelessTracker, pub views: StatelessTracker, /// Contains all bind groups that were passed in any call to /// `set_bind_group` on the encoder. /// /// WebGPU requires that submission fails if any resource in any of these /// bind groups is destroyed, even if the resource is not actually used by /// the pipeline (e.g. because the pipeline does not use the bound slot, or /// because the bind group was replaced by a subsequent call to /// `set_bind_group`). pub bind_groups: StatelessTracker, pub compute_pipelines: StatelessTracker, pub render_pipelines: StatelessTracker, pub bundles: StatelessTracker, pub query_sets: StatelessTracker, } impl Tracker { pub fn new( ordered_buffer_usages: wgt::BufferUses, ordered_texture_usages: wgt::TextureUses, ) -> Self { Self { buffers: BufferTracker::new(ordered_buffer_usages), textures: TextureTracker::new(ordered_texture_usages), blas_s: BlasTracker::new(), tlas_s: StatelessTracker::new(), views: StatelessTracker::new(), bind_groups: StatelessTracker::new(), compute_pipelines: StatelessTracker::new(), render_pipelines: StatelessTracker::new(), bundles: StatelessTracker::new(), query_sets: StatelessTracker::new(), } } /// Iterates through all resources in the given bind group and adopts /// the state given for those resources in the UsageScope. It also /// removes all touched resources from the usage scope. /// /// If a transition is needed to get the resources into the needed /// state, those transitions are stored within the tracker. A /// subsequent call to [`BufferTracker::drain_transitions`] or /// [`TextureTracker::drain_transitions`] is needed to get those transitions. /// /// This is a really funky method used by Compute Passes to generate /// barriers after a call to dispatch without needing to iterate /// over all elements in the usage scope. We use each the /// bind group as a source of which IDs to look at. The bind groups /// must have first been added to the usage scope. /// /// Only stateful things are merged in here, all other resources are owned /// indirectly by the bind group. /// /// # Panics /// /// If a resource in the `bind_group` is not found in the usage scope. pub fn set_and_remove_from_usage_scope_sparse( &mut self, scope: &mut UsageScope, bind_group: &BindGroupStates, ) { self.buffers.set_and_remove_from_usage_scope_sparse( &mut scope.buffers, bind_group.buffers.used_tracker_indices(), ); self.textures .set_and_remove_from_usage_scope_sparse(&mut scope.textures, &bind_group.views); } } ================================================ FILE: wgpu-core/src/track/range.rs ================================================ [File too large to display: 6.5 KB] ================================================ FILE: wgpu-core/src/track/stateless.rs ================================================ [File too large to display: 975 B] ================================================ FILE: wgpu-core/src/track/texture.rs ================================================ //! Texture Trackers //! //! Texture trackers are significantly more complicated than //! the buffer trackers because textures can be in a "complex" //! state where each individual subresource can potentially be //! in a different state from every other subtresource. These //! complex states are stored separately from the simple states //! because they are signifignatly more difficult to track and //! most resources spend the vast majority of their lives in //! simple states. //! //! There are two special texture usages: `UNKNOWN` and `UNINITIALIZED`. //! - `UNKNOWN` is only used in complex states and is used to signify //! that the complex state does not know anything about those subresources. //! It cannot leak into transitions, it is invalid to transition into UNKNOWN //! state. //! - `UNINITIALIZED` is used in both simple and complex states to mean the texture //! is known to be in some undefined state. Any transition away from UNINITIALIZED //! will treat the contents as junk. use super::{range::RangedStates, PendingTransition, PendingTransitionList}; use crate::{ resource::{RawResourceAccess, Texture, TextureInner, TextureView, Trackable}, snatch::SnatchGuard, track::{ invalid_resource_state, skip_barrier, ResourceMetadata, ResourceMetadataProvider, ResourceUsageCompatibilityError, ResourceUses, }, }; use hal::TextureBarrier; use arrayvec::ArrayVec; use naga::FastHashMap; use wgt::{strict_assert, strict_assert_eq, TextureSelector, TextureUses}; use alloc::{ sync::{Arc, Weak}, vec::{Drain, Vec}, }; use core::iter; impl ResourceUses for TextureUses { const EXCLUSIVE: Self = Self::EXCLUSIVE; type Selector = TextureSelector; fn bits(self) -> u16 { Self::bits(&self) } fn any_exclusive(self) -> bool { self.intersects(Self::EXCLUSIVE) } } /// Represents the complex state of textures where every subresource is potentially /// in a different state. #[derive(Clone, Debug, Default, PartialEq)] struct ComplexTextureState { mips: ArrayVec, { hal::MAX_MIP_LEVELS as usize }>, } impl ComplexTextureState { /// Creates complex texture state for the given sizes. /// /// This state will be initialized with the UNKNOWN state, a special state /// which means the trakcer knows nothing about the state. fn new(mip_level_count: u32, array_layer_count: u32) -> Self { Self { mips: iter::repeat_with(|| { RangedStates::from_range(0..array_layer_count, TextureUses::UNKNOWN) }) .take(mip_level_count as usize) .collect(), } } /// Initialize a complex state from a selector representing the full size of the texture /// and an iterator of a selector and a texture use, specifying a usage for a specific /// set of subresources. /// /// [`Self::to_selector_state_iter`] can be used to create such an iterator. /// /// # Safety /// /// All selectors in the iterator must be inside of the full_range selector. /// /// The full range selector must have mips and layers start at 0. unsafe fn from_selector_state_iter( full_range: TextureSelector, state_iter: impl Iterator, ) -> Self { strict_assert_eq!(full_range.layers.start, 0); strict_assert_eq!(full_range.mips.start, 0); let mut complex = ComplexTextureState::new(full_range.mips.len() as u32, full_range.layers.len() as u32); for (selector, desired_state) in state_iter { strict_assert!(selector.layers.end <= full_range.layers.end); strict_assert!(selector.mips.end <= full_range.mips.end); // This should only ever happen with a wgpu bug, but let's just double // check that resource states don't have any conflicts. strict_assert_eq!(invalid_resource_state(desired_state), false); let mips = selector.mips.start as usize..selector.mips.end as usize; for mip in unsafe { complex.mips.get_unchecked_mut(mips) } { for &mut (_, ref mut state) in mip.isolate(&selector.layers, TextureUses::UNKNOWN) { *state = desired_state; } } } complex } /// Convert a complex state into an iterator over all states stored. /// /// [`Self::from_selector_state_iter`] can be used to consume such an iterator. fn to_selector_state_iter( &self, ) -> impl Iterator + Clone + '_ { self.mips.iter().enumerate().flat_map(|(mip, inner)| { let mip = mip as u32; { inner.iter().map(move |&(ref layers, inner)| { ( TextureSelector { mips: mip..mip + 1, layers: layers.clone(), }, inner, ) }) } }) } } /// Stores a bind group's texture views + their usages (within the bind group). #[derive(Debug)] pub(crate) struct TextureViewBindGroupState { views: Vec<(Arc, TextureUses)>, } impl TextureViewBindGroupState { pub fn new() -> Self { Self { views: Vec::new() } } /// Optimize the texture bind group state by sorting it by ID. /// /// When this list of states is merged into a tracker, the memory /// accesses will be in a constant ascending order. pub(crate) fn optimize(&mut self) { self.views .sort_unstable_by_key(|(view, _)| view.parent.tracker_index()); } /// Adds the given resource with the given state. pub fn insert_single(&mut self, view: Arc, usage: TextureUses) { self.views.push((view, usage)); } } /// Container for corresponding simple and complex texture states. #[derive(Debug)] pub(crate) struct TextureStateSet { simple: Vec, complex: FastHashMap, } impl TextureStateSet { fn new() -> Self { Self { simple: Vec::new(), complex: FastHashMap::default(), } } fn clear(&mut self) { self.simple.clear(); self.complex.clear(); } fn set_size(&mut self, size: usize) { self.simple.resize(size, TextureUses::UNINITIALIZED); } fn size(&self) -> usize { self.simple.len() } /// SAFETY: `index` must be in bounds. unsafe fn get_unchecked( &self, index: usize, ) -> SingleOrManyStates { let simple = unsafe { *self.simple.get_unchecked(index) }; if simple == TextureUses::COMPLEX { SingleOrManyStates::Many(unsafe { self.complex.get(&index).unwrap_unchecked() }) } else { SingleOrManyStates::Single(simple) } } /// # Safety /// /// The `index` must be in bounds. unsafe fn get_mut_unchecked( &mut self, index: usize, ) -> SingleOrManyStates<&mut TextureUses, &mut ComplexTextureState> { let simple = unsafe { self.simple.get_unchecked_mut(index) }; if *simple == TextureUses::COMPLEX { SingleOrManyStates::Many(unsafe { self.complex.get_mut(&index).unwrap_unchecked() }) } else { SingleOrManyStates::Single(simple) } } /// # Safety /// /// The `index` must be in bounds. unsafe fn insert_simple_unchecked(&mut self, index: usize, simple: TextureUses) { unsafe { *self.simple.get_unchecked_mut(index) = simple }; } /// # Safety /// /// The `index` must be in bounds. unsafe fn insert_complex_unchecked(&mut self, index: usize, complex: ComplexTextureState) { unsafe { *self.simple.get_unchecked_mut(index) = TextureUses::COMPLEX }; self.complex.insert(index, complex); } /// # Safety /// /// The `index` must be in bounds. unsafe fn make_simple_unchecked(&mut self, index: usize, simple: TextureUses) { unsafe { *self.simple.get_unchecked_mut(index) = simple }; unsafe { self.complex.remove(&index).unwrap_unchecked() }; } /// # Safety /// /// The `index` must be in bounds. unsafe fn make_complex_unchecked(&mut self, index: usize, complex: ComplexTextureState) { unsafe { *self.simple.get_unchecked_mut(index) = TextureUses::COMPLEX }; self.complex.insert(index, complex); } fn tracker_assert_in_bounds(&self, index: usize) { strict_assert!(index < self.size()); } } /// Stores all texture state within a single usage scope. #[derive(Debug)] pub(crate) struct TextureUsageScope { set: TextureStateSet, metadata: ResourceMetadata>, ordered_uses_mask: TextureUses, } impl Default for TextureUsageScope { fn default() -> Self { Self { set: TextureStateSet::new(), metadata: ResourceMetadata::new(), ordered_uses_mask: TextureUses::empty(), } } } impl TextureUsageScope { fn tracker_assert_in_bounds(&self, index: usize) { self.metadata.tracker_assert_in_bounds(index); self.set.tracker_assert_in_bounds(index); } pub fn clear(&mut self) { self.set.clear(); self.metadata.clear(); } /// Sets the size of all the vectors inside the tracker. /// /// Must be called with the highest possible Texture ID before /// all unsafe functions are called. pub fn set_size(&mut self, size: usize) { self.set.set_size(size); self.metadata.set_size(size); } pub fn set_ordered_uses_mask(&mut self, ordered_uses_mask: TextureUses) { self.ordered_uses_mask = ordered_uses_mask; } /// Returns true if the tracker owns no resources. /// /// This is a O(n) operation. pub(crate) fn is_empty(&self) -> bool { self.metadata.is_empty() } /// Merge the list of texture states in the given usage scope into this UsageScope. /// /// If any of the resulting states is invalid, stops the merge and returns a usage /// conflict with the details of the invalid state. /// /// If the given tracker uses IDs higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. pub fn merge_usage_scope( &mut self, scope: &Self, ) -> Result<(), ResourceUsageCompatibilityError> { let incoming_size = scope.set.size(); if incoming_size > self.set.size() { self.set_size(incoming_size); } for index in scope.metadata.owned_indices() { self.tracker_assert_in_bounds(index); scope.tracker_assert_in_bounds(index); let texture_selector = unsafe { &scope.metadata.get_resource_unchecked(index).full_range }; unsafe { insert_or_merge( texture_selector, &mut self.set, &mut self.metadata, index, TextureStateProvider::TextureSet { set: &scope.set }, ResourceMetadataProvider::Indirect { metadata: &scope.metadata, }, )? }; } Ok(()) } /// Merge the list of texture states in the given bind group into this usage scope. /// /// If any of the resulting states is invalid, stops the merge and returns a usage /// conflict with the details of the invalid state. /// /// Because bind groups do not check if the union of all their states is valid, /// this method is allowed to return Err on the first bind group bound. /// /// # Safety /// /// [`Self::set_size`] must be called with the maximum possible Buffer ID before this /// method is called. pub unsafe fn merge_bind_group( &mut self, bind_group: &TextureViewBindGroupState, ) -> Result<(), ResourceUsageCompatibilityError> { for (view, usage) in bind_group.views.iter() { unsafe { self.merge_single(&view.parent, Some(view.selector.clone()), *usage)? }; } Ok(()) } /// Merge a single state into the UsageScope. /// /// If the resulting state is invalid, returns a usage /// conflict with the details of the invalid state. /// /// # Safety /// /// Unlike other trackers whose merge_single is safe, this method is only /// called where there is already other unsafe tracking functions active, /// so we can prove this unsafe "for free". /// /// [`Self::set_size`] must be called with the maximum possible Buffer ID before this /// method is called. pub unsafe fn merge_single( &mut self, texture: &Arc, selector: Option, new_state: TextureUses, ) -> Result<(), ResourceUsageCompatibilityError> { let index = texture.tracker_index().as_usize(); self.tracker_assert_in_bounds(index); let texture_selector = &texture.full_range; unsafe { insert_or_merge( texture_selector, &mut self.set, &mut self.metadata, index, TextureStateProvider::from_option(selector, new_state), ResourceMetadataProvider::Direct { resource: texture }, )? }; Ok(()) } } pub(crate) trait TextureTrackerSetSingle { fn set_single( &mut self, texture: &Arc, selector: TextureSelector, new_state: TextureUses, ) -> Drain<'_, PendingTransition>; } /// Stores all texture state within a command buffer. pub(crate) struct TextureTracker { start_set: TextureStateSet, end_set: TextureStateSet, metadata: ResourceMetadata>, temp: Vec>, ordered_uses_mask: TextureUses, } impl TextureTracker { pub fn new(ordered_uses_mask: TextureUses) -> Self { Self { start_set: TextureStateSet::new(), end_set: TextureStateSet::new(), metadata: ResourceMetadata::new(), temp: Vec::new(), ordered_uses_mask, } } fn tracker_assert_in_bounds(&self, index: usize) { self.metadata.tracker_assert_in_bounds(index); self.start_set.tracker_assert_in_bounds(index); self.end_set.tracker_assert_in_bounds(index); } /// Sets the size of all the vectors inside the tracker. /// /// Must be called with the highest possible Texture ID before /// all unsafe functions are called. pub fn set_size(&mut self, size: usize) { self.start_set.set_size(size); self.end_set.set_size(size); self.metadata.set_size(size); } /// Extend the vectors to let the given index be valid. fn allow_index(&mut self, index: usize) { if index >= self.start_set.size() { self.set_size(index + 1); } } /// Returns true if the tracker owns the given texture. pub fn contains(&self, texture: &Texture) -> bool { self.metadata.contains(texture.tracker_index().as_usize()) } /// Returns a list of all textures tracked. pub fn used_resources(&self) -> impl Iterator> + '_ { self.metadata.owned_resources() } /// Drain all currently pending transitions. pub fn drain_transitions<'a>( &'a mut self, snatch_guard: &'a SnatchGuard<'a>, ) -> (PendingTransitionList, Vec>) { let mut textures = Vec::new(); let transitions = self .temp .drain(..) .inspect(|pending| { let tex = unsafe { self.metadata.get_resource_unchecked(pending.id as _) }; textures.push(tex.inner.get(snatch_guard)); }) .collect(); (transitions, textures) } /// Sets the state of a single texture. /// /// If a transition is needed to get the texture into the given state, that transition /// is returned. /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. pub fn set_single( &mut self, texture: &Arc, selector: TextureSelector, new_state: TextureUses, ) -> Drain<'_, PendingTransition> { let index = texture.tracker_index().as_usize(); self.allow_index(index); self.tracker_assert_in_bounds(index); unsafe { insert_or_barrier_update( &texture.full_range, Some(&mut self.start_set), &mut self.end_set, &mut self.metadata, index, TextureStateProvider::Selector { selector, state: new_state, }, None, ResourceMetadataProvider::Direct { resource: texture }, &mut self.temp, self.ordered_uses_mask, ) } self.temp.drain(..) } /// Sets the given state for all texture in the given tracker. /// /// If a transition is needed to get the texture into the needed state, /// those transitions are stored within the tracker. A subsequent /// call to [`Self::drain_transitions`] is needed to get those transitions. /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. pub fn set_from_tracker(&mut self, tracker: &Self) { let incoming_size = tracker.start_set.size(); if incoming_size > self.start_set.size() { self.set_size(incoming_size); } for index in tracker.metadata.owned_indices() { self.tracker_assert_in_bounds(index); tracker.tracker_assert_in_bounds(index); unsafe { let texture_selector = &tracker.metadata.get_resource_unchecked(index).full_range; insert_or_barrier_update( texture_selector, Some(&mut self.start_set), &mut self.end_set, &mut self.metadata, index, TextureStateProvider::TextureSet { set: &tracker.start_set, }, Some(TextureStateProvider::TextureSet { set: &tracker.end_set, }), ResourceMetadataProvider::Indirect { metadata: &tracker.metadata, }, &mut self.temp, self.ordered_uses_mask, ); } } } /// Sets the given state for all textures in the given UsageScope. /// /// If a transition is needed to get the textures into the needed state, /// those transitions are stored within the tracker. A subsequent /// call to [`Self::drain_transitions`] is needed to get those transitions. /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. pub fn set_from_usage_scope(&mut self, scope: &TextureUsageScope) { let incoming_size = scope.set.size(); if incoming_size > self.start_set.size() { self.set_size(incoming_size); } for index in scope.metadata.owned_indices() { self.tracker_assert_in_bounds(index); scope.tracker_assert_in_bounds(index); unsafe { let texture_selector = &scope.metadata.get_resource_unchecked(index).full_range; insert_or_barrier_update( texture_selector, Some(&mut self.start_set), &mut self.end_set, &mut self.metadata, index, TextureStateProvider::TextureSet { set: &scope.set }, None, ResourceMetadataProvider::Indirect { metadata: &scope.metadata, }, &mut self.temp, self.ordered_uses_mask, ); } } } /// Iterates through all textures in the given bind group and adopts /// the state given for those textures in the UsageScope. It also /// removes all touched textures from the usage scope. /// /// If a transition is needed to get the textures into the needed state, /// those transitions are stored within the tracker. A subsequent /// call to [`Self::drain_transitions`] is needed to get those transitions. /// /// This is a really funky method used by Compute Passes to generate /// barriers after a call to dispatch without needing to iterate /// over all elements in the usage scope. We use each the /// bind group as a source of which IDs to look at. The bind groups /// must have first been added to the usage scope. /// /// # Panics /// /// If a resource in `bind_group_state` is not found in the usage scope. pub fn set_and_remove_from_usage_scope_sparse( &mut self, scope: &mut TextureUsageScope, bind_group_state: &TextureViewBindGroupState, ) { let incoming_size = scope.set.size(); if incoming_size > self.start_set.size() { self.set_size(incoming_size); } for (view, _) in bind_group_state.views.iter() { let index = view.parent.tracker_index().as_usize(); scope.tracker_assert_in_bounds(index); if unsafe { !scope.metadata.contains_unchecked(index) } { continue; } let texture_selector = &view.parent.full_range; // SAFETY: we checked that the index is in bounds for the scope, and // called `set_size` to ensure it is valid for `self`. unsafe { insert_or_barrier_update( texture_selector, Some(&mut self.start_set), &mut self.end_set, &mut self.metadata, index, TextureStateProvider::TextureSet { set: &scope.set }, None, ResourceMetadataProvider::Indirect { metadata: &scope.metadata, }, &mut self.temp, self.ordered_uses_mask, ) }; unsafe { scope.metadata.remove(index) }; } } } impl TextureTrackerSetSingle for TextureTracker { fn set_single( &mut self, texture: &Arc, selector: TextureSelector, new_state: TextureUses, ) -> Drain<'_, PendingTransition> { self.set_single(texture, selector, new_state) } } /// Stores all texture state within a device. pub(crate) struct DeviceTextureTracker { current_state_set: TextureStateSet, metadata: ResourceMetadata>, temp: Vec>, ordered_uses_mask: TextureUses, } impl DeviceTextureTracker { pub fn new(ordered_uses_mask: TextureUses) -> Self { Self { current_state_set: TextureStateSet::new(), metadata: ResourceMetadata::new(), temp: Vec::new(), ordered_uses_mask, } } fn tracker_assert_in_bounds(&self, index: usize) { self.metadata.tracker_assert_in_bounds(index); self.current_state_set.tracker_assert_in_bounds(index); } /// Extend the vectors to let the given index be valid. fn allow_index(&mut self, index: usize) { if index >= self.current_state_set.size() { self.current_state_set.set_size(index + 1); self.metadata.set_size(index + 1); } } /// Returns a list of all textures tracked. pub fn used_resources(&self) -> impl Iterator> + '_ { self.metadata.owned_resources() } /// Inserts a single texture and a state into the resource tracker. /// /// If the resource already exists in the tracker, it will be overwritten. pub fn insert_single(&mut self, texture: &Arc, state: TextureUses) { let index = texture.tracker_index().as_usize(); self.allow_index(index); self.tracker_assert_in_bounds(index); unsafe { insert( None, None, &mut self.current_state_set, &mut self.metadata, index, TextureStateProvider::KnownSingle { state }, None, ResourceMetadataProvider::Direct { resource: &Arc::downgrade(texture), }, ) }; } /// Sets the state of a single texture. /// /// If a transition is needed to get the texture into the given state, that transition /// is returned. pub fn set_single( &mut self, texture: &Arc, selector: TextureSelector, new_state: TextureUses, ) -> Drain<'_, PendingTransition> { let index = texture.tracker_index().as_usize(); self.allow_index(index); self.tracker_assert_in_bounds(index); let start_state_provider = TextureStateProvider::Selector { selector, state: new_state, }; unsafe { barrier( &texture.full_range, &self.current_state_set, index, start_state_provider.clone(), &mut self.temp, self.ordered_uses_mask, ) }; unsafe { update( &texture.full_range, None, &mut self.current_state_set, index, start_state_provider, ) }; self.temp.drain(..) } /// Sets the given state for all texture in the given tracker. /// /// If a transition is needed to get the texture into the needed state, /// those transitions are returned. pub fn set_from_tracker_and_drain_transitions<'a, 'b: 'a>( &'a mut self, tracker: &'a TextureTracker, snatch_guard: &'b SnatchGuard<'b>, ) -> impl Iterator> { for index in tracker.metadata.owned_indices() { self.tracker_assert_in_bounds(index); let start_state_provider = TextureStateProvider::TextureSet { set: &tracker.start_set, }; let end_state_provider = TextureStateProvider::TextureSet { set: &tracker.end_set, }; unsafe { let texture_selector = &tracker.metadata.get_resource_unchecked(index).full_range; barrier( texture_selector, &self.current_state_set, index, start_state_provider, &mut self.temp, self.ordered_uses_mask, ); update( texture_selector, None, &mut self.current_state_set, index, end_state_provider, ); } } self.temp.drain(..).map(|pending| { let tex = unsafe { tracker.metadata.get_resource_unchecked(pending.id as _) }; let tex = tex.try_raw(snatch_guard).unwrap(); pending.into_hal(tex) }) } /// Sets the given state for all textures in the given UsageScope. /// /// If a transition is needed to get the textures into the needed state, /// those transitions are returned. pub fn set_from_usage_scope_and_drain_transitions<'a, 'b: 'a>( &'a mut self, scope: &'a TextureUsageScope, snatch_guard: &'b SnatchGuard<'b>, ) -> impl Iterator> { for index in scope.metadata.owned_indices() { self.tracker_assert_in_bounds(index); let start_state_provider = TextureStateProvider::TextureSet { set: &scope.set }; unsafe { let texture_selector = &scope.metadata.get_resource_unchecked(index).full_range; barrier( texture_selector, &self.current_state_set, index, start_state_provider.clone(), &mut self.temp, self.ordered_uses_mask, ); update( texture_selector, None, &mut self.current_state_set, index, start_state_provider, ); } } self.temp.drain(..).map(|pending| { let tex = unsafe { scope.metadata.get_resource_unchecked(pending.id as _) }; let tex = tex.try_raw(snatch_guard).unwrap(); pending.into_hal(tex) }) } } impl TextureTrackerSetSingle for DeviceTextureTracker { fn set_single( &mut self, texture: &Arc, selector: TextureSelector, new_state: TextureUses, ) -> Drain<'_, PendingTransition> { self.set_single(texture, selector, new_state) } } /// An iterator adapter that can store two different iterator types. #[derive(Clone)] enum EitherIter { Left(L), Right(R), } impl Iterator for EitherIter where L: Iterator, R: Iterator, { type Item = D; fn next(&mut self) -> Option { match *self { EitherIter::Left(ref mut inner) => inner.next(), EitherIter::Right(ref mut inner) => inner.next(), } } } /// Container that signifies storing both different things /// if there is a single state or many different states /// involved in the operation. #[derive(Debug, Clone)] enum SingleOrManyStates { Single(S), Many(M), } /// A source of texture state. #[derive(Clone)] enum TextureStateProvider<'a> { /// Comes directly from a single state. KnownSingle { state: TextureUses }, /// Comes from a selector and a single state. Selector { selector: TextureSelector, state: TextureUses, }, /// Comes from another texture set. TextureSet { set: &'a TextureStateSet }, } impl<'a> TextureStateProvider<'a> { /// Convenience function turning `Option` into this enum. fn from_option(selector: Option, state: TextureUses) -> Self { match selector { Some(selector) => Self::Selector { selector, state }, None => Self::KnownSingle { state }, } } /// Get the state provided by this. /// /// # Panics /// /// Panics if texture_selector is None and this uses a Selector source. /// /// # Safety /// /// - The index must be in bounds of the state set if this uses an TextureSet source. #[inline(always)] unsafe fn get_state( self, texture_selector: Option<&TextureSelector>, index: usize, ) -> SingleOrManyStates< TextureUses, impl Iterator + Clone + 'a, > { match self { TextureStateProvider::KnownSingle { state } => SingleOrManyStates::Single(state), TextureStateProvider::Selector { selector, state } => { // We check if the selector given is actually for the full resource, // and if it is we promote to a simple state. This allows upstream // code to specify selectors willy nilly, and all that are really // single states are promoted here. if *texture_selector.unwrap() == selector { SingleOrManyStates::Single(state) } else { SingleOrManyStates::Many(EitherIter::Left(iter::once((selector, state)))) } } TextureStateProvider::TextureSet { set } => match unsafe { set.get_unchecked(index) } { SingleOrManyStates::Single(single) => SingleOrManyStates::Single(single), SingleOrManyStates::Many(complex) => { SingleOrManyStates::Many(EitherIter::Right(complex.to_selector_state_iter())) } }, } } } /// Does an insertion operation if the index isn't tracked /// in the current metadata, otherwise merges the given state /// with the current state. If the merging would cause /// a conflict, returns that usage conflict. /// /// # Safety /// /// Indexes must be valid indexes into all arrays passed in /// to this function, either directly or via metadata or provider structs. #[inline(always)] unsafe fn insert_or_merge( texture_selector: &TextureSelector, current_state_set: &mut TextureStateSet, resource_metadata: &mut ResourceMetadata>, index: usize, state_provider: TextureStateProvider<'_>, metadata_provider: ResourceMetadataProvider<'_, Arc>, ) -> Result<(), ResourceUsageCompatibilityError> { let currently_owned = unsafe { resource_metadata.contains_unchecked(index) }; if !currently_owned { unsafe { insert( Some(texture_selector), None, current_state_set, resource_metadata, index, state_provider, None, metadata_provider, ) }; return Ok(()); } unsafe { merge( texture_selector, current_state_set, index, state_provider, metadata_provider, ) } } /// If the resource isn't tracked /// - Inserts the given resource. /// - Uses the `start_state_provider` to populate `start_states` /// - Uses either `end_state_provider` or `start_state_provider` /// to populate `current_states`. /// /// If the resource is tracked /// - Inserts barriers from the state in `current_states` /// to the state provided by `start_state_provider`. /// - Updates the `current_states` with either the state from /// `end_state_provider` or `start_state_provider`. /// /// Any barriers are added to the barrier vector. /// /// # Safety /// /// Indexes must be valid indexes into all arrays passed in /// to this function, either directly or via metadata or provider structs. #[inline(always)] unsafe fn insert_or_barrier_update( texture_selector: &TextureSelector, start_state: Option<&mut TextureStateSet>, current_state_set: &mut TextureStateSet, resource_metadata: &mut ResourceMetadata>, index: usize, start_state_provider: TextureStateProvider<'_>, end_state_provider: Option>, metadata_provider: ResourceMetadataProvider<'_, Arc>, barriers: &mut Vec>, ordered_uses_mask: TextureUses, ) { let currently_owned = unsafe { resource_metadata.contains_unchecked(index) }; if !currently_owned { unsafe { insert( Some(texture_selector), start_state, current_state_set, resource_metadata, index, start_state_provider, end_state_provider, metadata_provider, ) }; return; } let update_state_provider = end_state_provider.unwrap_or_else(|| start_state_provider.clone()); unsafe { barrier( texture_selector, current_state_set, index, start_state_provider, barriers, ordered_uses_mask, ) }; unsafe { update( texture_selector, start_state, current_state_set, index, update_state_provider, ) }; } #[inline(always)] unsafe fn insert( texture_selector: Option<&TextureSelector>, start_state: Option<&mut TextureStateSet>, end_state: &mut TextureStateSet, resource_metadata: &mut ResourceMetadata, index: usize, start_state_provider: TextureStateProvider<'_>, end_state_provider: Option>, metadata_provider: ResourceMetadataProvider<'_, T>, ) { let start_layers = unsafe { start_state_provider.get_state(texture_selector, index) }; match start_layers { SingleOrManyStates::Single(state) => { // This should only ever happen with a wgpu bug, but let's just double // check that resource states don't have any conflicts. strict_assert_eq!(invalid_resource_state(state), false); if let Some(start_state) = start_state { unsafe { start_state.insert_simple_unchecked(index, state) }; } // We only need to insert ourselves the end state if there is no end state provider. if end_state_provider.is_none() { unsafe { end_state.insert_simple_unchecked(index, state) }; } } SingleOrManyStates::Many(state_iter) => { let full_range = texture_selector.unwrap().clone(); let complex = unsafe { ComplexTextureState::from_selector_state_iter(full_range, state_iter) }; if let Some(start_state) = start_state { unsafe { start_state.insert_complex_unchecked(index, complex.clone()) }; } // We only need to insert ourselves the end state if there is no end state provider. if end_state_provider.is_none() { unsafe { end_state.insert_complex_unchecked(index, complex) }; } } } if let Some(end_state_provider) = end_state_provider { match unsafe { end_state_provider.get_state(texture_selector, index) } { SingleOrManyStates::Single(state) => { // This should only ever happen with a wgpu bug, but let's just double // check that resource states don't have any conflicts. strict_assert_eq!(invalid_resource_state(state), false); // We only need to insert into the end, as there is guaranteed to be // a start state provider. unsafe { end_state.insert_simple_unchecked(index, state) }; } SingleOrManyStates::Many(state_iter) => { let full_range = texture_selector.unwrap().clone(); let complex = unsafe { ComplexTextureState::from_selector_state_iter(full_range, state_iter) }; // We only need to insert into the end, as there is guaranteed to be // a start state provider. unsafe { end_state.insert_complex_unchecked(index, complex) }; } } } unsafe { let resource = metadata_provider.get(index); resource_metadata.insert(index, resource.clone()); } } #[inline(always)] unsafe fn merge( texture_selector: &TextureSelector, current_state_set: &mut TextureStateSet, index: usize, state_provider: TextureStateProvider<'_>, metadata_provider: ResourceMetadataProvider<'_, Arc>, ) -> Result<(), ResourceUsageCompatibilityError> { let current_state = unsafe { current_state_set.get_mut_unchecked(index) }; let new_state = unsafe { state_provider.get_state(Some(texture_selector), index) }; match (current_state, new_state) { (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Single(new_simple)) => { let merged_state = *current_simple | new_simple; if invalid_resource_state(merged_state) { return Err(ResourceUsageCompatibilityError::from_texture( unsafe { metadata_provider.get(index) }, texture_selector.clone(), *current_simple, new_simple, )); } *current_simple = merged_state; } (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Many(new_many)) => { // Because we are now demoting this simple state to a complex state, // we actually need to make a whole new complex state for us to use // as there wasn't one before. let mut new_complex = unsafe { ComplexTextureState::from_selector_state_iter( texture_selector.clone(), iter::once((texture_selector.clone(), *current_simple)), ) }; for (selector, new_state) in new_many { let merged_state = *current_simple | new_state; if invalid_resource_state(merged_state) { return Err(ResourceUsageCompatibilityError::from_texture( unsafe { metadata_provider.get(index) }, selector, *current_simple, new_state, )); } for mip in &mut new_complex.mips[selector.mips.start as usize..selector.mips.end as usize] { for &mut (_, ref mut current_layer_state) in mip.isolate(&selector.layers, TextureUses::UNKNOWN) { *current_layer_state = merged_state; } mip.coalesce(); } } unsafe { current_state_set.make_complex_unchecked(index, new_complex) }; } (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Single(new_simple)) => { for (mip_id, mip) in current_complex.mips.iter_mut().enumerate() { let mip_id = mip_id as u32; for &mut (ref layers, ref mut current_layer_state) in mip.iter_mut() { let merged_state = *current_layer_state | new_simple; // Once we remove unknown, this will never be empty, as // simple states are never unknown. let merged_state = merged_state - TextureUses::UNKNOWN; if invalid_resource_state(merged_state) { return Err(ResourceUsageCompatibilityError::from_texture( unsafe { metadata_provider.get(index) }, TextureSelector { mips: mip_id..mip_id + 1, layers: layers.clone(), }, *current_layer_state, new_simple, )); } *current_layer_state = merged_state; } mip.coalesce(); } } (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Many(new_many)) => { for (selector, new_state) in new_many { for mip_id in selector.mips { strict_assert!((mip_id as usize) < current_complex.mips.len()); let mip = unsafe { current_complex.mips.get_unchecked_mut(mip_id as usize) }; for &mut (ref layers, ref mut current_layer_state) in mip.isolate(&selector.layers, TextureUses::UNKNOWN) { let merged_state = *current_layer_state | new_state; let merged_state = merged_state - TextureUses::UNKNOWN; if merged_state.is_empty() { // We know nothing about this state, lets just move on. continue; } if invalid_resource_state(merged_state) { return Err(ResourceUsageCompatibilityError::from_texture( unsafe { metadata_provider.get(index) }, TextureSelector { mips: mip_id..mip_id + 1, layers: layers.clone(), }, *current_layer_state, new_state, )); } *current_layer_state = merged_state; } mip.coalesce(); } } } } Ok(()) } #[inline(always)] unsafe fn barrier( texture_selector: &TextureSelector, current_state_set: &TextureStateSet, index: usize, state_provider: TextureStateProvider<'_>, barriers: &mut Vec>, ordered_uses_mask: TextureUses, ) { let current_state = unsafe { current_state_set.get_unchecked(index) }; let new_state = unsafe { state_provider.get_state(Some(texture_selector), index) }; match (current_state, new_state) { (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Single(new_simple)) => { if skip_barrier(current_simple, ordered_uses_mask, new_simple) { return; } barriers.push(PendingTransition { id: index as _, selector: texture_selector.clone(), usage: hal::StateTransition { from: current_simple, to: new_simple, }, }); } (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Many(new_many)) => { for (selector, new_state) in new_many { if new_state == TextureUses::UNKNOWN { continue; } if skip_barrier(current_simple, ordered_uses_mask, new_state) { continue; } barriers.push(PendingTransition { id: index as _, selector, usage: hal::StateTransition { from: current_simple, to: new_state, }, }); } } (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Single(new_simple)) => { for (mip_id, mip) in current_complex.mips.iter().enumerate() { let mip_id = mip_id as u32; for &(ref layers, current_layer_state) in mip.iter() { if current_layer_state == TextureUses::UNKNOWN { continue; } if skip_barrier(current_layer_state, ordered_uses_mask, new_simple) { continue; } barriers.push(PendingTransition { id: index as _, selector: TextureSelector { mips: mip_id..mip_id + 1, layers: layers.clone(), }, usage: hal::StateTransition { from: current_layer_state, to: new_simple, }, }); } } } (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Many(new_many)) => { for (selector, new_state) in new_many { for mip_id in selector.mips { strict_assert!((mip_id as usize) < current_complex.mips.len()); let mip = unsafe { current_complex.mips.get_unchecked(mip_id as usize) }; for (layers, current_layer_state) in mip.iter_filter(&selector.layers) { if *current_layer_state == TextureUses::UNKNOWN || new_state == TextureUses::UNKNOWN { continue; } if skip_barrier(*current_layer_state, ordered_uses_mask, new_state) { continue; } barriers.push(PendingTransition { id: index as _, selector: TextureSelector { mips: mip_id..mip_id + 1, layers, }, usage: hal::StateTransition { from: *current_layer_state, to: new_state, }, }); } } } } } } #[inline(always)] unsafe fn update( texture_selector: &TextureSelector, start_state_set: Option<&mut TextureStateSet>, current_state_set: &mut TextureStateSet, index: usize, state_provider: TextureStateProvider<'_>, ) { // We only ever need to update the start state here if the state is complex. // // If the state is simple, the first insert to the tracker would cover it. let mut start_complex = start_state_set.and_then(|start_state_set| { match unsafe { start_state_set.get_mut_unchecked(index) } { SingleOrManyStates::Single(_) => None, SingleOrManyStates::Many(complex) => Some(complex), } }); let current_state = unsafe { current_state_set.get_mut_unchecked(index) }; let new_state = unsafe { state_provider.get_state(Some(texture_selector), index) }; match (current_state, new_state) { (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Single(new_simple)) => { *current_simple = new_simple; } (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Many(new_many)) => { // Because we are now demoting this simple state to a complex state, // we actually need to make a whole new complex state for us to use // as there wasn't one before. let mut new_complex = unsafe { ComplexTextureState::from_selector_state_iter( texture_selector.clone(), iter::once((texture_selector.clone(), *current_simple)), ) }; for (selector, mut new_state) in new_many { if new_state == TextureUses::UNKNOWN { new_state = *current_simple; } for mip in &mut new_complex.mips[selector.mips.start as usize..selector.mips.end as usize] { for &mut (_, ref mut current_layer_state) in mip.isolate(&selector.layers, TextureUses::UNKNOWN) { *current_layer_state = new_state; } mip.coalesce(); } } unsafe { current_state_set.make_complex_unchecked(index, new_complex) }; } (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Single(new_single)) => { for (mip_id, mip) in current_complex.mips.iter().enumerate() { for &(ref layers, current_layer_state) in mip.iter() { // If this state is unknown, that means that the start is _also_ unknown. if current_layer_state == TextureUses::UNKNOWN { if let Some(&mut ref mut start_complex) = start_complex { strict_assert!(mip_id < start_complex.mips.len()); let start_mip = unsafe { start_complex.mips.get_unchecked_mut(mip_id) }; for &mut (_, ref mut current_start_state) in start_mip.isolate(layers, TextureUses::UNKNOWN) { strict_assert_eq!(*current_start_state, TextureUses::UNKNOWN); *current_start_state = new_single; } start_mip.coalesce(); } } } } unsafe { current_state_set.make_simple_unchecked(index, new_single) }; } (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Many(new_many)) => { for (selector, new_state) in new_many { if new_state == TextureUses::UNKNOWN { // We know nothing new continue; } for mip_id in selector.mips { let mip_id = mip_id as usize; strict_assert!(mip_id < current_complex.mips.len()); let mip = unsafe { current_complex.mips.get_unchecked_mut(mip_id) }; for &mut (ref layers, ref mut current_layer_state) in mip.isolate(&selector.layers, TextureUses::UNKNOWN) { if *current_layer_state == TextureUses::UNKNOWN && new_state != TextureUses::UNKNOWN { // We now know something about this subresource that // we didn't before so we should go back and update // the start state. // // We know we must have starter state be complex, // otherwise we would know about this state. strict_assert!(start_complex.is_some()); let start_complex = unsafe { start_complex.as_deref_mut().unwrap_unchecked() }; strict_assert!(mip_id < start_complex.mips.len()); let start_mip = unsafe { start_complex.mips.get_unchecked_mut(mip_id) }; for &mut (_, ref mut current_start_state) in start_mip.isolate(layers, TextureUses::UNKNOWN) { strict_assert_eq!(*current_start_state, TextureUses::UNKNOWN); *current_start_state = new_state; } start_mip.coalesce(); } *current_layer_state = new_state; } mip.coalesce(); } } } } } ================================================ FILE: wgpu-core/src/validation/shader_io_deductions.rs ================================================ use core::fmt::{self, Debug, Display, Formatter}; #[cfg(doc)] #[expect(unused_imports)] use crate::validation::StageError; /// Max shader I/O variable deductions for vertex shader output. Used by /// [`StageError::TooManyUserDefinedVertexOutputs`] and /// [`StageError::VertexOutputLocationTooLarge`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum MaxVertexShaderOutputDeduction { /// When a pipeline's [`crate::pipeline::RenderPipelineDescriptor::primitive`] is set to /// [`wgt::PrimitiveTopology::PointList`]. PointListPrimitiveTopology, /// When a clip distances array primitive is used in an output. ClipDistances { array_size: u32 }, } impl MaxVertexShaderOutputDeduction { fn variables_from_clip_distance_slot(num_slots: u32) -> u32 { num_slots.div_ceil(4) } } impl MaxVertexShaderOutputDeduction { pub fn for_variables(self) -> u32 { match self { Self::PointListPrimitiveTopology => 1, Self::ClipDistances { array_size } => { Self::variables_from_clip_distance_slot(array_size) } } } pub fn for_location(self) -> u32 { match self { Self::PointListPrimitiveTopology => 0, Self::ClipDistances { array_size } => { Self::variables_from_clip_distance_slot(array_size) } } } } /// Max shader I/O variable deductions for vertex shader output. Used by /// [`StageError::TooManyUserDefinedFragmentInputs`] and /// [`StageError::FragmentInputLocationTooLarge`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum MaxFragmentShaderInputDeduction { InterStageBuiltIn(InterStageBuiltIn), } impl MaxFragmentShaderInputDeduction { pub fn for_variables(self) -> u32 { match self { Self::InterStageBuiltIn(builtin) => match builtin { InterStageBuiltIn::FrontFacing | InterStageBuiltIn::SampleIndex | InterStageBuiltIn::SampleMask | InterStageBuiltIn::PrimitiveIndex | InterStageBuiltIn::SubgroupInvocationId | InterStageBuiltIn::SubgroupSize | InterStageBuiltIn::ViewIndex | InterStageBuiltIn::PointCoord => 1, InterStageBuiltIn::Barycentric => 3, InterStageBuiltIn::Position => 0, }, } } pub fn from_inter_stage_builtin(builtin: naga::BuiltIn) -> Option { use naga::BuiltIn; Some(Self::InterStageBuiltIn(match builtin { BuiltIn::Position { .. } => InterStageBuiltIn::Position, BuiltIn::FrontFacing => InterStageBuiltIn::FrontFacing, BuiltIn::SampleIndex => InterStageBuiltIn::SampleIndex, BuiltIn::SampleMask => InterStageBuiltIn::SampleMask, BuiltIn::PrimitiveIndex => InterStageBuiltIn::PrimitiveIndex, BuiltIn::SubgroupSize => InterStageBuiltIn::SubgroupSize, BuiltIn::SubgroupInvocationId => InterStageBuiltIn::SubgroupInvocationId, BuiltIn::PointCoord => InterStageBuiltIn::PointCoord, BuiltIn::Barycentric { .. } => InterStageBuiltIn::Barycentric, BuiltIn::ViewIndex => InterStageBuiltIn::ViewIndex, BuiltIn::BaseInstance | BuiltIn::BaseVertex | BuiltIn::ClipDistances | BuiltIn::CullDistance | BuiltIn::InstanceIndex | BuiltIn::PointSize | BuiltIn::VertexIndex | BuiltIn::DrawIndex | BuiltIn::FragDepth | BuiltIn::GlobalInvocationId | BuiltIn::LocalInvocationId | BuiltIn::LocalInvocationIndex | BuiltIn::WorkGroupId | BuiltIn::WorkGroupSize | BuiltIn::NumWorkGroups | BuiltIn::NumSubgroups | BuiltIn::SubgroupId | BuiltIn::MeshTaskSize | BuiltIn::CullPrimitive | BuiltIn::PointIndex | BuiltIn::LineIndices | BuiltIn::TriangleIndices | BuiltIn::VertexCount | BuiltIn::Vertices | BuiltIn::PrimitiveCount | BuiltIn::Primitives | BuiltIn::RayInvocationId | BuiltIn::NumRayInvocations | BuiltIn::InstanceCustomData | BuiltIn::GeometryIndex | BuiltIn::WorldRayOrigin | BuiltIn::WorldRayDirection | BuiltIn::ObjectRayOrigin | BuiltIn::ObjectRayDirection | BuiltIn::RayTmin | BuiltIn::RayTCurrentMax | BuiltIn::ObjectToWorld | BuiltIn::WorldToObject | BuiltIn::HitKind => return None, })) } } /// A [`naga::BuiltIn`] that counts towards /// a [`MaxFragmentShaderInputDeduction::InterStageBuiltIn`]. /// /// See also . #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum InterStageBuiltIn { // Standard for WebGPU Position, FrontFacing, SampleIndex, SampleMask, PrimitiveIndex, SubgroupInvocationId, SubgroupSize, // Non-standard PointCoord, Barycentric, ViewIndex, } pub(in crate::validation) fn display_deductions_as_optional_list( deductions: &[T], accessor: fn(&T) -> u32, ) -> impl Display + '_ where T: Debug, { struct DisplayFromFn(F); impl Display for DisplayFromFn where F: Fn(&mut Formatter<'_>) -> fmt::Result, { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let Self(inner) = self; inner(f) } } DisplayFromFn(move |f: &mut Formatter<'_>| { let relevant_deductions = deductions .iter() .map(|deduction| (deduction, accessor(deduction))) .filter(|(_, effective_deduction)| *effective_deduction > 0); if relevant_deductions.clone().next().is_some() { writeln!(f, "; note that some deductions apply during validation:")?; let mut wrote_something = false; for deduction in deductions { let deducted_amount = accessor(deduction); if deducted_amount > 0 { writeln!(f, "\n- {deduction:?}: {}", accessor(deduction))?; wrote_something = true; } } debug_assert!( wrote_something, "no substantial deductions found in error display" ); } Ok(()) }) } ================================================ FILE: wgpu-core/src/validation.rs ================================================ [File too large to display: 79.7 KB] ================================================ FILE: wgpu-core/src/weak_vec.rs ================================================ [File too large to display: 1.8 KB] ================================================ FILE: wgpu-hal/Cargo.toml ================================================ [File too large to display: 11.6 KB] ================================================ FILE: wgpu-hal/LICENSE.APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: wgpu-hal/LICENSE.MIT ================================================ MIT License Copyright (c) 2025 The gfx-rs developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: wgpu-hal/README.md ================================================ # `wgpu_hal`: a cross-platform unsafe graphics abstraction This crate defines a set of traits abstracting over modern graphics APIs, with implementations ("backends") for Vulkan, Metal, Direct3D, and GL. `wgpu_hal` is a spiritual successor to [gfx-hal](https://github.com/gfx-rs/gfx), but with reduced scope, and oriented towards WebGPU implementation goals. It has no overhead for validation or tracking, and the API translation overhead is kept to the bare minimum by the design of WebGPU. This API can be used for resource-demanding applications and engines. The `wgpu_hal` crate's main design choices: - Our traits are meant to be *portable*: proper use should get equivalent results regardless of the backend. - Our traits' contracts are *unsafe*: implementations perform minimal validation, if any, and incorrect use will often cause undefined behavior. This allows us to minimize the overhead we impose over the underlying graphics system. If you need safety, the [`wgpu-core`] crate provides a safe API for driving `wgpu_hal`, implementing all necessary validation, resource state tracking, and so on. (Note that `wgpu-core` is designed for use via FFI; the [`wgpu`] crate provides more idiomatic Rust bindings for `wgpu-core`.) Or, you can do your own validation. - In the same vein, returned errors *only cover cases the user can't anticipate*, like running out of memory or losing the device. Any errors that the user could reasonably anticipate are their responsibility to avoid. For example, `wgpu_hal` returns no error for mapping a buffer that's not mappable: as the buffer creator, the user should already know if they can map it. - We use *static dispatch*. The traits are not generally object-safe. You must select a specific backend type like [`vulkan::Api`] or [`metal::Api`], and then use that according to the main traits, or call backend-specific methods. - We use *idiomatic Rust parameter passing*, taking objects by reference, returning them by value, and so on, unlike `wgpu-core`, which refers to objects by ID. - We map buffer contents *persistently*. This means that the buffer can remain mapped on the CPU while the GPU reads or writes to it. You must explicitly indicate when data might need to be transferred between CPU and GPU, if `wgpu_hal` indicates that the mapping is not coherent (that is, automatically synchronized between the two devices). - You must record *explicit barriers* between different usages of a resource. For example, if a buffer is written to by a compute shader, and then used as and index buffer to a draw call, you must use [`CommandEncoder::transition_buffers`] between those two operations. - Pipeline layouts are *explicitly specified* when setting bind group. Incompatible layouts disturb groups bound at higher indices. - The API *accepts collections as iterators*, to avoid forcing the user to store data in particular containers. The implementation doesn't guarantee that any of the iterators are drained, unless stated otherwise by the function documentation. For this reason, we recommend that iterators don't do any mutating work. Unfortunately, `wgpu_hal`'s safety requirements are not fully documented. Ideally, all trait methods would have doc comments setting out the requirements users must meet to ensure correct and portable behavior. If you are aware of a specific requirement that a backend imposes that is not ensured by the traits' documented rules, please file an issue. Or, if you are a capable technical writer, please file a pull request! [`wgpu-core`]: https://crates.io/crates/wgpu-core [`wgpu`]: https://crates.io/crates/wgpu [`vulkan::Api`]: vulkan/struct.Api.html [`metal::Api`]: metal/struct.Api.html ## Primary backends The `wgpu_hal` crate has full-featured backends implemented on the following platform graphics APIs: - Vulkan, available on Linux, Android, and Windows, using the [`ash`] crate's Vulkan bindings. It's also available on macOS, if you install [MoltenVK]. - Metal on macOS, using the [`metal`] crate's bindings. - Direct3D 12 on Windows, using the [`windows`] crate's bindings. [`ash`]: https://crates.io/crates/ash [MoltenVK]: https://github.com/KhronosGroup/MoltenVK [`metal`]: https://crates.io/crates/metal [`windows`]: https://crates.io/crates/windows ## Secondary backends The `wgpu_hal` crate has a partial implementation based on the following platform graphics API: - The GL backend is available anywhere OpenGL, OpenGL ES, or WebGL are available. See the [`gles`] module documentation for details. [`gles`]: gles/index.html You can see what capabilities an adapter is missing by checking the [`DownlevelCapabilities`][tdc] in [`ExposedAdapter::capabilities`], available from [`Instance::enumerate_adapters`]. The API is generally designed to fit the primary backends better than the secondary backends, so the latter may impose more overhead. [tdc]: wgt::DownlevelCapabilities ## Debugging Most of the information on the wiki [Debugging wgpu Applications][wiki-debug] page still applies to this API, with the exception of API tracing/replay functionality, which is only available in `wgpu-core`. [wiki-debug]: https://github.com/gfx-rs/wgpu/wiki/Debugging-wgpu-Applications ================================================ FILE: wgpu-hal/build.rs ================================================ [File too large to display: 1.7 KB] ================================================ FILE: wgpu-hal/examples/halmark/main.rs ================================================ [File too large to display: 32.9 KB] ================================================ FILE: wgpu-hal/examples/halmark/shader.wgsl ================================================ [File too large to display: 1.1 KB] ================================================ FILE: wgpu-hal/examples/raw-gles.em.html ================================================ ================================================ FILE: wgpu-hal/examples/raw-gles.rs ================================================ [File too large to display: 14.1 KB] ================================================ FILE: wgpu-hal/examples/ray-traced-triangle/main.rs ================================================ [File too large to display: 43.4 KB] ================================================ FILE: wgpu-hal/examples/ray-traced-triangle/shader.wgsl ================================================ [File too large to display: 1.3 KB] ================================================ FILE: wgpu-hal/src/auxil/dxgi/conv.rs ================================================ use alloc::string::String; use std::{ffi::OsString, os::windows::ffi::OsStringExt}; use windows::Win32::Graphics::Dxgi; // Helper to convert DXGI adapter name to a normal string pub fn map_adapter_name(name: [u16; 128]) -> String { let len = name.iter().take_while(|&&c| c != 0).count(); let name = OsString::from_wide(&name[..len]); name.to_string_lossy().into_owned() } pub fn map_texture_format_failable( format: wgt::TextureFormat, ) -> Option { use wgt::TextureFormat as Tf; use Dxgi::Common::*; Some(match format { Tf::R8Unorm => DXGI_FORMAT_R8_UNORM, Tf::R8Snorm => DXGI_FORMAT_R8_SNORM, Tf::R8Uint => DXGI_FORMAT_R8_UINT, Tf::R8Sint => DXGI_FORMAT_R8_SINT, Tf::R16Uint => DXGI_FORMAT_R16_UINT, Tf::R16Sint => DXGI_FORMAT_R16_SINT, Tf::R16Unorm => DXGI_FORMAT_R16_UNORM, Tf::R16Snorm => DXGI_FORMAT_R16_SNORM, Tf::R16Float => DXGI_FORMAT_R16_FLOAT, Tf::Rg8Unorm => DXGI_FORMAT_R8G8_UNORM, Tf::Rg8Snorm => DXGI_FORMAT_R8G8_SNORM, Tf::Rg8Uint => DXGI_FORMAT_R8G8_UINT, Tf::Rg8Sint => DXGI_FORMAT_R8G8_SINT, Tf::Rg16Unorm => DXGI_FORMAT_R16G16_UNORM, Tf::Rg16Snorm => DXGI_FORMAT_R16G16_SNORM, Tf::R32Uint => DXGI_FORMAT_R32_UINT, Tf::R32Sint => DXGI_FORMAT_R32_SINT, Tf::R32Float => DXGI_FORMAT_R32_FLOAT, Tf::Rg16Uint => DXGI_FORMAT_R16G16_UINT, Tf::Rg16Sint => DXGI_FORMAT_R16G16_SINT, Tf::Rg16Float => DXGI_FORMAT_R16G16_FLOAT, Tf::Rgba8Unorm => DXGI_FORMAT_R8G8B8A8_UNORM, Tf::Rgba8UnormSrgb => DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, Tf::Bgra8UnormSrgb => DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, Tf::Rgba8Snorm => DXGI_FORMAT_R8G8B8A8_SNORM, Tf::Bgra8Unorm => DXGI_FORMAT_B8G8R8A8_UNORM, Tf::Rgba8Uint => DXGI_FORMAT_R8G8B8A8_UINT, Tf::Rgba8Sint => DXGI_FORMAT_R8G8B8A8_SINT, Tf::Rgb9e5Ufloat => DXGI_FORMAT_R9G9B9E5_SHAREDEXP, Tf::Rgb10a2Uint => DXGI_FORMAT_R10G10B10A2_UINT, Tf::Rgb10a2Unorm => DXGI_FORMAT_R10G10B10A2_UNORM, Tf::Rg11b10Ufloat => DXGI_FORMAT_R11G11B10_FLOAT, Tf::R64Uint => DXGI_FORMAT_R32G32_UINT, // R64 emulated by R32G32 Tf::Rg32Uint => DXGI_FORMAT_R32G32_UINT, Tf::Rg32Sint => DXGI_FORMAT_R32G32_SINT, Tf::Rg32Float => DXGI_FORMAT_R32G32_FLOAT, Tf::Rgba16Uint => DXGI_FORMAT_R16G16B16A16_UINT, Tf::Rgba16Sint => DXGI_FORMAT_R16G16B16A16_SINT, Tf::Rgba16Unorm => DXGI_FORMAT_R16G16B16A16_UNORM, Tf::Rgba16Snorm => DXGI_FORMAT_R16G16B16A16_SNORM, Tf::Rgba16Float => DXGI_FORMAT_R16G16B16A16_FLOAT, Tf::Rgba32Uint => DXGI_FORMAT_R32G32B32A32_UINT, Tf::Rgba32Sint => DXGI_FORMAT_R32G32B32A32_SINT, Tf::Rgba32Float => DXGI_FORMAT_R32G32B32A32_FLOAT, Tf::Stencil8 => DXGI_FORMAT_D24_UNORM_S8_UINT, Tf::Depth16Unorm => DXGI_FORMAT_D16_UNORM, Tf::Depth24Plus => DXGI_FORMAT_D24_UNORM_S8_UINT, Tf::Depth24PlusStencil8 => DXGI_FORMAT_D24_UNORM_S8_UINT, Tf::Depth32Float => DXGI_FORMAT_D32_FLOAT, Tf::Depth32FloatStencil8 => DXGI_FORMAT_D32_FLOAT_S8X24_UINT, Tf::NV12 => DXGI_FORMAT_NV12, Tf::P010 => DXGI_FORMAT_P010, Tf::Bc1RgbaUnorm => DXGI_FORMAT_BC1_UNORM, Tf::Bc1RgbaUnormSrgb => DXGI_FORMAT_BC1_UNORM_SRGB, Tf::Bc2RgbaUnorm => DXGI_FORMAT_BC2_UNORM, Tf::Bc2RgbaUnormSrgb => DXGI_FORMAT_BC2_UNORM_SRGB, Tf::Bc3RgbaUnorm => DXGI_FORMAT_BC3_UNORM, Tf::Bc3RgbaUnormSrgb => DXGI_FORMAT_BC3_UNORM_SRGB, Tf::Bc4RUnorm => DXGI_FORMAT_BC4_UNORM, Tf::Bc4RSnorm => DXGI_FORMAT_BC4_SNORM, Tf::Bc5RgUnorm => DXGI_FORMAT_BC5_UNORM, Tf::Bc5RgSnorm => DXGI_FORMAT_BC5_SNORM, Tf::Bc6hRgbUfloat => DXGI_FORMAT_BC6H_UF16, Tf::Bc6hRgbFloat => DXGI_FORMAT_BC6H_SF16, Tf::Bc7RgbaUnorm => DXGI_FORMAT_BC7_UNORM, Tf::Bc7RgbaUnormSrgb => DXGI_FORMAT_BC7_UNORM_SRGB, Tf::Etc2Rgb8Unorm | Tf::Etc2Rgb8UnormSrgb | Tf::Etc2Rgb8A1Unorm | Tf::Etc2Rgb8A1UnormSrgb | Tf::Etc2Rgba8Unorm | Tf::Etc2Rgba8UnormSrgb | Tf::EacR11Unorm | Tf::EacR11Snorm | Tf::EacRg11Unorm | Tf::EacRg11Snorm | Tf::Astc { block: _, channel: _, } => return None, }) } pub fn map_texture_format(format: wgt::TextureFormat) -> Dxgi::Common::DXGI_FORMAT { match map_texture_format_failable(format) { Some(f) => f, None => unreachable!(), } } // Note: DXGI doesn't allow sRGB format on the swapchain, // but creating RTV of swapchain buffers with sRGB works. pub fn map_texture_format_nosrgb(format: wgt::TextureFormat) -> Dxgi::Common::DXGI_FORMAT { match format { wgt::TextureFormat::Bgra8UnormSrgb => Dxgi::Common::DXGI_FORMAT_B8G8R8A8_UNORM, wgt::TextureFormat::Rgba8UnormSrgb => Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM, _ => map_texture_format(format), } } // SRV and UAV can't use the depth or typeless formats // see https://microsoft.github.io/DirectX-Specs/d3d/PlanarDepthStencilDDISpec.html#view-creation pub fn map_texture_format_for_srv_uav( format: wgt::TextureFormat, aspect: crate::FormatAspects, ) -> Option { Some(match (format, aspect) { (wgt::TextureFormat::Depth16Unorm, crate::FormatAspects::DEPTH) => { Dxgi::Common::DXGI_FORMAT_R16_UNORM } (wgt::TextureFormat::Depth32Float, crate::FormatAspects::DEPTH) => { Dxgi::Common::DXGI_FORMAT_R32_FLOAT } (wgt::TextureFormat::Depth32FloatStencil8, crate::FormatAspects::DEPTH) => { Dxgi::Common::DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS } ( wgt::TextureFormat::Depth24Plus | wgt::TextureFormat::Depth24PlusStencil8, crate::FormatAspects::DEPTH, ) => Dxgi::Common::DXGI_FORMAT_R24_UNORM_X8_TYPELESS, (wgt::TextureFormat::Depth32FloatStencil8, crate::FormatAspects::STENCIL) => { Dxgi::Common::DXGI_FORMAT_X32_TYPELESS_G8X24_UINT } ( wgt::TextureFormat::Stencil8 | wgt::TextureFormat::Depth24PlusStencil8, crate::FormatAspects::STENCIL, ) => Dxgi::Common::DXGI_FORMAT_X24_TYPELESS_G8_UINT, (_, crate::FormatAspects::DEPTH) | (_, crate::FormatAspects::STENCIL) | (_, crate::FormatAspects::DEPTH_STENCIL) => return None, _ => map_texture_format(format), }) } // see https://microsoft.github.io/DirectX-Specs/d3d/PlanarDepthStencilDDISpec.html#planar-layout-for-staging-from-buffer pub fn map_texture_format_for_copy( format: wgt::TextureFormat, aspect: crate::FormatAspects, ) -> Option { Some(match (format, aspect) { (wgt::TextureFormat::Depth16Unorm, crate::FormatAspects::DEPTH) => { Dxgi::Common::DXGI_FORMAT_R16_UNORM } ( wgt::TextureFormat::Depth32Float | wgt::TextureFormat::Depth32FloatStencil8, crate::FormatAspects::DEPTH, ) => Dxgi::Common::DXGI_FORMAT_R32_FLOAT, ( wgt::TextureFormat::Stencil8 | wgt::TextureFormat::Depth24PlusStencil8 | wgt::TextureFormat::Depth32FloatStencil8, crate::FormatAspects::STENCIL, ) => Dxgi::Common::DXGI_FORMAT_R8_UINT, (format, crate::FormatAspects::COLOR) => map_texture_format(format), _ => return None, }) } pub fn map_texture_format_for_resource( format: wgt::TextureFormat, usage: wgt::TextureUses, has_view_formats: bool, casting_fully_typed_format_supported: bool, ) -> Dxgi::Common::DXGI_FORMAT { use wgt::TextureFormat as Tf; use Dxgi::Common::*; if casting_fully_typed_format_supported { map_texture_format(format) // We might view this resource as srgb or non-srgb } else if has_view_formats { match format { Tf::Rgba8Unorm | Tf::Rgba8UnormSrgb => DXGI_FORMAT_R8G8B8A8_TYPELESS, Tf::Bgra8Unorm | Tf::Bgra8UnormSrgb => DXGI_FORMAT_B8G8R8A8_TYPELESS, Tf::Bc1RgbaUnorm | Tf::Bc1RgbaUnormSrgb => DXGI_FORMAT_BC1_TYPELESS, Tf::Bc2RgbaUnorm | Tf::Bc2RgbaUnormSrgb => DXGI_FORMAT_BC2_TYPELESS, Tf::Bc3RgbaUnorm | Tf::Bc3RgbaUnormSrgb => DXGI_FORMAT_BC3_TYPELESS, Tf::Bc7RgbaUnorm | Tf::Bc7RgbaUnormSrgb => DXGI_FORMAT_BC7_TYPELESS, format => map_texture_format(format), } // We might view this resource as SRV/UAV but also as DSV } else if format.is_depth_stencil_format() && usage.intersects( wgt::TextureUses::RESOURCE | wgt::TextureUses::STORAGE_READ_ONLY | wgt::TextureUses::STORAGE_WRITE_ONLY | wgt::TextureUses::STORAGE_READ_WRITE, ) { match format { Tf::Depth16Unorm => DXGI_FORMAT_R16_TYPELESS, Tf::Depth32Float => DXGI_FORMAT_R32_TYPELESS, Tf::Depth32FloatStencil8 => DXGI_FORMAT_R32G8X24_TYPELESS, Tf::Stencil8 | Tf::Depth24Plus | Tf::Depth24PlusStencil8 => DXGI_FORMAT_R24G8_TYPELESS, _ => unreachable!(), } } else { map_texture_format(format) } } pub fn map_index_format(format: wgt::IndexFormat) -> Dxgi::Common::DXGI_FORMAT { match format { wgt::IndexFormat::Uint16 => Dxgi::Common::DXGI_FORMAT_R16_UINT, wgt::IndexFormat::Uint32 => Dxgi::Common::DXGI_FORMAT_R32_UINT, } } pub fn map_vertex_format(format: wgt::VertexFormat) -> Dxgi::Common::DXGI_FORMAT { use wgt::VertexFormat as Vf; use Dxgi::Common::*; match format { Vf::Unorm8 => DXGI_FORMAT_R8_UNORM, Vf::Snorm8 => DXGI_FORMAT_R8_SNORM, Vf::Uint8 => DXGI_FORMAT_R8_UINT, Vf::Sint8 => DXGI_FORMAT_R8_SINT, Vf::Unorm8x2 => DXGI_FORMAT_R8G8_UNORM, Vf::Snorm8x2 => DXGI_FORMAT_R8G8_SNORM, Vf::Uint8x2 => DXGI_FORMAT_R8G8_UINT, Vf::Sint8x2 => DXGI_FORMAT_R8G8_SINT, Vf::Unorm8x4 => DXGI_FORMAT_R8G8B8A8_UNORM, Vf::Snorm8x4 => DXGI_FORMAT_R8G8B8A8_SNORM, Vf::Uint8x4 => DXGI_FORMAT_R8G8B8A8_UINT, Vf::Sint8x4 => DXGI_FORMAT_R8G8B8A8_SINT, Vf::Unorm16 => DXGI_FORMAT_R16_UNORM, Vf::Snorm16 => DXGI_FORMAT_R16_SNORM, Vf::Uint16 => DXGI_FORMAT_R16_UINT, Vf::Sint16 => DXGI_FORMAT_R16_SINT, Vf::Float16 => DXGI_FORMAT_R16_FLOAT, Vf::Unorm16x2 => DXGI_FORMAT_R16G16_UNORM, Vf::Snorm16x2 => DXGI_FORMAT_R16G16_SNORM, Vf::Uint16x2 => DXGI_FORMAT_R16G16_UINT, Vf::Sint16x2 => DXGI_FORMAT_R16G16_SINT, Vf::Float16x2 => DXGI_FORMAT_R16G16_FLOAT, Vf::Unorm16x4 => DXGI_FORMAT_R16G16B16A16_UNORM, Vf::Snorm16x4 => DXGI_FORMAT_R16G16B16A16_SNORM, Vf::Uint16x4 => DXGI_FORMAT_R16G16B16A16_UINT, Vf::Sint16x4 => DXGI_FORMAT_R16G16B16A16_SINT, Vf::Float16x4 => DXGI_FORMAT_R16G16B16A16_FLOAT, Vf::Uint32 => DXGI_FORMAT_R32_UINT, Vf::Sint32 => DXGI_FORMAT_R32_SINT, Vf::Float32 => DXGI_FORMAT_R32_FLOAT, Vf::Uint32x2 => DXGI_FORMAT_R32G32_UINT, Vf::Sint32x2 => DXGI_FORMAT_R32G32_SINT, Vf::Float32x2 => DXGI_FORMAT_R32G32_FLOAT, Vf::Uint32x3 => DXGI_FORMAT_R32G32B32_UINT, Vf::Sint32x3 => DXGI_FORMAT_R32G32B32_SINT, Vf::Float32x3 => DXGI_FORMAT_R32G32B32_FLOAT, Vf::Uint32x4 => DXGI_FORMAT_R32G32B32A32_UINT, Vf::Sint32x4 => DXGI_FORMAT_R32G32B32A32_SINT, Vf::Float32x4 => DXGI_FORMAT_R32G32B32A32_FLOAT, Vf::Unorm10_10_10_2 => DXGI_FORMAT_R10G10B10A2_UNORM, Vf::Unorm8x4Bgra => DXGI_FORMAT_B8G8R8A8_UNORM, Vf::Float64 | Vf::Float64x2 | Vf::Float64x3 | Vf::Float64x4 => unimplemented!(), } } pub fn map_acomposite_alpha_mode(mode: wgt::CompositeAlphaMode) -> Dxgi::Common::DXGI_ALPHA_MODE { match mode { wgt::CompositeAlphaMode::PreMultiplied => Dxgi::Common::DXGI_ALPHA_MODE_PREMULTIPLIED, wgt::CompositeAlphaMode::PostMultiplied => Dxgi::Common::DXGI_ALPHA_MODE_STRAIGHT, wgt::CompositeAlphaMode::Opaque => Dxgi::Common::DXGI_ALPHA_MODE_IGNORE, wgt::CompositeAlphaMode::Auto | wgt::CompositeAlphaMode::Inherit => { Dxgi::Common::DXGI_ALPHA_MODE_UNSPECIFIED } } } ================================================ FILE: wgpu-hal/src/auxil/dxgi/exception.rs ================================================ [File too large to display: 3.4 KB] ================================================ FILE: wgpu-hal/src/auxil/dxgi/factory.rs ================================================ [File too large to display: 6.1 KB] ================================================ FILE: wgpu-hal/src/auxil/dxgi/mod.rs ================================================ [File too large to display: 94 B] ================================================ FILE: wgpu-hal/src/auxil/dxgi/name.rs ================================================ [File too large to display: 829 B] ================================================ FILE: wgpu-hal/src/auxil/dxgi/result.rs ================================================ [File too large to display: 1.0 KB] ================================================ FILE: wgpu-hal/src/auxil/dxgi/time.rs ================================================ [File too large to display: 3.7 KB] ================================================ FILE: wgpu-hal/src/auxil/mod.rs ================================================ [File too large to display: 8.0 KB] ================================================ FILE: wgpu-hal/src/auxil/renderdoc.rs ================================================ [File too large to display: 4.6 KB] ================================================ FILE: wgpu-hal/src/dx12/adapter.rs ================================================ [File too large to display: 60.6 KB] ================================================ FILE: wgpu-hal/src/dx12/command.rs ================================================ [File too large to display: 70.3 KB] ================================================ FILE: wgpu-hal/src/dx12/conv.rs ================================================ [File too large to display: 18.1 KB] ================================================ FILE: wgpu-hal/src/dx12/dcomp.rs ================================================ [File too large to display: 3.9 KB] ================================================ FILE: wgpu-hal/src/dx12/descriptor.rs ================================================ [File too large to display: 10.0 KB] ================================================ FILE: wgpu-hal/src/dx12/device.rs ================================================ [File too large to display: 106.8 KB] ================================================ FILE: wgpu-hal/src/dx12/device_creation.rs ================================================ [File too large to display: 6.6 KB] ================================================ FILE: wgpu-hal/src/dx12/instance.rs ================================================ [File too large to display: 7.5 KB] ================================================ FILE: wgpu-hal/src/dx12/mod.rs ================================================ [File too large to display: 55.1 KB] ================================================ FILE: wgpu-hal/src/dx12/pipeline_desc.rs ================================================ [File too large to display: 14.4 KB] ================================================ FILE: wgpu-hal/src/dx12/sampler.rs ================================================ [File too large to display: 9.5 KB] ================================================ FILE: wgpu-hal/src/dx12/shader_compilation.rs ================================================ [File too large to display: 13.4 KB] ================================================ FILE: wgpu-hal/src/dx12/suballocation.rs ================================================ [File too large to display: 20.9 KB] ================================================ FILE: wgpu-hal/src/dx12/types.rs ================================================ [File too large to display: 1.2 KB] ================================================ FILE: wgpu-hal/src/dx12/view.rs ================================================ [File too large to display: 14.9 KB] ================================================ FILE: wgpu-hal/src/dynamic/adapter.rs ================================================ [File too large to display: 2.4 KB] ================================================ FILE: wgpu-hal/src/dynamic/command.rs ================================================ [File too large to display: 23.9 KB] ================================================ FILE: wgpu-hal/src/dynamic/device.rs ================================================ [File too large to display: 20.1 KB] ================================================ FILE: wgpu-hal/src/dynamic/instance.rs ================================================ [File too large to display: 2.2 KB] ================================================ FILE: wgpu-hal/src/dynamic/mod.rs ================================================ [File too large to display: 8.1 KB] ================================================ FILE: wgpu-hal/src/dynamic/queue.rs ================================================ [File too large to display: 1.8 KB] ================================================ FILE: wgpu-hal/src/dynamic/surface.rs ================================================ [File too large to display: 2.1 KB] ================================================ FILE: wgpu-hal/src/gles/adapter.rs ================================================ [File too large to display: 55.1 KB] ================================================ FILE: wgpu-hal/src/gles/command.rs ================================================ [File too large to display: 47.2 KB] ================================================ FILE: wgpu-hal/src/gles/conv.rs ================================================ [File too large to display: 19.8 KB] ================================================ FILE: wgpu-hal/src/gles/device.rs ================================================ [File too large to display: 63.6 KB] ================================================ FILE: wgpu-hal/src/gles/egl.rs ================================================ [File too large to display: 52.2 KB] ================================================ FILE: wgpu-hal/src/gles/emscripten.rs ================================================ [File too large to display: 983 B] ================================================ FILE: wgpu-hal/src/gles/fence.rs ================================================ [File too large to display: 4.7 KB] ================================================ FILE: wgpu-hal/src/gles/mod.rs ================================================ [File too large to display: 32.5 KB] ================================================ FILE: wgpu-hal/src/gles/queue.rs ================================================ [File too large to display: 85.3 KB] ================================================ FILE: wgpu-hal/src/gles/shaders/clear.frag ================================================ [File too large to display: 182 B] ================================================ FILE: wgpu-hal/src/gles/shaders/clear.vert ================================================ [File too large to display: 209 B] ================================================ FILE: wgpu-hal/src/gles/shaders/srgb_present.frag ================================================ [File too large to display: 515 B] ================================================ FILE: wgpu-hal/src/gles/shaders/srgb_present.vert ================================================ [File too large to display: 402 B] ================================================ FILE: wgpu-hal/src/gles/web.rs ================================================ [File too large to display: 16.2 KB] ================================================ FILE: wgpu-hal/src/gles/wgl.rs ================================================ [File too large to display: 29.5 KB] ================================================ FILE: wgpu-hal/src/lib.rs ================================================ [File too large to display: 104.4 KB] ================================================ FILE: wgpu-hal/src/metal/adapter.rs ================================================ [File too large to display: 71.6 KB] ================================================ FILE: wgpu-hal/src/metal/command.rs ================================================ [File too large to display: 75.0 KB] ================================================ FILE: wgpu-hal/src/metal/conv.rs ================================================ [File too large to display: 18.4 KB] ================================================ FILE: wgpu-hal/src/metal/device.rs ================================================ [File too large to display: 85.8 KB] ================================================ FILE: wgpu-hal/src/metal/library_from_metallib.rs ================================================ [File too large to display: 1.7 KB] ================================================ FILE: wgpu-hal/src/metal/mod.rs ================================================ [File too large to display: 35.3 KB] ================================================ FILE: wgpu-hal/src/metal/surface.rs ================================================ [File too large to display: 7.4 KB] ================================================ FILE: wgpu-hal/src/metal/time.rs ================================================ [File too large to display: 1005 B] ================================================ FILE: wgpu-hal/src/noop/buffer.rs ================================================ [File too large to display: 3.4 KB] ================================================ FILE: wgpu-hal/src/noop/command.rs ================================================ [File too large to display: 9.3 KB] ================================================ FILE: wgpu-hal/src/noop/mod.rs ================================================ [File too large to display: 16.9 KB] ================================================ FILE: wgpu-hal/src/validation_canary.rs ================================================ [File too large to display: 1.2 KB] ================================================ FILE: wgpu-hal/src/vulkan/adapter.rs ================================================ [File too large to display: 142.3 KB] ================================================ FILE: wgpu-hal/src/vulkan/command.rs ================================================ [File too large to display: 50.1 KB] ================================================ FILE: wgpu-hal/src/vulkan/conv.rs ================================================ [File too large to display: 41.3 KB] ================================================ FILE: wgpu-hal/src/vulkan/device.rs ================================================ [File too large to display: 108.4 KB] ================================================ FILE: wgpu-hal/src/vulkan/drm.rs ================================================ [File too large to display: 6.1 KB] ================================================ FILE: wgpu-hal/src/vulkan/instance.rs ================================================ [File too large to display: 40.3 KB] ================================================ FILE: wgpu-hal/src/vulkan/mod.rs ================================================ [File too large to display: 52.1 KB] ================================================ FILE: wgpu-hal/src/vulkan/sampler.rs ================================================ [File too large to display: 6.8 KB] ================================================ FILE: wgpu-hal/src/vulkan/semaphore_list.rs ================================================ [File too large to display: 6.5 KB] ================================================ FILE: wgpu-hal/src/vulkan/swapchain/mod.rs ================================================ [File too large to display: 4.1 KB] ================================================ FILE: wgpu-hal/src/vulkan/swapchain/native.rs ================================================ [File too large to display: 35.8 KB] ================================================ FILE: wgpu-info/Cargo.toml ================================================ [File too large to display: 674 B] ================================================ FILE: wgpu-info/LICENSE.APACHE ================================================ [File too large to display: 9.9 KB] ================================================ FILE: wgpu-info/LICENSE.MIT ================================================ [File too large to display: 1.1 KB] ================================================ FILE: wgpu-info/README.md ================================================ [File too large to display: 844 B] ================================================ FILE: wgpu-info/src/cli.rs ================================================ [File too large to display: 3.9 KB] ================================================ FILE: wgpu-info/src/human.rs ================================================ [File too large to display: 15.1 KB] ================================================ FILE: wgpu-info/src/main.rs ================================================ [File too large to display: 206 B] ================================================ FILE: wgpu-info/src/report.rs ================================================ [File too large to display: 2.2 KB] ================================================ FILE: wgpu-info/src/tests.rs ================================================ [File too large to display: 1.0 KB] ================================================ FILE: wgpu-info/src/texture.rs ================================================ [File too large to display: 10.0 KB] ================================================ FILE: wgpu-macros/Cargo.toml ================================================ [File too large to display: 448 B] ================================================ FILE: wgpu-macros/src/lib.rs ================================================ [File too large to display: 1.6 KB] ================================================ FILE: wgpu-naga-bridge/Cargo.toml ================================================ [File too large to display: 484 B] ================================================ FILE: wgpu-naga-bridge/LICENSE.APACHE ================================================ [File too large to display: 9.9 KB] ================================================ FILE: wgpu-naga-bridge/LICENSE.MIT ================================================ [File too large to display: 1.1 KB] ================================================ FILE: wgpu-naga-bridge/src/lib.rs ================================================ [File too large to display: 10.5 KB] ================================================ FILE: wgpu-types/Cargo.toml ================================================ [File too large to display: 2.1 KB] ================================================ FILE: wgpu-types/LICENSE.APACHE ================================================ [File too large to display: 9.9 KB] ================================================ FILE: wgpu-types/LICENSE.MIT ================================================ [File too large to display: 1.1 KB] ================================================ FILE: wgpu-types/src/adapter.rs ================================================ [File too large to display: 11.4 KB] ================================================ FILE: wgpu-types/src/assertions.rs ================================================ [File too large to display: 2.3 KB] ================================================ FILE: wgpu-types/src/backend.rs ================================================ [File too large to display: 36.3 KB] ================================================ FILE: wgpu-types/src/binding.rs ================================================ [File too large to display: 13.6 KB] ================================================ FILE: wgpu-types/src/buffer.rs ================================================ [File too large to display: 7.7 KB] ================================================ FILE: wgpu-types/src/cast_utils.rs ================================================ [File too large to display: 999 B] ================================================ FILE: wgpu-types/src/counters.rs ================================================ [File too large to display: 7.0 KB] ================================================ FILE: wgpu-types/src/device.rs ================================================ [File too large to display: 4.2 KB] ================================================ FILE: wgpu-types/src/env.rs ================================================ [File too large to display: 326 B] ================================================ FILE: wgpu-types/src/error.rs ================================================ [File too large to display: 1.7 KB] ================================================ FILE: wgpu-types/src/features.rs ================================================ [File too large to display: 82.3 KB] ================================================ FILE: wgpu-types/src/instance.rs ================================================ [File too large to display: 15.8 KB] ================================================ FILE: wgpu-types/src/lib.rs ================================================ [File too large to display: 22.1 KB] ================================================ FILE: wgpu-types/src/limits.rs ================================================ [File too large to display: 47.1 KB] ================================================ FILE: wgpu-types/src/math.rs ================================================ [File too large to display: 787 B] ================================================ FILE: wgpu-types/src/origin_extent.rs ================================================ [File too large to display: 5.6 KB] ================================================ FILE: wgpu-types/src/ray_tracing.rs ================================================ [File too large to display: 7.5 KB] ================================================ FILE: wgpu-types/src/render.rs ================================================ [File too large to display: 35.6 KB] ================================================ FILE: wgpu-types/src/send_sync.rs ================================================ [File too large to display: 1.6 KB] ================================================ FILE: wgpu-types/src/shader.rs ================================================ [File too large to display: 8.0 KB] ================================================ FILE: wgpu-types/src/surface.rs ================================================ [File too large to display: 15.0 KB] ================================================ FILE: wgpu-types/src/texture/external_image.rs ================================================ [File too large to display: 6.0 KB] ================================================ FILE: wgpu-types/src/texture/external_texture.rs ================================================ [File too large to display: 6.7 KB] ================================================ FILE: wgpu-types/src/texture/format.rs ================================================ [File too large to display: 109.6 KB] ================================================ FILE: wgpu-types/src/texture.rs ================================================ [File too large to display: 39.4 KB] ================================================ FILE: wgpu-types/src/tokens.rs ================================================ [File too large to display: 2.7 KB] ================================================ FILE: wgpu-types/src/transfers.rs ================================================ [File too large to display: 17.4 KB] ================================================ FILE: wgpu-types/src/vertex.rs ================================================ [File too large to display: 11.6 KB] ================================================ FILE: wgpu-types/src/write_only.rs ================================================ [File too large to display: 41.0 KB] ================================================ FILE: xtask/Cargo.toml ================================================ [File too large to display: 574 B] ================================================ FILE: xtask/src/changelog.rs ================================================ [File too large to display: 17.2 KB] ================================================ FILE: xtask/src/cts.rs ================================================ [File too large to display: 15.0 KB] ================================================ FILE: xtask/src/install_agility_sdk.rs ================================================ [File too large to display: 3.7 KB] ================================================ FILE: xtask/src/install_warp.rs ================================================ [File too large to display: 3.9 KB] ================================================ FILE: xtask/src/main.rs ================================================ [File too large to display: 6.4 KB] ================================================ FILE: xtask/src/miri.rs ================================================ [File too large to display: 720 B] ================================================ FILE: xtask/src/run_wasm.rs ================================================ [File too large to display: 3.2 KB] ================================================ FILE: xtask/src/test.rs ================================================ [File too large to display: 5.0 KB] ================================================ FILE: xtask/src/util.rs ================================================ [File too large to display: 6.2 KB] ================================================ FILE: xtask/src/vendor_web_sys.rs ================================================ [File too large to display: 10.8 KB]

(a: &[P], b: &[P]) -> P where P: num_traits::PrimInt + num_traits::WrappingAdd + num_traits::WrappingMul, { a.iter() .zip(b.iter()) .map(|(&aa, bb)| aa.wrapping_mul(bb)) .fold(P::zero(), |acc, x| acc.wrapping_add(&x)) } let result = match_literal_vector!(match (e1, e2) => Literal { Float => |e1, e2| { float_dot_checked(e1, e2)? }, AbstractInt => |e1, e2 | { int_dot_checked(e1, e2)? }, I32 => |e1, e2| { int_dot_wrapping(e1, e2) }, U32 => |e1, e2| { int_dot_wrapping(e1, e2) }, })?; self.register_evaluated_expr(Expression::Literal(result), span) } crate::MathFunction::Length => { // https://www.w3.org/TR/WGSL/#length-builtin let e1 = self.extract_vec(arg, true)?; fn float_length(e: &[F]) -> F where F: core::ops::Mul, F: num_traits::Float + iter::Sum, { if e.len() == 1 { // Avoids possible overflow in squaring e[0].abs() } else { e.iter().map(|&ei| ei * ei).sum::().sqrt() } } let result = match_literal_vector!(match e1 => Literal { Float => |e1| { float_length(e1) }, })?; self.register_evaluated_expr(Expression::Literal(result), span) } crate::MathFunction::Distance => { // https://www.w3.org/TR/WGSL/#distance-builtin let e1 = self.extract_vec(arg, true)?; let e2 = self.extract_vec(arg1.unwrap(), true)?; if e1.len() != e2.len() { return Err(ConstantEvaluatorError::InvalidMathArg); } fn float_distance(a: &[F], b: &[F]) -> F where F: core::ops::Mul, F: num_traits::Float + iter::Sum + core::ops::Sub, { if a.len() == 1 { // Avoids possible overflow in squaring (a[0] - b[0]).abs() } else { a.iter() .zip(b.iter()) .map(|(&aa, &bb)| aa - bb) .map(|ei| ei * ei) .sum::() .sqrt() } } let result = match_literal_vector!(match (e1, e2) => Literal { Float => |e1, e2| { float_distance(e1, e2) }, })?; self.register_evaluated_expr(Expression::Literal(result), span) } crate::MathFunction::Normalize => { // https://www.w3.org/TR/WGSL/#normalize-builtin let e1 = self.extract_vec(arg, true)?; fn float_normalize(e: &[F]) -> ArrayVec where F: core::ops::Mul, F: num_traits::Float + iter::Sum, { let len = e.iter().map(|&ei| ei * ei).sum::().sqrt(); let mut out = ArrayVec::new(); for &ei in e { out.push(ei / len); } out } let result = match_literal_vector!(match e1 => LiteralVector { Float => |e1| { float_normalize(e1) }, })?; result.register_as_evaluated_expr(self, span) } // unimplemented crate::MathFunction::Modf | crate::MathFunction::Frexp | crate::MathFunction::Ldexp | crate::MathFunction::Outer | crate::MathFunction::FaceForward | crate::MathFunction::Reflect | crate::MathFunction::Refract | crate::MathFunction::Mix | crate::MathFunction::SmoothStep | crate::MathFunction::Inverse | crate::MathFunction::Transpose | crate::MathFunction::Determinant | crate::MathFunction::QuantizeToF16 | crate::MathFunction::ExtractBits | crate::MathFunction::InsertBits | crate::MathFunction::Pack4x8snorm | crate::MathFunction::Pack4x8unorm | crate::MathFunction::Pack2x16snorm | crate::MathFunction::Pack2x16unorm | crate::MathFunction::Pack2x16float | crate::MathFunction::Pack4xI8 | crate::MathFunction::Pack4xU8 | crate::MathFunction::Pack4xI8Clamp | crate::MathFunction::Pack4xU8Clamp | crate::MathFunction::Unpack4x8snorm | crate::MathFunction::Unpack4x8unorm | crate::MathFunction::Unpack2x16snorm | crate::MathFunction::Unpack2x16unorm | crate::MathFunction::Unpack2x16float | crate::MathFunction::Unpack4xI8 | crate::MathFunction::Unpack4xU8 => Err(ConstantEvaluatorError::NotImplemented( format!("{fun:?} built-in function"), )), } } /// Dot product of two packed vectors (`dot4I8Packed` and `dot4U8Packed`) fn packed_dot_product( &mut self, a: Handle, b: Handle, span: Span, signed: bool, ) -> Result, ConstantEvaluatorError> { let Expression::Literal(Literal::U32(a)) = self.expressions[a] else { return Err(ConstantEvaluatorError::InvalidMathArg); }; let Expression::Literal(Literal::U32(b)) = self.expressions[b] else { return Err(ConstantEvaluatorError::InvalidMathArg); }; let result = if signed { Literal::I32( (a & 0xFF) as i8 as i32 * (b & 0xFF) as i8 as i32 + ((a >> 8) & 0xFF) as i8 as i32 * ((b >> 8) & 0xFF) as i8 as i32 + ((a >> 16) & 0xFF) as i8 as i32 * ((b >> 16) & 0xFF) as i8 as i32 + ((a >> 24) & 0xFF) as i8 as i32 * ((b >> 24) & 0xFF) as i8 as i32, ) } else { Literal::U32( (a & 0xFF) * (b & 0xFF) + ((a >> 8) & 0xFF) * ((b >> 8) & 0xFF) + ((a >> 16) & 0xFF) * ((b >> 16) & 0xFF) + ((a >> 24) & 0xFF) * ((b >> 24) & 0xFF), ) }; self.register_evaluated_expr(Expression::Literal(result), span) } /// Vector cross product. fn cross_product( &mut self, a: Handle, b: Handle, span: Span, ) -> Result, ConstantEvaluatorError> { use Literal as Li; let (a, ty) = self.extract_vec_with_size::<3>(a)?; let (b, _) = self.extract_vec_with_size::<3>(b)?; let product = match (a, b) { ( [Li::AbstractInt(a0), Li::AbstractInt(a1), Li::AbstractInt(a2)], [Li::AbstractInt(b0), Li::AbstractInt(b1), Li::AbstractInt(b2)], ) => { // `cross` has no overload for AbstractInt, so AbstractInt // arguments are automatically converted to AbstractFloat. Since // `f64` has a much wider range than `i64`, there's no danger of // overflow here. let p = cross_product( [a0 as f64, a1 as f64, a2 as f64], [b0 as f64, b1 as f64, b2 as f64], ); [ Li::AbstractFloat(p[0]), Li::AbstractFloat(p[1]), Li::AbstractFloat(p[2]), ] } ( [Li::AbstractFloat(a0), Li::AbstractFloat(a1), Li::AbstractFloat(a2)], [Li::AbstractFloat(b0), Li::AbstractFloat(b1), Li::AbstractFloat(b2)], ) => { let p = cross_product([a0, a1, a2], [b0, b1, b2]); [ Li::AbstractFloat(p[0]), Li::AbstractFloat(p[1]), Li::AbstractFloat(p[2]), ] } ([Li::F16(a0), Li::F16(a1), Li::F16(a2)], [Li::F16(b0), Li::F16(b1), Li::F16(b2)]) => { let p = cross_product([a0, a1, a2], [b0, b1, b2]); [Li::F16(p[0]), Li::F16(p[1]), Li::F16(p[2])] } ([Li::F32(a0), Li::F32(a1), Li::F32(a2)], [Li::F32(b0), Li::F32(b1), Li::F32(b2)]) => { let p = cross_product([a0, a1, a2], [b0, b1, b2]); [Li::F32(p[0]), Li::F32(p[1]), Li::F32(p[2])] } ([Li::F64(a0), Li::F64(a1), Li::F64(a2)], [Li::F64(b0), Li::F64(b1), Li::F64(b2)]) => { let p = cross_product([a0, a1, a2], [b0, b1, b2]); [Li::F64(p[0]), Li::F64(p[1]), Li::F64(p[2])] } _ => return Err(ConstantEvaluatorError::InvalidMathArg), }; let p0 = self.register_evaluated_expr(Expression::Literal(product[0]), span)?; let p1 = self.register_evaluated_expr(Expression::Literal(product[1]), span)?; let p2 = self.register_evaluated_expr(Expression::Literal(product[2]), span)?; self.register_evaluated_expr( Expression::Compose { ty, components: vec![p0, p1, p2], }, span, ) } /// Extract the values of a `vecN` from `expr`. /// /// Return the value of `expr`, whose type is `vecN` for some /// vector size `N` and scalar `S`, as an array of `N` [`Literal`] /// values. /// /// Also return the type handle from the `Compose` expression. fn extract_vec_with_size( &mut self, expr: Handle, ) -> Result<([Literal; N], Handle), ConstantEvaluatorError> { let span = self.expressions.get_span(expr); let expr = self.eval_zero_value_and_splat(expr, span)?; let Expression::Compose { ty, ref components } = self.expressions[expr] else { return Err(ConstantEvaluatorError::InvalidMathArg); }; let mut value = [Literal::Bool(false); N]; for (component, elt) in crate::proc::flatten_compose(ty, components, self.expressions, self.types) .zip(value.iter_mut()) { let Expression::Literal(literal) = self.expressions[component] else { return Err(ConstantEvaluatorError::InvalidMathArg); }; *elt = literal; } Ok((value, ty)) } /// Extract the values of a `vecN` from `expr`. /// /// Return the value of `expr`, whose type is `vecN` for some /// vector size `N` and scalar `S`, as an array of `N` [`Literal`] /// values. /// /// Also return the type handle from the `Compose` expression. fn extract_vec( &mut self, expr: Handle, allow_single: bool, ) -> Result { let span = self.expressions.get_span(expr); let expr = self.eval_zero_value_and_splat(expr, span)?; match self.expressions[expr] { Expression::Literal(literal) if allow_single => { Ok(LiteralVector::from_literal(literal)) } Expression::Compose { ty, ref components } => { let mut components_out = ArrayVec::::new(); for expr in crate::proc::flatten_compose(ty, components, self.expressions, self.types) { match self.expressions[expr] { Expression::Literal(l) => components_out.push(l), _ => return Err(ConstantEvaluatorError::InvalidMathArg), } } LiteralVector::from_literal_vec(components_out) } _ => Err(ConstantEvaluatorError::InvalidMathArg), } } fn array_length( &mut self, array: Handle, span: Span, ) -> Result, ConstantEvaluatorError> { match self.expressions[array] { Expression::ZeroValue(ty) | Expression::Compose { ty, .. } => { match self.types[ty].inner { TypeInner::Array { size, .. } => match size { ArraySize::Constant(len) => { let expr = Expression::Literal(Literal::U32(len.get())); self.register_evaluated_expr(expr, span) } ArraySize::Pending(_) => Err(ConstantEvaluatorError::ArrayLengthOverridden), ArraySize::Dynamic => Err(ConstantEvaluatorError::ArrayLengthDynamic), }, _ => Err(ConstantEvaluatorError::InvalidArrayLengthArg), } } _ => Err(ConstantEvaluatorError::InvalidArrayLengthArg), } } fn access( &mut self, base: Handle, index: usize, span: Span, ) -> Result, ConstantEvaluatorError> { match self.expressions[base] { Expression::ZeroValue(ty) => { let ty_inner = &self.types[ty].inner; let components = ty_inner .components() .ok_or(ConstantEvaluatorError::InvalidAccessBase)?; if index >= components as usize { Err(ConstantEvaluatorError::InvalidAccessBase) } else { let ty_res = ty_inner .component_type(index) .ok_or(ConstantEvaluatorError::InvalidAccessIndex)?; let ty = match ty_res { crate::proc::TypeResolution::Handle(ty) => ty, crate::proc::TypeResolution::Value(inner) => { self.types.insert(Type { name: None, inner }, span) } }; self.register_evaluated_expr(Expression::ZeroValue(ty), span) } } Expression::Splat { size, value } => { if index >= size as usize { Err(ConstantEvaluatorError::InvalidAccessBase) } else { Ok(value) } } Expression::Compose { ty, ref components } => { let _ = self.types[ty] .inner .components() .ok_or(ConstantEvaluatorError::InvalidAccessBase)?; crate::proc::flatten_compose(ty, components, self.expressions, self.types) .nth(index) .ok_or(ConstantEvaluatorError::InvalidAccessIndex) } _ => Err(ConstantEvaluatorError::InvalidAccessBase), } } /// Lower [`ZeroValue`] and [`Splat`] expressions to [`Literal`] and [`Compose`] expressions. /// /// [`ZeroValue`]: Expression::ZeroValue /// [`Splat`]: Expression::Splat /// [`Literal`]: Expression::Literal /// [`Compose`]: Expression::Compose fn eval_zero_value_and_splat( &mut self, mut expr: Handle, span: Span, ) -> Result, ConstantEvaluatorError> { // If expr is a Compose expression, eliminate ZeroValue and Splat expressions for // each of its components. if let Expression::Compose { ty, ref components } = self.expressions[expr] { let components = components .clone() .iter() .map(|component| self.eval_zero_value_and_splat(*component, span)) .collect::>()?; expr = self.register_evaluated_expr(Expression::Compose { ty, components }, span)?; } // The result of the splat() for a Splat of a scalar ZeroValue is a // vector ZeroValue, so we must call eval_zero_value_impl() after // splat() in order to ensure we have no ZeroValues remaining. if let Expression::Splat { size, value } = self.expressions[expr] { expr = self.splat(value, size, span)?; } if let Expression::ZeroValue(ty) = self.expressions[expr] { expr = self.eval_zero_value_impl(ty, span)?; } Ok(expr) } /// Lower [`ZeroValue`] expressions to [`Literal`] and [`Compose`] expressions. /// /// [`ZeroValue`]: Expression::ZeroValue /// [`Literal`]: Expression::Literal /// [`Compose`]: Expression::Compose fn eval_zero_value( &mut self, expr: Handle, span: Span, ) -> Result, ConstantEvaluatorError> { match self.expressions[expr] { Expression::ZeroValue(ty) => self.eval_zero_value_impl(ty, span), _ => Ok(expr), } } /// Lower [`ZeroValue`] expressions to [`Literal`] and [`Compose`] expressions. /// /// [`ZeroValue`]: Expression::ZeroValue /// [`Literal`]: Expression::Literal /// [`Compose`]: Expression::Compose fn eval_zero_value_impl( &mut self, ty: Handle, span: Span, ) -> Result, ConstantEvaluatorError> { match self.types[ty].inner { TypeInner::Scalar(scalar) => { let expr = Expression::Literal( Literal::zero(scalar).ok_or(ConstantEvaluatorError::TypeNotConstructible)?, ); self.register_evaluated_expr(expr, span) } TypeInner::Vector { size, scalar } => { let scalar_ty = self.types.insert( Type { name: None, inner: TypeInner::Scalar(scalar), }, span, ); let el = self.eval_zero_value_impl(scalar_ty, span)?; let expr = Expression::Compose { ty, components: vec![el; size as usize], }; self.register_evaluated_expr(expr, span) } TypeInner::Matrix { columns, rows, scalar, } => { let vec_ty = self.types.insert( Type { name: None, inner: TypeInner::Vector { size: rows, scalar }, }, span, ); let el = self.eval_zero_value_impl(vec_ty, span)?; let expr = Expression::Compose { ty, components: vec![el; columns as usize], }; self.register_evaluated_expr(expr, span) } TypeInner::Array { base, size: ArraySize::Constant(size), .. } => { let el = self.eval_zero_value_impl(base, span)?; let expr = Expression::Compose { ty, components: vec![el; size.get() as usize], }; self.register_evaluated_expr(expr, span) } TypeInner::Struct { ref members, .. } => { let types: Vec<_> = members.iter().map(|m| m.ty).collect(); let mut components = Vec::with_capacity(members.len()); for ty in types { components.push(self.eval_zero_value_impl(ty, span)?); } let expr = Expression::Compose { ty, components }; self.register_evaluated_expr(expr, span) } _ => Err(ConstantEvaluatorError::TypeNotConstructible), } } /// Convert the scalar components of `expr` to `target`. /// /// Treat `span` as the location of the resulting expression. pub fn cast( &mut self, expr: Handle, target: crate::Scalar, span: Span, ) -> Result, ConstantEvaluatorError> { use crate::Scalar as Sc; let expr = self.eval_zero_value(expr, span)?; let make_error = || -> Result<_, ConstantEvaluatorError> { let from = format!("{:?} {:?}", expr, self.expressions[expr]); #[cfg(feature = "wgsl-in")] let to = target.to_wgsl_for_diagnostics(); #[cfg(not(feature = "wgsl-in"))] let to = format!("{target:?}"); Err(ConstantEvaluatorError::InvalidCastArg { from, to }) }; use crate::proc::type_methods::IntFloatLimits; let expr = match self.expressions[expr] { Expression::Literal(literal) => { let literal = match target { Sc::I32 => Literal::I32(match literal { Literal::I32(v) => v, Literal::U32(v) => v as i32, Literal::F32(v) => v.clamp(i32::min_float(), i32::max_float()) as i32, Literal::F16(v) => f16::to_i32(&v).unwrap(), //Only None on NaN or Inf Literal::Bool(v) => v as i32, Literal::F64(_) | Literal::I64(_) | Literal::U64(_) => { return make_error(); } Literal::AbstractInt(v) => i32::try_from_abstract(v)?, Literal::AbstractFloat(v) => i32::try_from_abstract(v)?, }), Sc::U32 => Literal::U32(match literal { Literal::I32(v) => v as u32, Literal::U32(v) => v, Literal::F32(v) => v.clamp(u32::min_float(), u32::max_float()) as u32, // max(0) avoids None due to negative, therefore only None on NaN or Inf Literal::F16(v) => f16::to_u32(&v.max(f16::ZERO)).unwrap(), Literal::Bool(v) => v as u32, Literal::F64(_) | Literal::I64(_) | Literal::U64(_) => { return make_error(); } Literal::AbstractInt(v) => u32::try_from_abstract(v)?, Literal::AbstractFloat(v) => u32::try_from_abstract(v)?, }), Sc::I64 => Literal::I64(match literal { Literal::I32(v) => v as i64, Literal::U32(v) => v as i64, Literal::F32(v) => v.clamp(i64::min_float(), i64::max_float()) as i64, Literal::Bool(v) => v as i64, Literal::F64(v) => v.clamp(i64::min_float(), i64::max_float()) as i64, Literal::I64(v) => v, Literal::U64(v) => v as i64, Literal::F16(v) => f16::to_i64(&v).unwrap(), //Only None on NaN or Inf Literal::AbstractInt(v) => i64::try_from_abstract(v)?, Literal::AbstractFloat(v) => i64::try_from_abstract(v)?, }), Sc::U64 => Literal::U64(match literal { Literal::I32(v) => v as u64, Literal::U32(v) => v as u64, Literal::F32(v) => v.clamp(u64::min_float(), u64::max_float()) as u64, Literal::Bool(v) => v as u64, Literal::F64(v) => v.clamp(u64::min_float(), u64::max_float()) as u64, Literal::I64(v) => v as u64, Literal::U64(v) => v, // max(0) avoids None due to negative, therefore only None on NaN or Inf Literal::F16(v) => f16::to_u64(&v.max(f16::ZERO)).unwrap(), Literal::AbstractInt(v) => u64::try_from_abstract(v)?, Literal::AbstractFloat(v) => u64::try_from_abstract(v)?, }), Sc::F16 => Literal::F16(match literal { Literal::F16(v) => v, Literal::F32(v) => f16::from_f32(v), Literal::F64(v) => f16::from_f64(v), Literal::Bool(v) => f16::from_u32(v as u32).unwrap(), Literal::I64(v) => f16::from_i64(v).unwrap(), Literal::U64(v) => f16::from_u64(v).unwrap(), Literal::I32(v) => f16::from_i32(v).unwrap(), Literal::U32(v) => f16::from_u32(v).unwrap(), Literal::AbstractFloat(v) => f16::try_from_abstract(v)?, Literal::AbstractInt(v) => f16::try_from_abstract(v)?, }), Sc::F32 => Literal::F32(match literal { Literal::I32(v) => v as f32, Literal::U32(v) => v as f32, Literal::F32(v) => v, Literal::Bool(v) => v as u32 as f32, Literal::F64(_) | Literal::I64(_) | Literal::U64(_) => { return make_error(); } Literal::F16(v) => f16::to_f32(v), Literal::AbstractInt(v) => f32::try_from_abstract(v)?, Literal::AbstractFloat(v) => f32::try_from_abstract(v)?, }), Sc::F64 => Literal::F64(match literal { Literal::I32(v) => v as f64, Literal::U32(v) => v as f64, Literal::F16(v) => f16::to_f64(v), Literal::F32(v) => v as f64, Literal::F64(v) => v, Literal::Bool(v) => v as u32 as f64, Literal::I64(_) | Literal::U64(_) => return make_error(), Literal::AbstractInt(v) => f64::try_from_abstract(v)?, Literal::AbstractFloat(v) => f64::try_from_abstract(v)?, }), Sc::BOOL => Literal::Bool(match literal { Literal::I32(v) => v != 0, Literal::U32(v) => v != 0, Literal::F32(v) => v != 0.0, Literal::F16(v) => v != f16::zero(), Literal::Bool(v) => v, Literal::AbstractInt(v) => v != 0, Literal::AbstractFloat(v) => v != 0.0, Literal::F64(_) | Literal::I64(_) | Literal::U64(_) => { return make_error(); } }), Sc::ABSTRACT_FLOAT => Literal::AbstractFloat(match literal { Literal::AbstractInt(v) => { // Overflow is forbidden, but inexact conversions // are fine. The range of f64 is far larger than // that of i64, so we don't have to check anything // here. v as f64 } Literal::AbstractFloat(v) => v, _ => return make_error(), }), Sc::ABSTRACT_INT => Literal::AbstractInt(match literal { Literal::AbstractInt(v) => v, _ => return make_error(), }), _ => { log::debug!("Constant evaluator refused to convert value to {target:?}"); return make_error(); } }; Expression::Literal(literal) } Expression::Compose { ty, components: ref src_components, } => { let ty_inner = match self.types[ty].inner { TypeInner::Vector { size, .. } => TypeInner::Vector { size, scalar: target, }, TypeInner::Matrix { columns, rows, .. } => TypeInner::Matrix { columns, rows, scalar: target, }, _ => return make_error(), }; let mut components = src_components.clone(); for component in &mut components { *component = self.cast(*component, target, span)?; } let ty = self.types.insert( Type { name: None, inner: ty_inner, }, span, ); Expression::Compose { ty, components } } Expression::Splat { size, value } => { let value_span = self.expressions.get_span(value); let cast_value = self.cast(value, target, value_span)?; Expression::Splat { size, value: cast_value, } } _ => return make_error(), }; self.register_evaluated_expr(expr, span) } /// Convert the scalar leaves of `expr` to `target`, handling arrays. /// /// `expr` must be a `Compose` expression whose type is a scalar, vector, /// matrix, or nested arrays of such. /// /// This is basically the same as the [`cast`] method, except that that /// should only handle Naga [`As`] expressions, which cannot convert arrays. /// /// Treat `span` as the location of the resulting expression. /// /// [`cast`]: ConstantEvaluator::cast /// [`As`]: crate::Expression::As pub fn cast_array( &mut self, expr: Handle, target: crate::Scalar, span: Span, ) -> Result, ConstantEvaluatorError> { let expr = self.check_and_get(expr)?; let Expression::Compose { ty, ref components } = self.expressions[expr] else { return self.cast(expr, target, span); }; let TypeInner::Array { base: _, size, stride: _, } = self.types[ty].inner else { return self.cast(expr, target, span); }; let mut components = components.clone(); for component in &mut components { *component = self.cast_array(*component, target, span)?; } let first = components.first().unwrap(); let new_base = match self.resolve_type(*first)? { crate::proc::TypeResolution::Handle(ty) => ty, crate::proc::TypeResolution::Value(inner) => { self.types.insert(Type { name: None, inner }, span) } }; let mut layouter = core::mem::take(self.layouter); layouter.update(self.to_ctx()).unwrap(); *self.layouter = layouter; let new_base_stride = self.layouter[new_base].to_stride(); let new_array_ty = self.types.insert( Type { name: None, inner: TypeInner::Array { base: new_base, size, stride: new_base_stride, }, }, span, ); let compose = Expression::Compose { ty: new_array_ty, components, }; self.register_evaluated_expr(compose, span) } fn unary_op( &mut self, op: UnaryOperator, expr: Handle, span: Span, ) -> Result, ConstantEvaluatorError> { let expr = self.eval_zero_value_and_splat(expr, span)?; let expr = match self.expressions[expr] { Expression::Literal(value) => Expression::Literal(match op { UnaryOperator::Negate => match value { Literal::I32(v) => Literal::I32(v.wrapping_neg()), Literal::I64(v) => Literal::I64(v.wrapping_neg()), Literal::F32(v) => Literal::F32(-v), Literal::F16(v) => Literal::F16(-v), Literal::F64(v) => Literal::F64(-v), Literal::AbstractInt(v) => Literal::AbstractInt(v.wrapping_neg()), Literal::AbstractFloat(v) => Literal::AbstractFloat(-v), _ => return Err(ConstantEvaluatorError::InvalidUnaryOpArg), }, UnaryOperator::LogicalNot => match value { Literal::Bool(v) => Literal::Bool(!v), _ => return Err(ConstantEvaluatorError::InvalidUnaryOpArg), }, UnaryOperator::BitwiseNot => match value { Literal::I32(v) => Literal::I32(!v), Literal::I64(v) => Literal::I64(!v), Literal::U32(v) => Literal::U32(!v), Literal::U64(v) => Literal::U64(!v), Literal::AbstractInt(v) => Literal::AbstractInt(!v), _ => return Err(ConstantEvaluatorError::InvalidUnaryOpArg), }, }), Expression::Compose { ty, components: ref src_components, } => { match self.types[ty].inner { TypeInner::Vector { .. } | TypeInner::Matrix { .. } => (), _ => return Err(ConstantEvaluatorError::InvalidUnaryOpArg), } let mut components = src_components.clone(); for component in &mut components { *component = self.unary_op(op, *component, span)?; } Expression::Compose { ty, components } } _ => return Err(ConstantEvaluatorError::InvalidUnaryOpArg), }; self.register_evaluated_expr(expr, span) } fn binary_op( &mut self, op: BinaryOperator, left: Handle, right: Handle, span: Span, ) -> Result, ConstantEvaluatorError> { let left = self.eval_zero_value_and_splat(left, span)?; let right = self.eval_zero_value_and_splat(right, span)?; // Note: in most cases constant evaluation checks for overflow, but for // i32/u32, it uses wrapping arithmetic. See // . let expr = match (&self.expressions[left], &self.expressions[right]) { (&Expression::Literal(left_value), &Expression::Literal(right_value)) => { if !matches!(op, BinaryOperator::ShiftLeft | BinaryOperator::ShiftRight) && core::mem::discriminant(&left_value) != core::mem::discriminant(&right_value) { return Err(ConstantEvaluatorError::InvalidBinaryOpArgs); } let literal = match op { BinaryOperator::Equal => Literal::Bool(left_value == right_value), BinaryOperator::NotEqual => Literal::Bool(left_value != right_value), BinaryOperator::Less => Literal::Bool(left_value < right_value), BinaryOperator::LessEqual => Literal::Bool(left_value <= right_value), BinaryOperator::Greater => Literal::Bool(left_value > right_value), BinaryOperator::GreaterEqual => Literal::Bool(left_value >= right_value), _ => match (left_value, right_value) { (Literal::I32(a), Literal::I32(b)) => Literal::I32(match op { BinaryOperator::Add => a.wrapping_add(b), BinaryOperator::Subtract => a.wrapping_sub(b), BinaryOperator::Multiply => a.wrapping_mul(b), BinaryOperator::Divide => { if b == 0 { return Err(ConstantEvaluatorError::DivisionByZero); } else { a.wrapping_div(b) } } BinaryOperator::Modulo => { if b == 0 { return Err(ConstantEvaluatorError::RemainderByZero); } else { a.wrapping_rem(b) } } BinaryOperator::And => a & b, BinaryOperator::ExclusiveOr => a ^ b, BinaryOperator::InclusiveOr => a | b, _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), }), (Literal::I32(a), Literal::U32(b)) => Literal::I32(match op { BinaryOperator::ShiftLeft => { if (if a.is_negative() { !a } else { a }).leading_zeros() <= b { return Err(ConstantEvaluatorError::Overflow("<<".to_string())); } a.checked_shl(b) .ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)? } BinaryOperator::ShiftRight => a .checked_shr(b) .ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?, _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), }), (Literal::U32(a), Literal::U32(b)) => Literal::U32(match op { BinaryOperator::Add => a.wrapping_add(b), BinaryOperator::Subtract => a.wrapping_sub(b), BinaryOperator::Multiply => a.wrapping_mul(b), BinaryOperator::Divide => a .checked_div(b) .ok_or(ConstantEvaluatorError::DivisionByZero)?, BinaryOperator::Modulo => a .checked_rem(b) .ok_or(ConstantEvaluatorError::RemainderByZero)?, BinaryOperator::And => a & b, BinaryOperator::ExclusiveOr => a ^ b, BinaryOperator::InclusiveOr => a | b, BinaryOperator::ShiftLeft => a .checked_mul( 1u32.checked_shl(b) .ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?, ) .ok_or(ConstantEvaluatorError::Overflow("<<".to_string()))?, BinaryOperator::ShiftRight => a .checked_shr(b) .ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?, _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), }), (Literal::F32(a), Literal::F32(b)) => Literal::F32(match op { BinaryOperator::Add => a + b, BinaryOperator::Subtract => a - b, BinaryOperator::Multiply => a * b, BinaryOperator::Divide => a / b, BinaryOperator::Modulo => a % b, _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), }), (Literal::AbstractInt(a), Literal::U32(b)) => { Literal::AbstractInt(match op { BinaryOperator::ShiftLeft => { if (if a.is_negative() { !a } else { a }).leading_zeros() <= b { return Err(ConstantEvaluatorError::Overflow( "<<".to_string(), )); } a.checked_shl(b).unwrap_or(0) } BinaryOperator::ShiftRight => a.checked_shr(b).unwrap_or(0), _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), }) } (Literal::F16(a), Literal::F16(b)) => { let result = match op { BinaryOperator::Add => a + b, BinaryOperator::Subtract => a - b, BinaryOperator::Multiply => a * b, BinaryOperator::Divide => a / b, BinaryOperator::Modulo => a % b, _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), }; if !result.is_finite() { return Err(ConstantEvaluatorError::Overflow(format!("{op:?}"))); } Literal::F16(result) } (Literal::AbstractInt(a), Literal::AbstractInt(b)) => { Literal::AbstractInt(match op { BinaryOperator::Add => a.checked_add(b).ok_or_else(|| { ConstantEvaluatorError::Overflow("addition".into()) })?, BinaryOperator::Subtract => a.checked_sub(b).ok_or_else(|| { ConstantEvaluatorError::Overflow("subtraction".into()) })?, BinaryOperator::Multiply => a.checked_mul(b).ok_or_else(|| { ConstantEvaluatorError::Overflow("multiplication".into()) })?, BinaryOperator::Divide => a.checked_div(b).ok_or_else(|| { if b == 0 { ConstantEvaluatorError::DivisionByZero } else { ConstantEvaluatorError::Overflow("division".into()) } })?, BinaryOperator::Modulo => a.checked_rem(b).ok_or_else(|| { if b == 0 { ConstantEvaluatorError::RemainderByZero } else { ConstantEvaluatorError::Overflow("remainder".into()) } })?, BinaryOperator::And => a & b, BinaryOperator::ExclusiveOr => a ^ b, BinaryOperator::InclusiveOr => a | b, _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), }) } (Literal::AbstractFloat(a), Literal::AbstractFloat(b)) => { let result = match op { BinaryOperator::Add => a + b, BinaryOperator::Subtract => a - b, BinaryOperator::Multiply => a * b, BinaryOperator::Divide => a / b, BinaryOperator::Modulo => a % b, _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), }; if !result.is_finite() { return Err(ConstantEvaluatorError::Overflow(format!("{op:?}"))); } Literal::AbstractFloat(result) } (Literal::Bool(a), Literal::Bool(b)) => Literal::Bool(match op { BinaryOperator::LogicalAnd => a && b, BinaryOperator::LogicalOr => a || b, _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), }), _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), }, }; Expression::Literal(literal) } ( &Expression::Compose { components: ref src_components, ty, }, &Expression::Literal(_), ) => { if !is_allowed_compose_literal_op(&self.types[ty].inner, op) { return Err(ConstantEvaluatorError::InvalidBinaryOpArgs); } let mut components = src_components.clone(); for component in &mut components { *component = self.binary_op(op, *component, right, span)?; } Expression::Compose { ty, components } } ( &Expression::Literal(_), &Expression::Compose { components: ref src_components, ty, }, ) => { if !is_allowed_compose_literal_op(&self.types[ty].inner, op) { return Err(ConstantEvaluatorError::InvalidBinaryOpArgs); } let mut components = src_components.clone(); for component in &mut components { *component = self.binary_op(op, left, *component, span)?; } Expression::Compose { ty, components } } ( &Expression::Compose { components: ref left_components, ty: left_ty, }, &Expression::Compose { components: ref right_components, ty: right_ty, }, ) => { // We have to make a copy of the component lists, because the // call to `binary_op_vector` needs `&mut self`, but `self` owns // the component lists. let left_flattened = crate::proc::flatten_compose( left_ty, left_components, self.expressions, self.types, ) .collect::>(); let right_flattened = crate::proc::flatten_compose( right_ty, right_components, self.expressions, self.types, ) .collect::>(); self.binary_op_compose( op, &left_flattened, &right_flattened, left_ty, right_ty, span, )? } _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), }; return self.register_evaluated_expr(expr, span); fn is_allowed_compose_literal_op(compose_ty: &TypeInner, op: BinaryOperator) -> bool { let is_numeric_vec = matches!( compose_ty, TypeInner::Vector { scalar, .. } if scalar.kind != ScalarKind::Bool ); let is_allowed_vec_scalar_op = matches!( op, BinaryOperator::Add | BinaryOperator::Subtract | BinaryOperator::Multiply | BinaryOperator::Divide | BinaryOperator::Modulo ); let is_mat = matches!(compose_ty, TypeInner::Matrix { .. }); let is_allowed_mat_scalar_op = matches!(op, BinaryOperator::Multiply); is_numeric_vec && is_allowed_vec_scalar_op || is_mat && is_allowed_mat_scalar_op } } fn binary_op_compose( &mut self, op: BinaryOperator, left_components: &[Handle], right_components: &[Handle], left_ty: Handle, right_ty: Handle, span: Span, ) -> Result { match (&self.types[left_ty].inner, &self.types[right_ty].inner) { // Binary operation on vector-vector ( &TypeInner::Vector { size: left_size, .. }, &TypeInner::Vector { size: right_size, .. }, ) if left_size == right_size => self.binary_op_vector( op, left_size, left_components, right_components, left_ty, span, ), // Binary operation on vector-matrix ( &TypeInner::Vector { size, .. }, &TypeInner::Matrix { columns, rows, scalar, }, ) if op == BinaryOperator::Multiply && size == rows => self.multiply_vector_matrix( left_components, right_components, columns, scalar, span, ), // Binary operation on matrix-vector ( &TypeInner::Matrix { columns, rows, scalar, }, &TypeInner::Vector { size, .. }, ) if op == BinaryOperator::Multiply && size == columns => { self.multiply_matrix_vector(left_components, right_components, rows, scalar, span) } // Binary operation on matrix-matrix ( &TypeInner::Matrix { columns: left_columns, rows: left_rows, scalar, }, &TypeInner::Matrix { columns: right_columns, rows: right_rows, .. }, ) => match op { BinaryOperator::Add | BinaryOperator::Subtract if left_columns == right_columns && left_rows == right_rows => { let components = left_components .iter() .zip(right_components) .map(|(&left, &right)| self.binary_op(op, left, right, span)) .collect::, _>>()?; Ok(Expression::Compose { ty: left_ty, components, }) } BinaryOperator::Multiply if left_columns == right_rows => self .multiply_matrix_matrix( left_components, right_components, left_rows, right_columns, scalar, span, ), _ => Err(ConstantEvaluatorError::InvalidBinaryOpArgs), }, _ => Err(ConstantEvaluatorError::InvalidBinaryOpArgs), } } fn binary_op_vector( &mut self, op: BinaryOperator, size: crate::VectorSize, left_components: &[Handle], right_components: &[Handle], left_ty: Handle, span: Span, ) -> Result { let ty = match op { // Relational operators produce vectors of booleans. BinaryOperator::Equal | BinaryOperator::NotEqual | BinaryOperator::Less | BinaryOperator::LessEqual | BinaryOperator::Greater | BinaryOperator::GreaterEqual => self.types.insert( Type { name: None, inner: TypeInner::Vector { size, scalar: crate::Scalar::BOOL, }, }, span, ), // Other operators produce the same type as their left // operand. BinaryOperator::Add | BinaryOperator::Subtract | BinaryOperator::Multiply | BinaryOperator::Divide | BinaryOperator::Modulo | BinaryOperator::And | BinaryOperator::ExclusiveOr | BinaryOperator::InclusiveOr | BinaryOperator::ShiftLeft | BinaryOperator::ShiftRight => left_ty, BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr => { // Not supported on vectors return Err(ConstantEvaluatorError::InvalidBinaryOpArgs); } }; let components = left_components .iter() .zip(right_components) .map(|(&left, &right)| self.binary_op(op, left, right, span)) .collect::, _>>()?; Ok(Expression::Compose { ty, components }) } fn multiply_vector_matrix( &mut self, vec_components: &[Handle], mat_components: &[Handle], mat_columns: crate::VectorSize, scalar: crate::Scalar, span: Span, ) -> Result { let ty = self.types.insert( Type { name: None, inner: TypeInner::Vector { size: mat_columns, scalar, }, }, span, ); let components = mat_components .iter() .map(|&column| { let Expression::Compose { ref components, .. } = self.expressions[column] else { unreachable!() }; self.dot_exprs( vec_components.iter().cloned(), components.clone().into_iter(), span, ) }) .collect::, _>>()?; Ok(Expression::Compose { ty, components }) } fn multiply_matrix_vector( &mut self, mat_components: &[Handle], vec_components: &[Handle], mat_rows: crate::VectorSize, scalar: crate::Scalar, span: Span, ) -> Result { let ty = self.types.insert( Type { name: None, inner: TypeInner::Vector { size: mat_rows, scalar, }, }, span, ); let flatten = self.flatten_matrix(mat_components); let nr = mat_rows as usize; let components = (0..nr) .map(|r| { let row = flatten.iter().skip(r).step_by(nr).cloned(); self.dot_exprs(row, vec_components.iter().cloned(), span) }) .collect::, _>>()?; Ok(Expression::Compose { ty, components }) } fn multiply_matrix_matrix( &mut self, left_components: &[Handle], right_components: &[Handle], left_rows: crate::VectorSize, right_columns: crate::VectorSize, scalar: crate::Scalar, span: Span, ) -> Result { let left_nc = left_components.len(); let left_nr = left_rows as usize; let right_nc = right_columns as usize; let right_nr = left_nc; let mut result = Vec::with_capacity(right_nc); let result_ty = self.types.insert( Type { name: None, inner: TypeInner::Matrix { columns: right_columns, rows: left_rows, scalar, }, }, span, ); let result_column_ty = self.types.insert( Type { name: None, inner: TypeInner::Vector { size: left_rows, scalar, }, }, span, ); let left_flattened = self.flatten_matrix(left_components); let right_flattened = self.flatten_matrix(right_components); for c in 0..right_nc { let result_column = (0..left_nr) .map(|r| { let row = left_flattened.iter().skip(r).step_by(left_nr); let column = right_flattened.iter().skip(c * right_nr).take(right_nr); self.dot_exprs(row.cloned(), column.cloned(), span) }) .collect::, _>>()?; let expr = Expression::Compose { ty: result_column_ty, components: result_column, }; let handle = self.register_evaluated_expr(expr, span)?; result.push(handle); } Ok(Expression::Compose { ty: result_ty, components: result, }) } fn flatten_matrix(&self, columns: &[Handle]) -> ArrayVec, 16> { let mut flattened = ArrayVec::<_, 16>::new(); for &column in columns { let Expression::Compose { ref components, .. } = self.expressions[column] else { unreachable!() }; flattened.extend(components.iter().cloned()); } flattened } fn dot_exprs( &mut self, left: impl Iterator>, right: impl Iterator>, span: Span, ) -> Result, ConstantEvaluatorError> { let mut acc = None; for (l, r) in left.zip(right) { let result = self.binary_op(BinaryOperator::Multiply, l, r, span)?; match acc.as_mut() { Some(acc) => *acc = self.binary_op(BinaryOperator::Add, *acc, result, span)?, None => acc = Some(result), } } Ok(acc.unwrap()) } fn relational( &mut self, fun: RelationalFunction, arg: Handle, span: Span, ) -> Result, ConstantEvaluatorError> { let arg = self.eval_zero_value_and_splat(arg, span)?; match fun { RelationalFunction::All | RelationalFunction::Any => match self.expressions[arg] { Expression::Literal(Literal::Bool(_)) => Ok(arg), Expression::Compose { ty, ref components } if matches!(self.types[ty].inner, TypeInner::Vector { .. }) => { let mut bool_components = ArrayVec::::new(); for component in crate::proc::flatten_compose(ty, components, self.expressions, self.types) { match self.expressions[component] { Expression::Literal(Literal::Bool(val)) => { bool_components.push(val); } _ => { return Err(ConstantEvaluatorError::InvalidRelationalArg(fun)); } } } let components = bool_components; let result = match fun { RelationalFunction::All => components.iter().all(|c| *c), RelationalFunction::Any => components.iter().any(|c| *c), _ => unreachable!(), }; self.register_evaluated_expr(Expression::Literal(Literal::Bool(result)), span) } _ => Err(ConstantEvaluatorError::InvalidRelationalArg(fun)), }, _ => Err(ConstantEvaluatorError::NotImplemented(format!( "{fun:?} built-in function" ))), } } /// Deep copy `expr` from `expressions` into `self.expressions`. /// /// Return the root of the new copy. /// /// This is used when we're evaluating expressions in a function's /// expression arena that refer to a constant: we need to copy the /// constant's value into the function's arena so we can operate on it. fn copy_from( &mut self, expr: Handle, expressions: &Arena, ) -> Result, ConstantEvaluatorError> { let span = expressions.get_span(expr); match expressions[expr] { ref expr @ (Expression::Literal(_) | Expression::Constant(_) | Expression::ZeroValue(_)) => self.register_evaluated_expr(expr.clone(), span), Expression::Compose { ty, ref components } => { let mut components = components.clone(); for component in &mut components { *component = self.copy_from(*component, expressions)?; } self.register_evaluated_expr(Expression::Compose { ty, components }, span) } Expression::Splat { size, value } => { let value = self.copy_from(value, expressions)?; self.register_evaluated_expr(Expression::Splat { size, value }, span) } _ => { log::debug!("copy_from: SubexpressionsAreNotConstant"); Err(ConstantEvaluatorError::SubexpressionsAreNotConstant) } } } /// Returns the total number of components, after flattening, of a vector compose expression. fn vector_compose_flattened_size( &self, components: &[Handle], ) -> Result { components .iter() .try_fold(0, |acc, c| -> Result<_, ConstantEvaluatorError> { let size = match *self.resolve_type(*c)?.inner_with(self.types) { TypeInner::Scalar(_) => 1, // We trust that the vector size of `component` is correct, // as it will have already been validated when `component` // was registered. TypeInner::Vector { size, .. } => size as usize, _ => return Err(ConstantEvaluatorError::InvalidVectorComposeComponent), }; Ok(acc + size) }) } fn register_evaluated_expr( &mut self, expr: Expression, span: Span, ) -> Result, ConstantEvaluatorError> { // It suffices to only check_literal_value() for `Literal` expressions, // since we only register one expression at a time, `Compose` // expressions can only refer to other expressions, and `ZeroValue` // expressions are always okay. if let Expression::Literal(literal) = expr { crate::valid::check_literal_value(literal)?; } // Ensure vector composes contain the correct number of components. We // do so here when each compose is registered to avoid having to deal // with the mess each time the compose is used in another expression. if let Expression::Compose { ty, ref components } = expr { if let TypeInner::Vector { size, scalar: _ } = self.types[ty].inner { let expected = size as usize; let actual = self.vector_compose_flattened_size(components)?; if expected != actual { return Err(ConstantEvaluatorError::InvalidVectorComposeLength { expected, actual, }); } } } Ok(self.append_expr(expr, span, ExpressionKind::Const)) } fn append_expr( &mut self, expr: Expression, span: Span, expr_type: ExpressionKind, ) -> Handle { let h = match self.behavior { Behavior::Wgsl( WgslRestrictions::Runtime(ref mut function_local_data) | WgslRestrictions::Const(Some(ref mut function_local_data)), ) | Behavior::Glsl(GlslRestrictions::Runtime(ref mut function_local_data)) => { let is_running = function_local_data.emitter.is_running(); let needs_pre_emit = expr.needs_pre_emit(); if is_running && needs_pre_emit { function_local_data .block .extend(function_local_data.emitter.finish(self.expressions)); let h = self.expressions.append(expr, span); function_local_data.emitter.start(self.expressions); h } else { self.expressions.append(expr, span) } } _ => self.expressions.append(expr, span), }; self.expression_kind_tracker.insert(h, expr_type); h } /// Resolve the type of `expr` if it is a constant expression. /// /// If `expr` was evaluated to a constant, returns its type. /// Otherwise, returns an error. fn resolve_type( &self, expr: Handle, ) -> Result { use crate::proc::TypeResolution as Tr; use crate::Expression as Ex; let resolution = match self.expressions[expr] { Ex::Literal(ref literal) => Tr::Value(literal.ty_inner()), Ex::Constant(c) => Tr::Handle(self.constants[c].ty), Ex::ZeroValue(ty) | Ex::Compose { ty, .. } => Tr::Handle(ty), Ex::Splat { size, value } => { let Tr::Value(TypeInner::Scalar(scalar)) = self.resolve_type(value)? else { return Err(ConstantEvaluatorError::SplatScalarOnly); }; Tr::Value(TypeInner::Vector { scalar, size }) } _ => { log::debug!("resolve_type: SubexpressionsAreNotConstant"); return Err(ConstantEvaluatorError::SubexpressionsAreNotConstant); } }; Ok(resolution) } fn select( &mut self, reject: Handle, accept: Handle, condition: Handle, span: Span, ) -> Result, ConstantEvaluatorError> { let mut arg = |arg| self.eval_zero_value_and_splat(arg, span); let reject = arg(reject)?; let accept = arg(accept)?; let condition = arg(condition)?; let select_single_component = |this: &mut Self, reject_scalar, reject, accept, condition| { let accept = this.cast(accept, reject_scalar, span)?; if condition { Ok(accept) } else { Ok(reject) } }; match (&self.expressions[reject], &self.expressions[accept]) { (&Expression::Literal(reject_lit), &Expression::Literal(_accept_lit)) => { let reject_scalar = reject_lit.scalar(); let &Expression::Literal(Literal::Bool(condition)) = &self.expressions[condition] else { return Err(ConstantEvaluatorError::SelectScalarConditionNotABool); }; select_single_component(self, reject_scalar, reject, accept, condition) } ( &Expression::Compose { ty: reject_ty, components: ref reject_components, }, &Expression::Compose { ty: accept_ty, components: ref accept_components, }, ) => { let ty_deets = |ty| { let (size, scalar) = self.types[ty].inner.vector_size_and_scalar().unwrap(); (size.unwrap(), scalar) }; let expected_vec_size = { let [(reject_vec_size, _), (accept_vec_size, _)] = [reject_ty, accept_ty].map(ty_deets); if reject_vec_size != accept_vec_size { return Err(ConstantEvaluatorError::SelectVecRejectAcceptSizeMismatch { reject: reject_vec_size, accept: accept_vec_size, }); } reject_vec_size }; let condition_components = match self.expressions[condition] { Expression::Literal(Literal::Bool(condition)) => { vec![condition; (expected_vec_size as u8).into()] } Expression::Compose { ty: condition_ty, components: ref condition_components, } => { let (condition_vec_size, condition_scalar) = ty_deets(condition_ty); if condition_scalar.kind != ScalarKind::Bool { return Err(ConstantEvaluatorError::SelectConditionNotAVecBool); } if condition_vec_size != expected_vec_size { return Err(ConstantEvaluatorError::SelectConditionVecSizeMismatch); } condition_components .iter() .copied() .map(|component| match &self.expressions[component] { &Expression::Literal(Literal::Bool(condition)) => condition, _ => unreachable!(), }) .collect() } _ => return Err(ConstantEvaluatorError::SelectConditionNotAVecBool), }; let evaluated = Expression::Compose { ty: reject_ty, components: reject_components .clone() .into_iter() .zip(accept_components.clone().into_iter()) .zip(condition_components.into_iter()) .map(|((reject, accept), condition)| { let reject_scalar = match &self.expressions[reject] { &Expression::Literal(lit) => lit.scalar(), _ => unreachable!(), }; select_single_component(self, reject_scalar, reject, accept, condition) }) .collect::>()?, }; self.register_evaluated_expr(evaluated, span) } _ => Err(ConstantEvaluatorError::SelectAcceptRejectTypeMismatch), } } } fn first_trailing_bit(concrete_int: ConcreteInt<1>) -> ConcreteInt<1> { // NOTE: Bit indices for this built-in start at 0 at the "right" (or LSB). For example, a value // of 1 means the least significant bit is set. Therefore, an input of `0x[80 00…]` would // return a right-to-left bit index of 0. let trailing_zeros_to_bit_idx = |e: u32| -> u32 { match e { idx @ 0..=31 => idx, 32 => u32::MAX, _ => unreachable!(), } }; match concrete_int { ConcreteInt::U32([e]) => ConcreteInt::U32([trailing_zeros_to_bit_idx(e.trailing_zeros())]), ConcreteInt::I32([e]) => { ConcreteInt::I32([trailing_zeros_to_bit_idx(e.trailing_zeros()) as i32]) } } } #[test] fn first_trailing_bit_smoke() { assert_eq!( first_trailing_bit(ConcreteInt::I32([0])), ConcreteInt::I32([-1]) ); assert_eq!( first_trailing_bit(ConcreteInt::I32([1])), ConcreteInt::I32([0]) ); assert_eq!( first_trailing_bit(ConcreteInt::I32([2])), ConcreteInt::I32([1]) ); assert_eq!( first_trailing_bit(ConcreteInt::I32([-1])), ConcreteInt::I32([0]), ); assert_eq!( first_trailing_bit(ConcreteInt::I32([i32::MIN])), ConcreteInt::I32([31]), ); assert_eq!( first_trailing_bit(ConcreteInt::I32([i32::MAX])), ConcreteInt::I32([0]), ); for idx in 0..32 { assert_eq!( first_trailing_bit(ConcreteInt::I32([1 << idx])), ConcreteInt::I32([idx]) ) } assert_eq!( first_trailing_bit(ConcreteInt::U32([0])), ConcreteInt::U32([u32::MAX]) ); assert_eq!( first_trailing_bit(ConcreteInt::U32([1])), ConcreteInt::U32([0]) ); assert_eq!( first_trailing_bit(ConcreteInt::U32([2])), ConcreteInt::U32([1]) ); assert_eq!( first_trailing_bit(ConcreteInt::U32([1 << 31])), ConcreteInt::U32([31]), ); assert_eq!( first_trailing_bit(ConcreteInt::U32([u32::MAX])), ConcreteInt::U32([0]), ); for idx in 0..32 { assert_eq!( first_trailing_bit(ConcreteInt::U32([1 << idx])), ConcreteInt::U32([idx]) ) } } fn first_leading_bit(concrete_int: ConcreteInt<1>) -> ConcreteInt<1> { // NOTE: Bit indices for this built-in start at 0 at the "right" (or LSB). For example, 1 means // the least significant bit is set. Therefore, an input of 1 would return a right-to-left bit // index of 0. let rtl_to_ltr_bit_idx = |e: u32| -> u32 { match e { idx @ 0..=31 => 31 - idx, 32 => u32::MAX, _ => unreachable!(), } }; match concrete_int { ConcreteInt::I32([e]) => ConcreteInt::I32([{ let rtl_bit_index = if e.is_negative() { e.leading_ones() } else { e.leading_zeros() }; rtl_to_ltr_bit_idx(rtl_bit_index) as i32 }]), ConcreteInt::U32([e]) => ConcreteInt::U32([rtl_to_ltr_bit_idx(e.leading_zeros())]), } } #[test] fn first_leading_bit_smoke() { assert_eq!( first_leading_bit(ConcreteInt::I32([-1])), ConcreteInt::I32([-1]) ); assert_eq!( first_leading_bit(ConcreteInt::I32([0])), ConcreteInt::I32([-1]) ); assert_eq!( first_leading_bit(ConcreteInt::I32([1])), ConcreteInt::I32([0]) ); assert_eq!( first_leading_bit(ConcreteInt::I32([-2])), ConcreteInt::I32([0]) ); assert_eq!( first_leading_bit(ConcreteInt::I32([1234 + 4567])), ConcreteInt::I32([12]) ); assert_eq!( first_leading_bit(ConcreteInt::I32([i32::MAX])), ConcreteInt::I32([30]) ); assert_eq!( first_leading_bit(ConcreteInt::I32([i32::MIN])), ConcreteInt::I32([30]) ); // NOTE: Ignore the sign bit, which is a separate (above) case. for idx in 0..(32 - 1) { assert_eq!( first_leading_bit(ConcreteInt::I32([1 << idx])), ConcreteInt::I32([idx]) ); } for idx in 1..(32 - 1) { assert_eq!( first_leading_bit(ConcreteInt::I32([-(1 << idx)])), ConcreteInt::I32([idx - 1]) ); } assert_eq!( first_leading_bit(ConcreteInt::U32([0])), ConcreteInt::U32([u32::MAX]) ); assert_eq!( first_leading_bit(ConcreteInt::U32([1])), ConcreteInt::U32([0]) ); assert_eq!( first_leading_bit(ConcreteInt::U32([u32::MAX])), ConcreteInt::U32([31]) ); for idx in 0..32 { assert_eq!( first_leading_bit(ConcreteInt::U32([1 << idx])), ConcreteInt::U32([idx]) ) } } /// Trait for conversions of abstract values to concrete types. trait TryFromAbstract: Sized { /// Convert an abstract literal `value` to `Self`. /// /// Since Naga's [`AbstractInt`] and [`AbstractFloat`] exist to support /// WGSL, we follow WGSL's conversion rules here: /// /// - WGSL §6.1.2. Conversion Rank says that automatic conversions /// from [`AbstractInt`] to an integer type are either lossless or an /// error. /// /// - WGSL §15.7.6 Floating Point Conversion says that conversions /// to floating point in constant expressions and override /// expressions are errors if the value is out of range for the /// destination type, but rounding is okay. /// /// - WGSL §17.1.2 i32()/u32() constructors treat AbstractFloat as any /// other floating point type, following the scalar floating point to /// integral conversion algorithm (§15.7.6). There is no automatic /// conversion from AbstractFloat to integer types. /// /// [`AbstractInt`]: crate::Literal::AbstractInt /// [`AbstractFloat`]: crate::Literal::AbstractFloat fn try_from_abstract(value: T) -> Result; } impl TryFromAbstract for i32 { fn try_from_abstract(value: i64) -> Result { i32::try_from(value).map_err(|_| ConstantEvaluatorError::AutomaticConversionLossy { value: format!("{value:?}"), to_type: "i32", }) } } impl TryFromAbstract for u32 { fn try_from_abstract(value: i64) -> Result { u32::try_from(value).map_err(|_| ConstantEvaluatorError::AutomaticConversionLossy { value: format!("{value:?}"), to_type: "u32", }) } } impl TryFromAbstract for u64 { fn try_from_abstract(value: i64) -> Result { u64::try_from(value).map_err(|_| ConstantEvaluatorError::AutomaticConversionLossy { value: format!("{value:?}"), to_type: "u64", }) } } impl TryFromAbstract for i64 { fn try_from_abstract(value: i64) -> Result { Ok(value) } } impl TryFromAbstract for f32 { fn try_from_abstract(value: i64) -> Result { let f = value as f32; // The range of `i64` is roughly ±18 × 10¹⁸, whereas the range of // `f32` is roughly ±3.4 × 10³⁸, so there's no opportunity for // overflow here. Ok(f) } } impl TryFromAbstract for f32 { fn try_from_abstract(value: f64) -> Result { let f = value as f32; if f.is_infinite() { return Err(ConstantEvaluatorError::AutomaticConversionLossy { value: format!("{value:?}"), to_type: "f32", }); } Ok(f) } } impl TryFromAbstract for f64 { fn try_from_abstract(value: i64) -> Result { let f = value as f64; // The range of `i64` is roughly ±18 × 10¹⁸, whereas the range of // `f64` is roughly ±1.8 × 10³⁰⁸, so there's no opportunity for // overflow here. Ok(f) } } impl TryFromAbstract for f64 { fn try_from_abstract(value: f64) -> Result { Ok(value) } } impl TryFromAbstract for i32 { fn try_from_abstract(value: f64) -> Result { // https://www.w3.org/TR/WGSL/#floating-point-conversion // To convert a floating point scalar value X to an integer scalar type T: // * If X is a NaN, the result is an indeterminate value in T. // * If X is exactly representable in the target type T, then the // result is that value. // * Otherwise, the result is the value in T closest to truncate(X) and // also exactly representable in the original floating point type. // // A rust cast satisfies these requirements apart from "the result // is... exactly representable in the original floating point type". // However, i32::MIN and i32::MAX are exactly representable by f64, so // we're all good. Ok(value as i32) } } impl TryFromAbstract for u32 { fn try_from_abstract(value: f64) -> Result { // As above, u32::MIN and u32::MAX are exactly representable by f64, // so a simple rust cast is sufficient. Ok(value as u32) } } impl TryFromAbstract for i64 { fn try_from_abstract(value: f64) -> Result { // As above, except we clamp to the minimum and maximum values // representable by both f64 and i64. use crate::proc::type_methods::IntFloatLimits; Ok(value.clamp(i64::min_float(), i64::max_float()) as i64) } } impl TryFromAbstract for u64 { fn try_from_abstract(value: f64) -> Result { // As above, this time clamping to the minimum and maximum values // representable by both f64 and u64. use crate::proc::type_methods::IntFloatLimits; Ok(value.clamp(u64::min_float(), u64::max_float()) as u64) } } impl TryFromAbstract for f16 { fn try_from_abstract(value: f64) -> Result { let f = f16::from_f64(value); if f.is_infinite() { return Err(ConstantEvaluatorError::AutomaticConversionLossy { value: format!("{value:?}"), to_type: "f16", }); } Ok(f) } } impl TryFromAbstract for f16 { fn try_from_abstract(value: i64) -> Result { let f = f16::from_i64(value); if f.is_none() { return Err(ConstantEvaluatorError::AutomaticConversionLossy { value: format!("{value:?}"), to_type: "f16", }); } Ok(f.unwrap()) } } fn cross_product(a: [T; 3], b: [T; 3]) -> [T; 3] where T: Copy, T: core::ops::Mul, T: core::ops::Sub, { [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], ] } #[cfg(test)] mod tests { use alloc::{vec, vec::Vec}; use crate::{ Arena, BinaryOperator, Constant, Expression, FastHashMap, Handle, Literal, ScalarKind, Type, TypeInner, UnaryOperator, UniqueArena, VectorSize, }; use super::{Behavior, ConstantEvaluator, ExpressionKindTracker, WgslRestrictions}; #[test] fn unary_op() { let mut types = UniqueArena::new(); let mut constants = Arena::new(); let overrides = Arena::new(); let mut global_expressions = Arena::new(); let scalar_ty = types.insert( Type { name: None, inner: TypeInner::Scalar(crate::Scalar::I32), }, Default::default(), ); let vec_ty = types.insert( Type { name: None, inner: TypeInner::Vector { size: VectorSize::Bi, scalar: crate::Scalar::I32, }, }, Default::default(), ); let h = constants.append( Constant { name: None, ty: scalar_ty, init: global_expressions .append(Expression::Literal(Literal::I32(4)), Default::default()), }, Default::default(), ); let h1 = constants.append( Constant { name: None, ty: scalar_ty, init: global_expressions .append(Expression::Literal(Literal::I32(8)), Default::default()), }, Default::default(), ); let vec_h = constants.append( Constant { name: None, ty: vec_ty, init: global_expressions.append( Expression::Compose { ty: vec_ty, components: vec![constants[h].init, constants[h1].init], }, Default::default(), ), }, Default::default(), ); let expr = global_expressions.append(Expression::Constant(h), Default::default()); let expr1 = global_expressions.append(Expression::Constant(vec_h), Default::default()); let expr2 = Expression::Unary { op: UnaryOperator::Negate, expr, }; let expr3 = Expression::Unary { op: UnaryOperator::BitwiseNot, expr, }; let expr4 = Expression::Unary { op: UnaryOperator::BitwiseNot, expr: expr1, }; let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&global_expressions); let mut solver = ConstantEvaluator { behavior: Behavior::Wgsl(WgslRestrictions::Const(None)), types: &mut types, constants: &constants, overrides: &overrides, expressions: &mut global_expressions, expression_kind_tracker, layouter: &mut crate::proc::Layouter::default(), }; let res1 = solver .try_eval_and_append(expr2, Default::default()) .unwrap(); let res2 = solver .try_eval_and_append(expr3, Default::default()) .unwrap(); let res3 = solver .try_eval_and_append(expr4, Default::default()) .unwrap(); assert_eq!( global_expressions[res1], Expression::Literal(Literal::I32(-4)) ); assert_eq!( global_expressions[res2], Expression::Literal(Literal::I32(!4)) ); let res3_inner = &global_expressions[res3]; match *res3_inner { Expression::Compose { ref ty, ref components, } => { assert_eq!(*ty, vec_ty); let mut components_iter = components.iter().copied(); assert_eq!( global_expressions[components_iter.next().unwrap()], Expression::Literal(Literal::I32(!4)) ); assert_eq!( global_expressions[components_iter.next().unwrap()], Expression::Literal(Literal::I32(!8)) ); assert!(components_iter.next().is_none()); } _ => panic!("Expected vector"), } } #[test] fn matrix_op() { let mut helper = MatrixTestHelper::new(); for nc in 2..=4 { for nr in 2..=4 { // Validates multiplication on vector-matrix. // vecR(0, 1, .., r) * matCxR(0, 1, .., nc * nr) let evaluated = helper.eval_vector_multiply_matrix(nc, nr); let expected = (0..nc) .map(|c| (0..nr).map(|r| (r * (c * nr + r)) as f32).sum()) .collect::>(); assert_eq!(evaluated, expected); // Validates multiplication on matrix-vector. // matCxR(0, 1, .., nc * nr) * vecC(0, 1, .., nc) let evaluated = helper.eval_matrix_multiply_vector(nc, nr); let expected = (0..nr) .map(|r| (0..nc).map(|c| (c * (c * nr + r)) as f32).sum()) .collect::>(); assert_eq!(evaluated, expected); for k in 2..=4 { // Validates multiplication on matrix-matrix. // matKxR(0, 1, .., k * nr) * matCxK(0, 1, .., nc * k) let evaluated = helper.eval_matrix_multiply_matrix(nr, nc, k); let expected = (0..nc) .flat_map(|c| { (0..nr).map(move |r| { (0..k).map(|v| ((v * nr + r) * (c * k + v)) as f32).sum() }) }) .collect::>(); assert_eq!(evaluated, expected); } } } } /// Test fixture providing pre-built f32 vector and matrix constant /// expressions with sequential element values, used to evaluate and verify /// matrix operations. struct MatrixTestHelper { types: UniqueArena, expressions: Arena, /// Vector expressions from [0, 1] to [0, 1, 2, 3]. vec_exprs: FastHashMap>, /// Matrix expressions from [0, .., 3] to [0, .., 15]. mat_exprs: FastHashMap<(usize, usize), Handle>, } impl MatrixTestHelper { fn new() -> Self { let mut types = UniqueArena::new(); let mut expressions = Arena::new(); let span = crate::Span::default(); let (mut vec_tys, mut mat_tys) = (FastHashMap::default(), FastHashMap::default()); for c in 2..=4 { let vec_ty = types.insert( Type { name: None, inner: TypeInner::Vector { size: Self::int_to_vector_size(c), scalar: crate::Scalar::F32, }, }, span, ); vec_tys.insert(c, vec_ty); for r in 2..=4 { let mat_ty = types.insert( Type { name: None, inner: TypeInner::Matrix { columns: Self::int_to_vector_size(c), rows: Self::int_to_vector_size(r), scalar: crate::Scalar::F32, }, }, span, ); mat_tys.insert((c, r), mat_ty); } } let mut lit_exprs = FastHashMap::default(); for i in 0..16 { let expr = expressions.append(Expression::Literal(Literal::F32(i as f32)), span); lit_exprs.insert(i, expr); } let mut vec_exprs = FastHashMap::default(); for c in 2..=4 { let expr = expressions.append( Expression::Compose { ty: *vec_tys.get(&c).unwrap(), components: (0..c) .map(|i| *lit_exprs.get(&i).unwrap()) .collect::>(), }, span, ); vec_exprs.insert(c, expr); } let mut mat_exprs = FastHashMap::default(); for c in 2..=4 { for r in 2..=4 { let mut columns = Vec::with_capacity(c); for cc in 0..c { let start = cc * r; let expr = expressions.append( Expression::Compose { ty: *vec_tys.get(&r).unwrap(), components: (start..start + r) .map(|i| *lit_exprs.get(&i).unwrap()) .collect::>(), }, span, ); columns.push(expr); } let expr = expressions.append( Expression::Compose { ty: *mat_tys.get(&(c, r)).unwrap(), components: columns, }, span, ); mat_exprs.insert((c, r), expr); } } Self { types, expressions, vec_exprs, mat_exprs, } } /// Evaluates vec[0..nr] * mat[0..nc*nr] and returns the result as f32s. fn eval_vector_multiply_matrix(&mut self, nc: usize, nr: usize) -> Vec { let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&self.expressions); let mut solver = ConstantEvaluator { behavior: Behavior::Wgsl(WgslRestrictions::Const(None)), types: &mut self.types, constants: &Arena::new(), overrides: &Arena::new(), expressions: &mut self.expressions, expression_kind_tracker, layouter: &mut crate::proc::Layouter::default(), }; let result = solver .try_eval_and_append( Expression::Binary { op: BinaryOperator::Multiply, left: *self.vec_exprs.get(&nr).unwrap(), right: *self.mat_exprs.get(&(nc, nr)).unwrap(), }, Default::default(), ) .unwrap(); self.flatten(result) } /// Evaluates mat[0..nc*nr] * vec[0..nc] and returns the result as f32s. fn eval_matrix_multiply_vector(&mut self, nc: usize, nr: usize) -> Vec { let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&self.expressions); let mut solver = ConstantEvaluator { behavior: Behavior::Wgsl(WgslRestrictions::Const(None)), types: &mut self.types, constants: &Arena::new(), overrides: &Arena::new(), expressions: &mut self.expressions, expression_kind_tracker, layouter: &mut crate::proc::Layouter::default(), }; let result = solver .try_eval_and_append( Expression::Binary { op: BinaryOperator::Multiply, left: *self.mat_exprs.get(&(nc, nr)).unwrap(), right: *self.vec_exprs.get(&nc).unwrap(), }, Default::default(), ) .unwrap(); self.flatten(result) } /// Evaluates mat[0..k*l_nr] * mat[0..r_nc*k] and returns the result as /// f32s. fn eval_matrix_multiply_matrix(&mut self, l_nr: usize, r_nc: usize, k: usize) -> Vec { let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&self.expressions); let mut solver = ConstantEvaluator { behavior: Behavior::Wgsl(WgslRestrictions::Const(None)), types: &mut self.types, constants: &Arena::new(), overrides: &Arena::new(), expressions: &mut self.expressions, expression_kind_tracker, layouter: &mut crate::proc::Layouter::default(), }; let result = solver .try_eval_and_append( Expression::Binary { op: BinaryOperator::Multiply, left: *self.mat_exprs.get(&(k, l_nr)).unwrap(), right: *self.mat_exprs.get(&(r_nc, k)).unwrap(), }, Default::default(), ) .unwrap(); self.flatten(result) } fn flatten(&self, expr: Handle) -> Vec { let Expression::Compose { ref components, ref ty, } = self.expressions[expr] else { unreachable!() }; match self.types[*ty].inner { TypeInner::Vector { .. } => components .iter() .map(|&comp| { let Expression::Literal(Literal::F32(v)) = self.expressions[comp] else { unreachable!() }; v }) .collect(), TypeInner::Matrix { .. } => components .iter() .flat_map(|&comp| self.flatten(comp)) .collect(), _ => unreachable!(), } } fn int_to_vector_size(int: usize) -> VectorSize { match int { 2 => VectorSize::Bi, 3 => VectorSize::Tri, 4 => VectorSize::Quad, _ => unreachable!(), } } } #[test] fn cast() { let mut types = UniqueArena::new(); let mut constants = Arena::new(); let overrides = Arena::new(); let mut global_expressions = Arena::new(); let scalar_ty = types.insert( Type { name: None, inner: TypeInner::Scalar(crate::Scalar::I32), }, Default::default(), ); let h = constants.append( Constant { name: None, ty: scalar_ty, init: global_expressions .append(Expression::Literal(Literal::I32(4)), Default::default()), }, Default::default(), ); let expr = global_expressions.append(Expression::Constant(h), Default::default()); let root = Expression::As { expr, kind: ScalarKind::Bool, convert: Some(crate::BOOL_WIDTH), }; let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&global_expressions); let mut solver = ConstantEvaluator { behavior: Behavior::Wgsl(WgslRestrictions::Const(None)), types: &mut types, constants: &constants, overrides: &overrides, expressions: &mut global_expressions, expression_kind_tracker, layouter: &mut crate::proc::Layouter::default(), }; let res = solver .try_eval_and_append(root, Default::default()) .unwrap(); assert_eq!( global_expressions[res], Expression::Literal(Literal::Bool(true)) ); } #[test] fn access() { let mut types = UniqueArena::new(); let mut constants = Arena::new(); let overrides = Arena::new(); let mut global_expressions = Arena::new(); let matrix_ty = types.insert( Type { name: None, inner: TypeInner::Matrix { columns: VectorSize::Bi, rows: VectorSize::Tri, scalar: crate::Scalar::F32, }, }, Default::default(), ); let vec_ty = types.insert( Type { name: None, inner: TypeInner::Vector { size: VectorSize::Tri, scalar: crate::Scalar::F32, }, }, Default::default(), ); let mut vec1_components = Vec::with_capacity(3); let mut vec2_components = Vec::with_capacity(3); for i in 0..3 { let h = global_expressions.append( Expression::Literal(Literal::F32(i as f32)), Default::default(), ); vec1_components.push(h) } for i in 3..6 { let h = global_expressions.append( Expression::Literal(Literal::F32(i as f32)), Default::default(), ); vec2_components.push(h) } let vec1 = constants.append( Constant { name: None, ty: vec_ty, init: global_expressions.append( Expression::Compose { ty: vec_ty, components: vec1_components, }, Default::default(), ), }, Default::default(), ); let vec2 = constants.append( Constant { name: None, ty: vec_ty, init: global_expressions.append( Expression::Compose { ty: vec_ty, components: vec2_components, }, Default::default(), ), }, Default::default(), ); let h = constants.append( Constant { name: None, ty: matrix_ty, init: global_expressions.append( Expression::Compose { ty: matrix_ty, components: vec![constants[vec1].init, constants[vec2].init], }, Default::default(), ), }, Default::default(), ); let base = global_expressions.append(Expression::Constant(h), Default::default()); let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&global_expressions); let mut solver = ConstantEvaluator { behavior: Behavior::Wgsl(WgslRestrictions::Const(None)), types: &mut types, constants: &constants, overrides: &overrides, expressions: &mut global_expressions, expression_kind_tracker, layouter: &mut crate::proc::Layouter::default(), }; let root1 = Expression::AccessIndex { base, index: 1 }; let res1 = solver .try_eval_and_append(root1, Default::default()) .unwrap(); let root2 = Expression::AccessIndex { base: res1, index: 2, }; let res2 = solver .try_eval_and_append(root2, Default::default()) .unwrap(); match global_expressions[res1] { Expression::Compose { ref ty, ref components, } => { assert_eq!(*ty, vec_ty); let mut components_iter = components.iter().copied(); assert_eq!( global_expressions[components_iter.next().unwrap()], Expression::Literal(Literal::F32(3.)) ); assert_eq!( global_expressions[components_iter.next().unwrap()], Expression::Literal(Literal::F32(4.)) ); assert_eq!( global_expressions[components_iter.next().unwrap()], Expression::Literal(Literal::F32(5.)) ); assert!(components_iter.next().is_none()); } _ => panic!("Expected vector"), } assert_eq!( global_expressions[res2], Expression::Literal(Literal::F32(5.)) ); } #[test] fn compose_of_constants() { let mut types = UniqueArena::new(); let mut constants = Arena::new(); let overrides = Arena::new(); let mut global_expressions = Arena::new(); let i32_ty = types.insert( Type { name: None, inner: TypeInner::Scalar(crate::Scalar::I32), }, Default::default(), ); let vec2_i32_ty = types.insert( Type { name: None, inner: TypeInner::Vector { size: VectorSize::Bi, scalar: crate::Scalar::I32, }, }, Default::default(), ); let h = constants.append( Constant { name: None, ty: i32_ty, init: global_expressions .append(Expression::Literal(Literal::I32(4)), Default::default()), }, Default::default(), ); let h_expr = global_expressions.append(Expression::Constant(h), Default::default()); let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&global_expressions); let mut solver = ConstantEvaluator { behavior: Behavior::Wgsl(WgslRestrictions::Const(None)), types: &mut types, constants: &constants, overrides: &overrides, expressions: &mut global_expressions, expression_kind_tracker, layouter: &mut crate::proc::Layouter::default(), }; let solved_compose = solver .try_eval_and_append( Expression::Compose { ty: vec2_i32_ty, components: vec![h_expr, h_expr], }, Default::default(), ) .unwrap(); let solved_negate = solver .try_eval_and_append( Expression::Unary { op: UnaryOperator::Negate, expr: solved_compose, }, Default::default(), ) .unwrap(); let pass = match global_expressions[solved_negate] { Expression::Compose { ty, ref components } => { ty == vec2_i32_ty && components.iter().all(|&component| { let component = &global_expressions[component]; matches!(*component, Expression::Literal(Literal::I32(-4))) }) } _ => false, }; if !pass { panic!("unexpected evaluation result") } } #[test] fn splat_of_constant() { let mut types = UniqueArena::new(); let mut constants = Arena::new(); let overrides = Arena::new(); let mut global_expressions = Arena::new(); let i32_ty = types.insert( Type { name: None, inner: TypeInner::Scalar(crate::Scalar::I32), }, Default::default(), ); let vec2_i32_ty = types.insert( Type { name: None, inner: TypeInner::Vector { size: VectorSize::Bi, scalar: crate::Scalar::I32, }, }, Default::default(), ); let h = constants.append( Constant { name: None, ty: i32_ty, init: global_expressions .append(Expression::Literal(Literal::I32(4)), Default::default()), }, Default::default(), ); let h_expr = global_expressions.append(Expression::Constant(h), Default::default()); let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&global_expressions); let mut solver = ConstantEvaluator { behavior: Behavior::Wgsl(WgslRestrictions::Const(None)), types: &mut types, constants: &constants, overrides: &overrides, expressions: &mut global_expressions, expression_kind_tracker, layouter: &mut crate::proc::Layouter::default(), }; let solved_compose = solver .try_eval_and_append( Expression::Splat { size: VectorSize::Bi, value: h_expr, }, Default::default(), ) .unwrap(); let solved_negate = solver .try_eval_and_append( Expression::Unary { op: UnaryOperator::Negate, expr: solved_compose, }, Default::default(), ) .unwrap(); let pass = match global_expressions[solved_negate] { Expression::Compose { ty, ref components } => { ty == vec2_i32_ty && components.iter().all(|&component| { let component = &global_expressions[component]; matches!(*component, Expression::Literal(Literal::I32(-4))) }) } _ => false, }; if !pass { panic!("unexpected evaluation result") } } #[test] fn splat_of_zero_value() { let mut types = UniqueArena::new(); let constants = Arena::new(); let overrides = Arena::new(); let mut global_expressions = Arena::new(); let f32_ty = types.insert( Type { name: None, inner: TypeInner::Scalar(crate::Scalar::F32), }, Default::default(), ); let vec2_f32_ty = types.insert( Type { name: None, inner: TypeInner::Vector { size: VectorSize::Bi, scalar: crate::Scalar::F32, }, }, Default::default(), ); let five = global_expressions.append(Expression::Literal(Literal::F32(5.0)), Default::default()); let five_splat = global_expressions.append( Expression::Splat { size: VectorSize::Bi, value: five, }, Default::default(), ); let zero = global_expressions.append(Expression::ZeroValue(f32_ty), Default::default()); let zero_splat = global_expressions.append( Expression::Splat { size: VectorSize::Bi, value: zero, }, Default::default(), ); let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&global_expressions); let mut solver = ConstantEvaluator { behavior: Behavior::Wgsl(WgslRestrictions::Const(None)), types: &mut types, constants: &constants, overrides: &overrides, expressions: &mut global_expressions, expression_kind_tracker, layouter: &mut crate::proc::Layouter::default(), }; let solved_add = solver .try_eval_and_append( Expression::Binary { op: BinaryOperator::Add, left: zero_splat, right: five_splat, }, Default::default(), ) .unwrap(); let pass = match global_expressions[solved_add] { Expression::Compose { ty, ref components } => { ty == vec2_f32_ty && components.iter().all(|&component| { let component = &global_expressions[component]; matches!(*component, Expression::Literal(Literal::F32(5.0))) }) } _ => false, }; if !pass { panic!("unexpected evaluation result") } } } ================================================ FILE: naga/src/proc/emitter.rs ================================================ use crate::arena::Arena; /// Helper class to emit expressions #[derive(Default, Debug)] pub struct Emitter { start_len: Option, } impl Emitter { pub fn start(&mut self, arena: &Arena) { if self.start_len.is_some() { unreachable!("Emitting has already started!"); } self.start_len = Some(arena.len()); } pub const fn is_running(&self) -> bool { self.start_len.is_some() } #[must_use] pub fn finish( &mut self, arena: &Arena, ) -> Option<(crate::Statement, crate::span::Span)> { let start_len = self.start_len.take().unwrap(); if start_len != arena.len() { let mut span = crate::span::Span::default(); let range = arena.range_from(start_len); for handle in range.clone() { span.subsume(arena.get_span(handle)) } Some((crate::Statement::Emit(range), span)) } else { None } } } ================================================ FILE: naga/src/proc/index.rs ================================================ /*! Definitions for index bounds checking. */ use core::iter::{self, zip}; use crate::arena::{Handle, HandleSet, UniqueArena}; use crate::{valid, FastHashSet}; /// How should code generated by Naga do bounds checks? /// /// When a vector, matrix, or array index is out of bounds—either negative, or /// greater than or equal to the number of elements in the type—WGSL requires /// that some other index of the implementation's choice that is in bounds is /// used instead. (There are no types with zero elements.) /// /// Similarly, when out-of-bounds coordinates, array indices, or sample indices /// are presented to the WGSL `textureLoad` and `textureStore` operations, the /// operation is redirected to do something safe. /// /// Different users of Naga will prefer different defaults: /// /// - When used as part of a WebGPU implementation, the WGSL specification /// requires the `Restrict` behavior for array, vector, and matrix accesses, /// and either the `Restrict` or `ReadZeroSkipWrite` behaviors for texture /// accesses. /// /// - When used by the `wgpu` crate for native development, `wgpu` selects /// `ReadZeroSkipWrite` as its default. /// /// - Naga's own default is `Unchecked`, so that shader translations /// are as faithful to the original as possible. /// /// Sometimes the underlying hardware and drivers can perform bounds checks /// themselves, in a way that performs better than the checks Naga would inject. /// If you're using native checks like this, then having Naga inject its own /// checks as well would be redundant, and the `Unchecked` policy is /// appropriate. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub enum BoundsCheckPolicy { /// Replace out-of-bounds indexes with some arbitrary in-bounds index. /// /// (This does not necessarily mean clamping. For example, interpreting the /// index as unsigned and taking the minimum with the largest valid index /// would also be a valid implementation. That would map negative indices to /// the last element, not the first.) Restrict, /// Out-of-bounds reads return zero, and writes have no effect. /// /// When applied to a chain of accesses, like `a[i][j].b[k]`, all index /// expressions are evaluated, regardless of whether prior or later index /// expressions were in bounds. But all the accesses per se are skipped /// if any index is out of bounds. ReadZeroSkipWrite, /// Naga adds no checks to indexing operations. Generate the fastest code /// possible. This is the default for Naga, as a translator, but consumers /// should consider defaulting to a safer behavior. Unchecked, } /// Policies for injecting bounds checks during code generation. #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[cfg_attr(feature = "deserialize", serde(default))] pub struct BoundsCheckPolicies { /// How should the generated code handle array, vector, or matrix indices /// that are out of range? pub index: BoundsCheckPolicy, /// How should the generated code handle array, vector, or matrix indices /// that are out of range, when those values live in a [`GlobalVariable`] in /// the [`Storage`] or [`Uniform`] address spaces? /// /// Some graphics hardware provides "robust buffer access", a feature that /// ensures that using a pointer cannot access memory outside the 'buffer' /// that it was derived from. In Naga terms, this means that the hardware /// ensures that pointers computed by applying [`Access`] and /// [`AccessIndex`] expressions to a [`GlobalVariable`] whose [`space`] is /// [`Storage`] or [`Uniform`] will never read or write memory outside that /// global variable. /// /// When hardware offers such a feature, it is probably undesirable to have /// Naga inject bounds checking code for such accesses, since the hardware /// can probably provide the same protection more efficiently. However, /// bounds checks are still needed on accesses to indexable values that do /// not live in buffers, like local variables. /// /// So, this option provides a separate policy that applies only to accesses /// to storage and uniform globals. When depending on hardware bounds /// checking, this policy can be `Unchecked` to avoid unnecessary overhead. /// /// When special hardware support is not available, this should probably be /// the same as `index_bounds_check_policy`. /// /// [`GlobalVariable`]: crate::GlobalVariable /// [`space`]: crate::GlobalVariable::space /// [`Restrict`]: crate::proc::BoundsCheckPolicy::Restrict /// [`ReadZeroSkipWrite`]: crate::proc::BoundsCheckPolicy::ReadZeroSkipWrite /// [`Access`]: crate::Expression::Access /// [`AccessIndex`]: crate::Expression::AccessIndex /// [`Storage`]: crate::AddressSpace::Storage /// [`Uniform`]: crate::AddressSpace::Uniform pub buffer: BoundsCheckPolicy, /// How should the generated code handle image texel loads that are out /// of range? /// /// This controls the behavior of [`ImageLoad`] expressions when a coordinate, /// texture array index, level of detail, or multisampled sample number is out of range. /// /// There is no corresponding policy for [`ImageStore`] statements. All the /// platforms we support already discard out-of-bounds image stores, /// effectively implementing the "skip write" part of [`ReadZeroSkipWrite`]. /// /// [`ImageLoad`]: crate::Expression::ImageLoad /// [`ImageStore`]: crate::Statement::ImageStore /// [`ReadZeroSkipWrite`]: BoundsCheckPolicy::ReadZeroSkipWrite pub image_load: BoundsCheckPolicy, /// How should the generated code handle binding array indexes that are out of bounds. pub binding_array: BoundsCheckPolicy, } /// The default `BoundsCheckPolicy` is `Unchecked`. impl Default for BoundsCheckPolicy { fn default() -> Self { BoundsCheckPolicy::Unchecked } } impl BoundsCheckPolicies { /// Determine which policy applies to `base`. /// /// `base` is the "base" expression (the expression being indexed) of a `Access` /// and `AccessIndex` expression. This is either a pointer, a value, being directly /// indexed, or a binding array. /// /// See the documentation for [`BoundsCheckPolicy`] for details about /// when each policy applies. pub fn choose_policy( &self, base: Handle, types: &UniqueArena, info: &valid::FunctionInfo, ) -> BoundsCheckPolicy { let ty = info[base].ty.inner_with(types); if let crate::TypeInner::BindingArray { .. } = *ty { return self.binding_array; } match ty.pointer_space() { Some(crate::AddressSpace::Storage { access: _ } | crate::AddressSpace::Uniform) => { self.buffer } // This covers other address spaces, but also accessing vectors and // matrices by value, where no pointer is involved. _ => self.index, } } /// Return `true` if any of `self`'s policies are `policy`. pub fn contains(&self, policy: BoundsCheckPolicy) -> bool { self.index == policy || self.buffer == policy || self.image_load == policy } } /// An index that may be statically known, or may need to be computed at runtime. /// /// This enum lets us handle both [`Access`] and [`AccessIndex`] expressions /// with the same code. /// /// [`Access`]: crate::Expression::Access /// [`AccessIndex`]: crate::Expression::AccessIndex #[derive(Clone, Copy, Debug)] pub enum GuardedIndex { Known(u32), Expression(Handle), } /// Build a set of expressions used as indices, to cache in temporary variables when /// emitted. /// /// Given the bounds-check policies `policies`, construct a `HandleSet` containing the handle /// indices of all the expressions in `function` that are ever used as guarded indices /// under the [`ReadZeroSkipWrite`] policy. The `module` argument must be the module to /// which `function` belongs, and `info` should be that function's analysis results. /// /// Such index expressions will be used twice in the generated code: first for the /// comparison to see if the index is in bounds, and then for the access itself, should /// the comparison succeed. To avoid computing the expressions twice, the generated code /// should cache them in temporary variables. /// /// Why do we need to build such a set in advance, instead of just processing access /// expressions as we encounter them? Whether an expression needs to be cached depends on /// whether it appears as something like the [`index`] operand of an [`Access`] expression /// or the [`level`] operand of an [`ImageLoad`] expression, and on the index bounds check /// policies that apply to those accesses. But [`Emit`] statements just identify a range /// of expressions by index; there's no good way to tell what an expression is used /// for. The only way to do it is to just iterate over all the expressions looking for /// relevant `Access` expressions --- which is what this function does. /// /// Simple expressions like variable loads and constants don't make sense to cache: it's /// no better than just re-evaluating them. But constants are not covered by `Emit` /// statements, and `Load`s are always cached to ensure they occur at the right time, so /// we don't bother filtering them out from this set. /// /// Fortunately, we don't need to deal with [`ImageStore`] statements here. When we emit /// code for a statement, the writer isn't in the middle of an expression, so we can just /// emit declarations for temporaries, initialized appropriately. /// /// None of these concerns apply for SPIR-V output, since it's easy to just reuse an /// instruction ID in two places; that has the same semantics as a temporary variable, and /// it's inherent in the design of SPIR-V. This function is more useful for text-based /// back ends. /// /// [`ReadZeroSkipWrite`]: BoundsCheckPolicy::ReadZeroSkipWrite /// [`index`]: crate::Expression::Access::index /// [`Access`]: crate::Expression::Access /// [`level`]: crate::Expression::ImageLoad::level /// [`ImageLoad`]: crate::Expression::ImageLoad /// [`Emit`]: crate::Statement::Emit /// [`ImageStore`]: crate::Statement::ImageStore pub fn find_checked_indexes( module: &crate::Module, function: &crate::Function, info: &valid::FunctionInfo, policies: BoundsCheckPolicies, ) -> HandleSet { use crate::Expression as Ex; let mut guarded_indices = HandleSet::for_arena(&function.expressions); // Don't bother scanning if we never need `ReadZeroSkipWrite`. if policies.contains(BoundsCheckPolicy::ReadZeroSkipWrite) { for (_handle, expr) in function.expressions.iter() { // There's no need to handle `AccessIndex` expressions, as their // indices never need to be cached. match *expr { Ex::Access { base, index } => { if policies.choose_policy(base, &module.types, info) == BoundsCheckPolicy::ReadZeroSkipWrite && access_needs_check( base, GuardedIndex::Expression(index), module, &function.expressions, info, ) .is_some() { guarded_indices.insert(index); } } Ex::ImageLoad { coordinate, array_index, sample, level, .. } => { if policies.image_load == BoundsCheckPolicy::ReadZeroSkipWrite { guarded_indices.insert(coordinate); if let Some(array_index) = array_index { guarded_indices.insert(array_index); } if let Some(sample) = sample { guarded_indices.insert(sample); } if let Some(level) = level { guarded_indices.insert(level); } } } _ => {} } } } guarded_indices } /// Determine whether `index` is statically known to be in bounds for `base`. /// /// If we can't be sure that the index is in bounds, return the limit within /// which valid indices must fall. /// /// The return value is one of the following: /// /// - `Some(Known(n))` indicates that `n` is the largest valid index. /// /// - `Some(Computed(global))` indicates that the largest valid index is one /// less than the length of the array that is the last member of the /// struct held in `global`. /// /// - `None` indicates that the index need not be checked, either because it /// is statically known to be in bounds, or because the applicable policy /// is `Unchecked`. /// /// This function only handles subscriptable types: arrays, vectors, and /// matrices. It does not handle struct member indices; those never require /// run-time checks, so it's best to deal with them further up the call /// chain. /// /// This function assumes that any relevant overrides have fully-evaluated /// constants as their values (as arranged by [`process_overrides`], for /// example). /// /// [`process_overrides`]: crate::back::pipeline_constants::process_overrides /// /// # Panics /// /// - If `base` is not an indexable type, panic. /// /// - If `base` is an override-sized array, but the override's value is not a /// fully-evaluated constant expression, panic. pub fn access_needs_check( base: Handle, mut index: GuardedIndex, module: &crate::Module, expressions: &crate::Arena, info: &valid::FunctionInfo, ) -> Option { let base_inner = info[base].ty.inner_with(&module.types); // Unwrap safety: `Err` here indicates unindexable base types and invalid // length constants, but `access_needs_check` is only used by back ends, so // validation should have caught those problems. let length = base_inner.indexable_length_resolved(module).unwrap(); index.try_resolve_to_constant(expressions, module); if let (&GuardedIndex::Known(index), &IndexableLength::Known(length)) = (&index, &length) { if index < length { // Index is statically known to be in bounds, no check needed. return None; } }; Some(length) } /// Items returned by the [`bounds_check_iter`] iterator. #[cfg_attr(not(feature = "msl-out"), allow(dead_code))] pub(crate) struct BoundsCheck { /// The base of the [`Access`] or [`AccessIndex`] expression. /// /// [`Access`]: crate::Expression::Access /// [`AccessIndex`]: crate::Expression::AccessIndex pub base: Handle, /// The index being accessed. pub index: GuardedIndex, /// The length of `base`. pub length: IndexableLength, } /// Returns an iterator of accesses within the chain of `Access` and /// `AccessIndex` expressions starting from `chain` that may need to be /// bounds-checked at runtime. /// /// Items are yielded as [`BoundsCheck`] instances. /// /// Accesses through a struct are omitted, since you never need a bounds check /// for accessing a struct field. /// /// If `chain` isn't an `Access` or `AccessIndex` expression at all, the /// iterator is empty. pub(crate) fn bounds_check_iter<'a>( mut chain: Handle, module: &'a crate::Module, function: &'a crate::Function, info: &'a valid::FunctionInfo, ) -> impl Iterator + 'a { iter::from_fn(move || { let (next_expr, result) = match function.expressions[chain] { crate::Expression::Access { base, index } => { (base, Some((base, GuardedIndex::Expression(index)))) } crate::Expression::AccessIndex { base, index } => { // Don't try to check indices into structs. Validation already took // care of them, and access_needs_check doesn't handle that case. let mut base_inner = info[base].ty.inner_with(&module.types); if let crate::TypeInner::Pointer { base, .. } = *base_inner { base_inner = &module.types[base].inner; } match *base_inner { crate::TypeInner::Struct { .. } => (base, None), _ => (base, Some((base, GuardedIndex::Known(index)))), } } _ => return None, }; chain = next_expr; Some(result) }) .flatten() .filter_map(|(base, index)| { access_needs_check(base, index, module, &function.expressions, info).map(|length| { BoundsCheck { base, index, length, } }) }) } /// Returns all the types which we need out-of-bounds locals for; that is, /// all of the types which the code might attempt to get an out-of-bounds /// pointer to, in which case we yield a pointer to the out-of-bounds local /// of the correct type. pub fn oob_local_types( module: &crate::Module, function: &crate::Function, info: &valid::FunctionInfo, policies: BoundsCheckPolicies, ) -> FastHashSet> { let mut result = FastHashSet::default(); if policies.index != BoundsCheckPolicy::ReadZeroSkipWrite { return result; } for statement in &function.body { // The only situation in which we end up actually needing to create an // out-of-bounds pointer is when passing one to a function. // // This is because pointers are never baked; they're just inlined everywhere // they're used. That means that loads can just return 0, and stores can just do // nothing; functions are the only case where you actually *have* to produce a // pointer. if let crate::Statement::Call { function: callee, ref arguments, .. } = *statement { // Now go through the arguments of the function looking for pointers which need bounds checks. for (arg_info, &arg) in zip(&module.functions[callee].arguments, arguments) { match module.types[arg_info.ty].inner { crate::TypeInner::ValuePointer { .. } => { // `ValuePointer`s should only ever be used when resolving the types of // expressions, since the arena can no longer be modified at that point; things // in the arena should always use proper `Pointer`s. unreachable!("`ValuePointer` found in arena") } crate::TypeInner::Pointer { base, .. } => { if bounds_check_iter(arg, module, function, info) .next() .is_some() { result.insert(base); } } _ => continue, }; } } } result } impl GuardedIndex { /// Make a `GuardedIndex::Known` from a `GuardedIndex::Expression` if possible. /// /// Return values that are already `Known` unchanged. pub(crate) fn try_resolve_to_constant( &mut self, expressions: &crate::Arena, module: &crate::Module, ) { if let GuardedIndex::Expression(expr) = *self { *self = GuardedIndex::from_expression(expr, expressions, module); } } pub(crate) fn from_expression( expr: Handle, expressions: &crate::Arena, module: &crate::Module, ) -> Self { match module.to_ctx().get_const_val_from(expr, expressions) { Ok(value) => Self::Known(value), Err(_) => Self::Expression(expr), } } } #[derive(Clone, Copy, Debug, thiserror::Error, PartialEq)] pub enum IndexableLengthError { #[error("Type is not indexable, and has no length (validation error)")] TypeNotIndexable, #[error(transparent)] ResolveArraySizeError(#[from] super::ResolveArraySizeError), #[error("Array size is still pending")] Pending(crate::ArraySize), } impl crate::TypeInner { /// Return the length of a subscriptable type. /// /// The `self` parameter should be a handle to a vector, matrix, or array /// type, a pointer to one of those, or a value pointer. Arrays may be /// fixed-size, dynamically sized, or sized by a specializable constant. /// This function does not handle struct member references, as with /// `AccessIndex`. /// /// The value returned is appropriate for bounds checks on subscripting. /// /// Return an error if `self` does not describe a subscriptable type at all. pub fn indexable_length( &self, module: &crate::Module, ) -> Result { use crate::TypeInner as Ti; let known_length = match *self { Ti::Vector { size, .. } => size as _, Ti::Matrix { columns, .. } => columns as _, Ti::Array { size, .. } | Ti::BindingArray { size, .. } => { return size.to_indexable_length(module); } Ti::ValuePointer { size: Some(size), .. } => size as _, Ti::Pointer { base, .. } => { // When assigning types to expressions, ResolveContext::Resolve // does a separate sub-match here instead of a full recursion, // so we'll do the same. let base_inner = &module.types[base].inner; match *base_inner { Ti::Vector { size, .. } => size as _, Ti::Matrix { columns, .. } => columns as _, Ti::Array { size, .. } | Ti::BindingArray { size, .. } => { return size.to_indexable_length(module) } _ => return Err(IndexableLengthError::TypeNotIndexable), } } _ => return Err(IndexableLengthError::TypeNotIndexable), }; Ok(IndexableLength::Known(known_length)) } /// Return the length of `self`, assuming overrides are yet to be supplied. /// /// Return the number of elements in `self`: /// /// - If `self` is a runtime-sized array, then return /// [`IndexableLength::Dynamic`]. /// /// - If `self` is an override-sized array, then assume that override values /// have not yet been supplied, and return [`IndexableLength::Dynamic`]. /// /// - Otherwise, the type simply tells us the length of `self`, so return /// [`IndexableLength::Known`]. /// /// If `self` is not an indexable type at all, return an error. /// /// The difference between this and `indexable_length_resolved` is that we /// treat override-sized arrays and dynamically-sized arrays both as /// [`Dynamic`], on the assumption that our callers want to treat both cases /// as "not yet possible to check". /// /// [`Dynamic`]: IndexableLength::Dynamic pub fn indexable_length_pending( &self, module: &crate::Module, ) -> Result { let length = self.indexable_length(module); if let Err(IndexableLengthError::Pending(_)) = length { return Ok(IndexableLength::Dynamic); } length } /// Return the length of `self`, assuming overrides have been resolved. /// /// Return the number of elements in `self`: /// /// - If `self` is a runtime-sized array, then return /// [`IndexableLength::Dynamic`]. /// /// - If `self` is an override-sized array, then assume that the override's /// value is a fully-evaluated constant expression, and return /// [`IndexableLength::Known`]. Otherwise, return an error. /// /// - Otherwise, the type simply tells us the length of `self`, so return /// [`IndexableLength::Known`]. /// /// If `self` is not an indexable type at all, return an error. /// /// The difference between this and `indexable_length_pending` is /// that if `self` is override-sized, we require the override's /// value to be known. pub fn indexable_length_resolved( &self, module: &crate::Module, ) -> Result { let length = self.indexable_length(module); // If the length is override-based, then try to compute its value now. if let Err(IndexableLengthError::Pending(size)) = length { if let IndexableLength::Known(computed) = size.resolve(module.to_ctx())? { return Ok(IndexableLength::Known(computed)); } } length } } /// The number of elements in an indexable type. /// /// This summarizes the length of vectors, matrices, and arrays in a way that is /// convenient for indexing and bounds-checking code. #[derive(Debug)] pub enum IndexableLength { /// Values of this type always have the given number of elements. Known(u32), /// The number of elements is determined at runtime. Dynamic, } impl crate::ArraySize { pub const fn to_indexable_length( self, _module: &crate::Module, ) -> Result { match self { Self::Constant(length) => Ok(IndexableLength::Known(length.get())), Self::Pending(_) => Err(IndexableLengthError::Pending(self)), Self::Dynamic => Ok(IndexableLength::Dynamic), } } } ================================================ FILE: naga/src/proc/keyword_set.rs ================================================ use core::{fmt, hash}; use crate::racy_lock::RacyLock; use crate::FastHashSet; /// A case-sensitive set of strings, /// for use with [`Namer`][crate::proc::Namer] to avoid collisions with keywords and other reserved /// identifiers. /// /// This is currently implemented as a hash table. /// Future versions of Naga may change the implementation based on speed and code size /// considerations. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct KeywordSet(FastHashSet<&'static str>); impl KeywordSet { /// Returns a new mutable empty set. pub fn new() -> Self { Self::default() } /// Returns a reference to the empty set. pub fn empty() -> &'static Self { static EMPTY: RacyLock = RacyLock::new(Default::default); &EMPTY } /// Returns whether the set contains the given string. #[inline] pub fn contains(&self, identifier: &str) -> bool { self.0.contains(identifier) } } impl Default for &'static KeywordSet { fn default() -> Self { KeywordSet::empty() } } impl FromIterator<&'static str> for KeywordSet { fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect()) } } /// Accepts double references so that `KeywordSet::from_iter(&["foo"])` works. impl<'a> FromIterator<&'a &'static str> for KeywordSet { fn from_iter>(iter: T) -> Self { Self::from_iter(iter.into_iter().copied()) } } impl Extend<&'static str> for KeywordSet { #[expect( clippy::useless_conversion, reason = "doing .into_iter() sooner reduces distinct monomorphizations" )] fn extend>(&mut self, iter: T) { self.0.extend(iter.into_iter()) } } /// Accepts double references so that `.extend(&["foo"])` works. impl<'a> Extend<&'a &'static str> for KeywordSet { fn extend>(&mut self, iter: T) { self.extend(iter.into_iter().copied()) } } /// A case-insensitive, ASCII-only set of strings, /// for use with [`Namer`][crate::proc::Namer] to avoid collisions with keywords and other reserved /// identifiers. /// /// This is currently implemented as a hash table. /// Future versions of Naga may change the implementation based on speed and code size /// considerations. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct CaseInsensitiveKeywordSet(FastHashSet>); impl CaseInsensitiveKeywordSet { /// Returns a new mutable empty set. pub fn new() -> Self { Self::default() } /// Returns a reference to the empty set. pub fn empty() -> &'static Self { static EMPTY: RacyLock = RacyLock::new(Default::default); &EMPTY } /// Returns whether the set contains the given string, with comparison /// by [`str::eq_ignore_ascii_case()`]. #[inline] pub fn contains(&self, identifier: &str) -> bool { self.0.contains(&AsciiUniCase(identifier)) } } impl Default for &'static CaseInsensitiveKeywordSet { fn default() -> Self { CaseInsensitiveKeywordSet::empty() } } impl FromIterator<&'static str> for CaseInsensitiveKeywordSet { fn from_iter>(iter: T) -> Self { Self( iter.into_iter() .inspect(debug_assert_ascii) .map(AsciiUniCase) .collect(), ) } } /// Accepts double references so that `CaseInsensitiveKeywordSet::from_iter(&["foo"])` works. impl<'a> FromIterator<&'a &'static str> for CaseInsensitiveKeywordSet { fn from_iter>(iter: T) -> Self { Self::from_iter(iter.into_iter().copied()) } } impl Extend<&'static str> for CaseInsensitiveKeywordSet { fn extend>(&mut self, iter: T) { self.0.extend( iter.into_iter() .inspect(debug_assert_ascii) .map(AsciiUniCase), ) } } /// Accepts double references so that `.extend(&["foo"])` works. impl<'a> Extend<&'a &'static str> for CaseInsensitiveKeywordSet { fn extend>(&mut self, iter: T) { self.extend(iter.into_iter().copied()) } } /// A string wrapper type with an ascii case insensitive Eq and Hash impl #[derive(Clone, Copy)] struct AsciiUniCase + ?Sized>(S); impl> fmt::Debug for AsciiUniCase { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.as_ref().fmt(f) } } impl> PartialEq for AsciiUniCase { #[inline] fn eq(&self, other: &Self) -> bool { self.0.as_ref().eq_ignore_ascii_case(other.0.as_ref()) } } impl> Eq for AsciiUniCase {} impl> hash::Hash for AsciiUniCase { #[inline] fn hash(&self, hasher: &mut H) { for byte in self .0 .as_ref() .as_bytes() .iter() .map(|b| b.to_ascii_lowercase()) { hasher.write_u8(byte); } } } fn debug_assert_ascii(s: &&'static str) { debug_assert!(s.is_ascii(), "{s:?} not ASCII") } ================================================ FILE: naga/src/proc/layouter.rs ================================================ use core::{fmt::Display, num::NonZeroU32, ops}; use crate::{ arena::{Handle, HandleVec}, valid::MAX_TYPE_SIZE, }; /// A newtype struct where its only valid values are powers of 2 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct Alignment(NonZeroU32); impl Alignment { pub const ONE: Self = Self(NonZeroU32::new(1).unwrap()); pub const TWO: Self = Self(NonZeroU32::new(2).unwrap()); pub const FOUR: Self = Self(NonZeroU32::new(4).unwrap()); pub const EIGHT: Self = Self(NonZeroU32::new(8).unwrap()); pub const SIXTEEN: Self = Self(NonZeroU32::new(16).unwrap()); pub const MIN_UNIFORM: Self = Self::SIXTEEN; pub const fn new(n: u32) -> Option { if n.is_power_of_two() { // Value can't be 0 since we just checked if it's a power of 2. Some(Self(NonZeroU32::new(n).unwrap())) } else { None } } /// # Panics /// If `width` is not a power of 2 pub const fn from_width(width: u8) -> Self { Self::new(width as u32).unwrap() } /// Returns whether or not `n` is a multiple of this alignment. pub const fn is_aligned(&self, n: u32) -> bool { // equivalent to: `n % self.0.get() == 0` but much faster n & (self.0.get() - 1) == 0 } /// Round `n` up to the nearest alignment boundary. pub const fn round_up(&self, n: u32) -> u32 { // equivalent to: // match n % self.0.get() { // 0 => n, // rem => n + (self.0.get() - rem), // } let mask = self.0.get() - 1; (n + mask) & !mask } } impl Display for Alignment { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.0.get().fmt(f) } } impl ops::Mul for Alignment { type Output = u32; fn mul(self, rhs: u32) -> Self::Output { self.0.get() * rhs } } impl ops::Mul for Alignment { type Output = Alignment; fn mul(self, rhs: Alignment) -> Self::Output { // Both lhs and rhs are powers of 2, the result will be a power of 2. Self(NonZeroU32::new(self.0.get() * rhs.0.get()).unwrap()) } } impl From for Alignment { fn from(size: crate::VectorSize) -> Self { match size { crate::VectorSize::Bi => Alignment::TWO, crate::VectorSize::Tri => Alignment::FOUR, crate::VectorSize::Quad => Alignment::FOUR, } } } impl From for Alignment { fn from(size: crate::CooperativeSize) -> Self { Self(NonZeroU32::new(size as u32).unwrap()) } } /// Size and alignment information for a type. #[derive(Clone, Copy, Debug, Hash, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct TypeLayout { pub size: u32, pub alignment: Alignment, } impl TypeLayout { /// Produce the stride as if this type is a base of an array. pub const fn to_stride(&self) -> u32 { self.alignment.round_up(self.size) } } /// Helper processor that derives the sizes of all types. /// /// `Layouter` uses the default layout algorithm/table, described in /// [WGSL §4.3.7, "Memory Layout"] /// /// A `Layouter` may be indexed by `Handle` values: `layouter[handle]` is the /// layout of the type whose handle is `handle`. /// /// [WGSL §4.3.7, "Memory Layout"](https://gpuweb.github.io/gpuweb/wgsl/#memory-layouts) #[derive(Debug, Default)] pub struct Layouter { /// Layouts for types in an arena. layouts: HandleVec, } impl ops::Index> for Layouter { type Output = TypeLayout; fn index(&self, handle: Handle) -> &TypeLayout { &self.layouts[handle] } } /// Errors generated by the `Layouter`. /// /// All of these errors can be produced when validating an arbitrary module. /// When processing WGSL source, only the `TooLarge` error should be /// produced by the `Layouter`, as the front-end should not produce IR /// that would result in the other errors. #[derive(Clone, Copy, Debug, PartialEq, thiserror::Error)] pub enum LayoutErrorInner { #[error("Array element type {0:?} doesn't exist")] InvalidArrayElementType(Handle), #[error("Struct member[{0}] type {1:?} doesn't exist")] InvalidStructMemberType(u32, Handle), #[error("Type width must be a power of two")] NonPowerOfTwoWidth, #[error("Size exceeds limit of {MAX_TYPE_SIZE} bytes")] TooLarge, } #[derive(Clone, Copy, Debug, PartialEq, thiserror::Error)] #[error("Error laying out type {ty:?}: {inner}")] pub struct LayoutError { pub ty: Handle, pub inner: LayoutErrorInner, } impl LayoutErrorInner { const fn with(self, ty: Handle) -> LayoutError { LayoutError { ty, inner: self } } } impl Layouter { /// Remove all entries from this `Layouter`, retaining storage. pub fn clear(&mut self) { self.layouts.clear(); } #[expect(rustdoc::private_intra_doc_links)] /// Extend this `Layouter` with layouts for any new entries in `gctx.types`. /// /// Ensure that every type in `gctx.types` has a corresponding [TypeLayout] /// in [`Self::layouts`]. /// /// Some front ends need to be able to compute layouts for existing types /// while module construction is still in progress and new types are still /// being added. This function assumes that the `TypeLayout` values already /// present in `self.layouts` cover their corresponding entries in `types`, /// and extends `self.layouts` as needed to cover the rest. Thus, a front /// end can call this function at any time, passing its current type and /// constant arenas, and then assume that layouts are available for all /// types. #[allow(clippy::or_fun_call)] pub fn update(&mut self, gctx: super::GlobalCtx) -> Result<(), LayoutError> { use crate::TypeInner as Ti; for (ty_handle, ty) in gctx.types.iter().skip(self.layouts.len()) { let size = ty .inner .try_size(gctx) .ok_or_else(|| LayoutErrorInner::TooLarge.with(ty_handle))?; let layout = match ty.inner { Ti::Scalar(scalar) | Ti::Atomic(scalar) => { let alignment = Alignment::new(scalar.width as u32) .ok_or(LayoutErrorInner::NonPowerOfTwoWidth.with(ty_handle))?; TypeLayout { size, alignment } } Ti::Vector { size: vec_size, scalar, } => { let alignment = Alignment::new(scalar.width as u32) .ok_or(LayoutErrorInner::NonPowerOfTwoWidth.with(ty_handle))?; TypeLayout { size, alignment: Alignment::from(vec_size) * alignment, } } Ti::Matrix { columns: _, rows, scalar, } => { let alignment = Alignment::new(scalar.width as u32) .ok_or(LayoutErrorInner::NonPowerOfTwoWidth.with(ty_handle))?; TypeLayout { size, alignment: Alignment::from(rows) * alignment, } } Ti::CooperativeMatrix { columns: _, rows, scalar, role: _, } => { let alignment = Alignment::new(scalar.width as u32) .ok_or(LayoutErrorInner::NonPowerOfTwoWidth.with(ty_handle))?; TypeLayout { size, alignment: Alignment::from(rows) * alignment, } } Ti::Pointer { .. } | Ti::ValuePointer { .. } => TypeLayout { size, alignment: Alignment::ONE, }, Ti::Array { base, stride: _, size: _, } => TypeLayout { size, alignment: if base < ty_handle { self[base].alignment } else { return Err(LayoutErrorInner::InvalidArrayElementType(base).with(ty_handle)); }, }, Ti::Struct { span, ref members } => { let mut alignment = Alignment::ONE; for (index, member) in members.iter().enumerate() { alignment = if member.ty < ty_handle { alignment.max(self[member.ty].alignment) } else { return Err(LayoutErrorInner::InvalidStructMemberType( index as u32, member.ty, ) .with(ty_handle)); }; } TypeLayout { size: span, alignment, } } Ti::Image { .. } | Ti::Sampler { .. } | Ti::AccelerationStructure { .. } | Ti::RayQuery { .. } | Ti::BindingArray { .. } => TypeLayout { size, alignment: Alignment::ONE, }, }; debug_assert!(size <= layout.size); self.layouts.insert(ty_handle, layout); } Ok(()) } } ================================================ FILE: naga/src/proc/mod.rs ================================================ /*! [`Module`](super::Module) processing functionality. */ mod constant_evaluator; mod emitter; pub mod index; mod keyword_set; mod layouter; mod namer; mod overloads; mod terminator; mod type_methods; mod typifier; pub use constant_evaluator::{ ConstantEvaluator, ConstantEvaluatorError, ExpressionKind, ExpressionKindTracker, }; pub use emitter::Emitter; pub use index::{BoundsCheckPolicies, BoundsCheckPolicy, IndexableLength, IndexableLengthError}; pub use keyword_set::{CaseInsensitiveKeywordSet, KeywordSet}; pub use layouter::{Alignment, LayoutError, LayoutErrorInner, Layouter, TypeLayout}; pub use namer::{EntryPointIndex, ExternalTextureNameKey, NameKey, Namer}; pub use overloads::{Conclusion, MissingSpecialType, OverloadSet, Rule}; pub use terminator::ensure_block_returns; use thiserror::Error; pub use type_methods::{ concrete_int_scalars, min_max_float_representable_by, vector_size_str, vector_sizes, }; pub use typifier::{compare_types, ResolveContext, ResolveError, TypeResolution}; use crate::non_max_u32::NonMaxU32; impl From for super::Scalar { fn from(format: super::StorageFormat) -> Self { use super::{ScalarKind as Sk, StorageFormat as Sf}; let kind = match format { Sf::R8Unorm => Sk::Float, Sf::R8Snorm => Sk::Float, Sf::R8Uint => Sk::Uint, Sf::R8Sint => Sk::Sint, Sf::R16Uint => Sk::Uint, Sf::R16Sint => Sk::Sint, Sf::R16Float => Sk::Float, Sf::Rg8Unorm => Sk::Float, Sf::Rg8Snorm => Sk::Float, Sf::Rg8Uint => Sk::Uint, Sf::Rg8Sint => Sk::Sint, Sf::R32Uint => Sk::Uint, Sf::R32Sint => Sk::Sint, Sf::R32Float => Sk::Float, Sf::Rg16Uint => Sk::Uint, Sf::Rg16Sint => Sk::Sint, Sf::Rg16Float => Sk::Float, Sf::Rgba8Unorm => Sk::Float, Sf::Rgba8Snorm => Sk::Float, Sf::Rgba8Uint => Sk::Uint, Sf::Rgba8Sint => Sk::Sint, Sf::Bgra8Unorm => Sk::Float, Sf::Rgb10a2Uint => Sk::Uint, Sf::Rgb10a2Unorm => Sk::Float, Sf::Rg11b10Ufloat => Sk::Float, Sf::R64Uint => Sk::Uint, Sf::Rg32Uint => Sk::Uint, Sf::Rg32Sint => Sk::Sint, Sf::Rg32Float => Sk::Float, Sf::Rgba16Uint => Sk::Uint, Sf::Rgba16Sint => Sk::Sint, Sf::Rgba16Float => Sk::Float, Sf::Rgba32Uint => Sk::Uint, Sf::Rgba32Sint => Sk::Sint, Sf::Rgba32Float => Sk::Float, Sf::R16Unorm => Sk::Float, Sf::R16Snorm => Sk::Float, Sf::Rg16Unorm => Sk::Float, Sf::Rg16Snorm => Sk::Float, Sf::Rgba16Unorm => Sk::Float, Sf::Rgba16Snorm => Sk::Float, }; let width = match format { Sf::R64Uint => 8, _ => 4, }; super::Scalar { kind, width } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum HashableLiteral { F64(u64), F32(u32), F16(u16), U32(u32), I32(i32), U64(u64), I64(i64), Bool(bool), AbstractInt(i64), AbstractFloat(u64), } impl From for HashableLiteral { fn from(l: crate::Literal) -> Self { match l { crate::Literal::F64(v) => Self::F64(v.to_bits()), crate::Literal::F32(v) => Self::F32(v.to_bits()), crate::Literal::F16(v) => Self::F16(v.to_bits()), crate::Literal::U32(v) => Self::U32(v), crate::Literal::I32(v) => Self::I32(v), crate::Literal::U64(v) => Self::U64(v), crate::Literal::I64(v) => Self::I64(v), crate::Literal::Bool(v) => Self::Bool(v), crate::Literal::AbstractInt(v) => Self::AbstractInt(v), crate::Literal::AbstractFloat(v) => Self::AbstractFloat(v.to_bits()), } } } impl crate::Literal { pub const fn new(value: u8, scalar: crate::Scalar) -> Option { match (value, scalar.kind, scalar.width) { (value, crate::ScalarKind::Float, 8) => Some(Self::F64(value as _)), (value, crate::ScalarKind::Float, 4) => Some(Self::F32(value as _)), (value, crate::ScalarKind::Float, 2) => { Some(Self::F16(half::f16::from_f32_const(value as _))) } (value, crate::ScalarKind::Uint, 4) => Some(Self::U32(value as _)), (value, crate::ScalarKind::Sint, 4) => Some(Self::I32(value as _)), (value, crate::ScalarKind::Uint, 8) => Some(Self::U64(value as _)), (value, crate::ScalarKind::Sint, 8) => Some(Self::I64(value as _)), (1, crate::ScalarKind::Bool, crate::BOOL_WIDTH) => Some(Self::Bool(true)), (0, crate::ScalarKind::Bool, crate::BOOL_WIDTH) => Some(Self::Bool(false)), (value, crate::ScalarKind::AbstractInt, 8) => Some(Self::AbstractInt(value as _)), (value, crate::ScalarKind::AbstractFloat, 8) => Some(Self::AbstractFloat(value as _)), _ => None, } } pub const fn zero(scalar: crate::Scalar) -> Option { Self::new(0, scalar) } pub const fn one(scalar: crate::Scalar) -> Option { Self::new(1, scalar) } pub const fn minus_one(scalar: crate::Scalar) -> Option { match (scalar.kind, scalar.width) { (crate::ScalarKind::Float, 8) => Some(Self::F64(-1.0)), (crate::ScalarKind::Float, 4) => Some(Self::F32(-1.0)), (crate::ScalarKind::Float, 2) => Some(Self::F16(half::f16::from_f32_const(-1.0))), (crate::ScalarKind::Sint, 8) => Some(Self::I64(-1)), (crate::ScalarKind::Sint, 4) => Some(Self::I32(-1)), (crate::ScalarKind::AbstractInt, 8) => Some(Self::AbstractInt(-1)), _ => None, } } pub const fn width(&self) -> crate::Bytes { match *self { Self::F64(_) | Self::I64(_) | Self::U64(_) => 8, Self::F32(_) | Self::U32(_) | Self::I32(_) => 4, Self::F16(_) => 2, Self::Bool(_) => crate::BOOL_WIDTH, Self::AbstractInt(_) | Self::AbstractFloat(_) => crate::ABSTRACT_WIDTH, } } pub const fn scalar(&self) -> crate::Scalar { match *self { Self::F64(_) => crate::Scalar::F64, Self::F32(_) => crate::Scalar::F32, Self::F16(_) => crate::Scalar::F16, Self::U32(_) => crate::Scalar::U32, Self::I32(_) => crate::Scalar::I32, Self::U64(_) => crate::Scalar::U64, Self::I64(_) => crate::Scalar::I64, Self::Bool(_) => crate::Scalar::BOOL, Self::AbstractInt(_) => crate::Scalar::ABSTRACT_INT, Self::AbstractFloat(_) => crate::Scalar::ABSTRACT_FLOAT, } } pub const fn scalar_kind(&self) -> crate::ScalarKind { self.scalar().kind } pub const fn ty_inner(&self) -> crate::TypeInner { crate::TypeInner::Scalar(self.scalar()) } } impl TryFrom for u32 { type Error = ConstValueError; fn try_from(value: crate::Literal) -> Result { match value { crate::Literal::U32(value) => Ok(value), crate::Literal::I32(value) => value.try_into().map_err(|_| ConstValueError::Negative), _ => Err(ConstValueError::InvalidType), } } } impl TryFrom for bool { type Error = ConstValueError; fn try_from(value: crate::Literal) -> Result { match value { crate::Literal::Bool(value) => Ok(value), _ => Err(ConstValueError::InvalidType), } } } impl super::AddressSpace { pub fn access(self) -> crate::StorageAccess { use crate::StorageAccess as Sa; match self { crate::AddressSpace::Function | crate::AddressSpace::Private | crate::AddressSpace::WorkGroup => Sa::LOAD | Sa::STORE, crate::AddressSpace::Uniform => Sa::LOAD, crate::AddressSpace::Storage { access } => access, crate::AddressSpace::Handle => Sa::LOAD, crate::AddressSpace::Immediate => Sa::LOAD, // TaskPayload isn't always writable, but this is checked for elsewhere, // when not using multiple payloads and matching the entry payload is checked. crate::AddressSpace::TaskPayload => Sa::LOAD | Sa::STORE, crate::AddressSpace::RayPayload | crate::AddressSpace::IncomingRayPayload => { Sa::LOAD | Sa::STORE } } } } impl super::MathFunction { pub const fn argument_count(&self) -> usize { match *self { // comparison Self::Abs => 1, Self::Min => 2, Self::Max => 2, Self::Clamp => 3, Self::Saturate => 1, // trigonometry Self::Cos => 1, Self::Cosh => 1, Self::Sin => 1, Self::Sinh => 1, Self::Tan => 1, Self::Tanh => 1, Self::Acos => 1, Self::Asin => 1, Self::Atan => 1, Self::Atan2 => 2, Self::Asinh => 1, Self::Acosh => 1, Self::Atanh => 1, Self::Radians => 1, Self::Degrees => 1, // decomposition Self::Ceil => 1, Self::Floor => 1, Self::Round => 1, Self::Fract => 1, Self::Trunc => 1, Self::Modf => 1, Self::Frexp => 1, Self::Ldexp => 2, // exponent Self::Exp => 1, Self::Exp2 => 1, Self::Log => 1, Self::Log2 => 1, Self::Pow => 2, // geometry Self::Dot => 2, Self::Dot4I8Packed => 2, Self::Dot4U8Packed => 2, Self::Outer => 2, Self::Cross => 2, Self::Distance => 2, Self::Length => 1, Self::Normalize => 1, Self::FaceForward => 3, Self::Reflect => 2, Self::Refract => 3, // computational Self::Sign => 1, Self::Fma => 3, Self::Mix => 3, Self::Step => 2, Self::SmoothStep => 3, Self::Sqrt => 1, Self::InverseSqrt => 1, Self::Inverse => 1, Self::Transpose => 1, Self::Determinant => 1, Self::QuantizeToF16 => 1, // bits Self::CountTrailingZeros => 1, Self::CountLeadingZeros => 1, Self::CountOneBits => 1, Self::ReverseBits => 1, Self::ExtractBits => 3, Self::InsertBits => 4, Self::FirstTrailingBit => 1, Self::FirstLeadingBit => 1, // data packing Self::Pack4x8snorm => 1, Self::Pack4x8unorm => 1, Self::Pack2x16snorm => 1, Self::Pack2x16unorm => 1, Self::Pack2x16float => 1, Self::Pack4xI8 => 1, Self::Pack4xU8 => 1, Self::Pack4xI8Clamp => 1, Self::Pack4xU8Clamp => 1, // data unpacking Self::Unpack4x8snorm => 1, Self::Unpack4x8unorm => 1, Self::Unpack2x16snorm => 1, Self::Unpack2x16unorm => 1, Self::Unpack2x16float => 1, Self::Unpack4xI8 => 1, Self::Unpack4xU8 => 1, } } } impl crate::Expression { /// Returns true if the expression is considered emitted at the start of a function. pub const fn needs_pre_emit(&self) -> bool { match *self { Self::Literal(_) | Self::Constant(_) | Self::Override(_) | Self::ZeroValue(_) | Self::FunctionArgument(_) | Self::GlobalVariable(_) | Self::LocalVariable(_) => true, _ => false, } } /// Return true if this expression is a dynamic array/vector/matrix index, /// for [`Access`]. /// /// This method returns true if this expression is a dynamically computed /// index, and as such can only be used to index matrices when they appear /// behind a pointer. See the documentation for [`Access`] for details. /// /// Note, this does not check the _type_ of the given expression. It's up to /// the caller to establish that the `Access` expression is well-typed /// through other means, like [`ResolveContext`]. /// /// [`Access`]: crate::Expression::Access /// [`ResolveContext`]: crate::proc::ResolveContext pub const fn is_dynamic_index(&self) -> bool { match *self { Self::Literal(_) | Self::ZeroValue(_) | Self::Constant(_) => false, _ => true, } } } impl crate::Function { /// Return the global variable being accessed by the expression `pointer`. /// /// Assuming that `pointer` is a series of `Access` and `AccessIndex` /// expressions that ultimately access some part of a `GlobalVariable`, /// return a handle for that global. /// /// If the expression does not ultimately access a global variable, return /// `None`. pub fn originating_global( &self, mut pointer: crate::Handle, ) -> Option> { loop { pointer = match self.expressions[pointer] { crate::Expression::Access { base, .. } => base, crate::Expression::AccessIndex { base, .. } => base, crate::Expression::GlobalVariable(handle) => return Some(handle), crate::Expression::LocalVariable(_) => return None, crate::Expression::FunctionArgument(_) => return None, // There are no other expressions that produce pointer values. _ => unreachable!(), } } } } impl crate::SampleLevel { pub const fn implicit_derivatives(&self) -> bool { match *self { Self::Auto | Self::Bias(_) => true, Self::Zero | Self::Exact(_) | Self::Gradient { .. } => false, } } } impl crate::Binding { pub const fn to_built_in(&self) -> Option { match *self { crate::Binding::BuiltIn(built_in) => Some(built_in), Self::Location { .. } => None, } } } impl super::SwizzleComponent { pub const XYZW: [Self; 4] = [Self::X, Self::Y, Self::Z, Self::W]; pub const fn index(&self) -> u32 { match *self { Self::X => 0, Self::Y => 1, Self::Z => 2, Self::W => 3, } } pub const fn from_index(idx: u32) -> Self { match idx { 0 => Self::X, 1 => Self::Y, 2 => Self::Z, _ => Self::W, } } } impl super::ImageClass { pub const fn is_multisampled(self) -> bool { match self { crate::ImageClass::Sampled { multi, .. } | crate::ImageClass::Depth { multi } => multi, crate::ImageClass::Storage { .. } => false, crate::ImageClass::External => false, } } pub const fn is_mipmapped(self) -> bool { match self { crate::ImageClass::Sampled { multi, .. } | crate::ImageClass::Depth { multi } => !multi, crate::ImageClass::Storage { .. } => false, crate::ImageClass::External => false, } } pub const fn is_depth(self) -> bool { matches!(self, crate::ImageClass::Depth { .. }) } } impl crate::Module { pub const fn to_ctx(&self) -> GlobalCtx<'_> { GlobalCtx { types: &self.types, constants: &self.constants, overrides: &self.overrides, global_expressions: &self.global_expressions, } } pub fn compare_types(&self, lhs: &TypeResolution, rhs: &TypeResolution) -> bool { compare_types(lhs, rhs, &self.types) } } #[derive(Debug)] pub enum ConstValueError { NonConst, Negative, InvalidType, } impl From for ConstValueError { fn from(_: core::convert::Infallible) -> Self { unreachable!() } } #[derive(Clone, Copy)] pub struct GlobalCtx<'a> { pub types: &'a crate::UniqueArena, pub constants: &'a crate::Arena, pub overrides: &'a crate::Arena, pub global_expressions: &'a crate::Arena, } impl GlobalCtx<'_> { /// Try to evaluate the expression in `self.global_expressions` using its `handle` /// and return it as a `T: TryFrom`. /// /// This currently only evaluates scalar expressions. If adding support for vectors, /// consider changing `valid::expression::validate_constant_shift_amounts` to use that /// support. #[cfg_attr( not(any( feature = "glsl-in", feature = "spv-in", feature = "wgsl-in", glsl_out, hlsl_out, msl_out, wgsl_out )), allow(dead_code) )] pub(super) fn get_const_val( &self, handle: crate::Handle, ) -> Result where T: TryFrom, E: Into, { self.get_const_val_from(handle, self.global_expressions) } pub(super) fn get_const_val_from( &self, handle: crate::Handle, arena: &crate::Arena, ) -> Result where T: TryFrom, E: Into, { fn get( gctx: GlobalCtx, handle: crate::Handle, arena: &crate::Arena, ) -> Option { match arena[handle] { crate::Expression::Literal(literal) => Some(literal), crate::Expression::ZeroValue(ty) => match gctx.types[ty].inner { crate::TypeInner::Scalar(scalar) => crate::Literal::zero(scalar), _ => None, }, _ => None, } } let value = match arena[handle] { crate::Expression::Constant(c) => { get(*self, self.constants[c].init, self.global_expressions) } _ => get(*self, handle, arena), }; match value { Some(v) => v.try_into().map_err(Into::into), None => Err(ConstValueError::NonConst), } } pub fn compare_types(&self, lhs: &TypeResolution, rhs: &TypeResolution) -> bool { compare_types(lhs, rhs, self.types) } } #[derive(Error, Debug, Clone, Copy, PartialEq)] pub enum ResolveArraySizeError { #[error("array element count must be positive (> 0)")] ExpectedPositiveArrayLength, #[error("internal: array size override has not been resolved")] NonConstArrayLength, } impl crate::ArraySize { /// Return the number of elements that `size` represents, if known at code generation time. /// /// If `size` is override-based, return an error unless the override's /// initializer is a fully evaluated constant expression. You can call /// [`pipeline_constants::process_overrides`] to supply values for a /// module's overrides and ensure their initializers are fully evaluated, as /// this function expects. /// /// [`pipeline_constants::process_overrides`]: crate::back::pipeline_constants::process_overrides pub fn resolve(&self, gctx: GlobalCtx) -> Result { match *self { crate::ArraySize::Constant(length) => Ok(IndexableLength::Known(length.get())), crate::ArraySize::Pending(handle) => { let Some(expr) = gctx.overrides[handle].init else { return Err(ResolveArraySizeError::NonConstArrayLength); }; let length = gctx.get_const_val(expr).map_err(|err| match err { ConstValueError::NonConst => ResolveArraySizeError::NonConstArrayLength, ConstValueError::Negative | ConstValueError::InvalidType => { ResolveArraySizeError::ExpectedPositiveArrayLength } })?; if length == 0 { return Err(ResolveArraySizeError::ExpectedPositiveArrayLength); } Ok(IndexableLength::Known(length)) } crate::ArraySize::Dynamic => Ok(IndexableLength::Dynamic), } } } /// Return an iterator over the individual components assembled by a /// `Compose` expression. /// /// Given `ty` and `components` from an `Expression::Compose`, return an /// iterator over the components of the resulting value. /// /// Normally, this would just be an iterator over `components`. However, /// `Compose` expressions can concatenate vectors, in which case the i'th /// value being composed is not generally the i'th element of `components`. /// This function consults `ty` to decide if this concatenation is occurring, /// and returns an iterator that produces the components of the result of /// the `Compose` expression in either case. pub fn flatten_compose<'arenas>( ty: crate::Handle, components: &'arenas [crate::Handle], expressions: &'arenas crate::Arena, types: &'arenas crate::UniqueArena, ) -> impl Iterator> + 'arenas { // Returning `impl Iterator` is a bit tricky. We may or may not // want to flatten the components, but we have to settle on a // single concrete type to return. This function returns a single // iterator chain that handles both the flattening and // non-flattening cases. let (size, is_vector) = if let crate::TypeInner::Vector { size, .. } = types[ty].inner { (size as usize, true) } else { (components.len(), false) }; /// Flatten `Compose` expressions if `is_vector` is true. fn flatten_compose<'c>( component: &'c crate::Handle, is_vector: bool, expressions: &'c crate::Arena, ) -> &'c [crate::Handle] { if is_vector { if let crate::Expression::Compose { ty: _, components: ref subcomponents, } = expressions[*component] { return subcomponents; } } core::slice::from_ref(component) } /// Flatten `Splat` expressions if `is_vector` is true. fn flatten_splat<'c>( component: &'c crate::Handle, is_vector: bool, expressions: &'c crate::Arena, ) -> impl Iterator> { let mut expr = *component; let mut count = 1; if is_vector { if let crate::Expression::Splat { size, value } = expressions[expr] { expr = value; count = size as usize; } } core::iter::repeat_n(expr, count) } // Expressions like `vec4(vec3(vec2(6, 7), 8), 9)` require us to // flatten up to two levels of `Compose` expressions. // // Expressions like `vec4(vec3(1.0), 1.0)` require us to flatten // `Splat` expressions. Fortunately, the operand of a `Splat` must // be a scalar, so we can stop there. components .iter() .flat_map(move |component| flatten_compose(component, is_vector, expressions)) .flat_map(move |component| flatten_compose(component, is_vector, expressions)) .flat_map(move |component| flatten_splat(component, is_vector, expressions)) .take(size) } impl super::ShaderStage { pub const fn compute_like(self) -> bool { match self { Self::Vertex | Self::Fragment => false, Self::Compute | Self::Task | Self::Mesh => true, Self::RayGeneration | Self::AnyHit | Self::ClosestHit | Self::Miss => false, } } /// Mesh or task shader pub const fn mesh_like(self) -> bool { match self { Self::Task | Self::Mesh => true, _ => false, } } } #[test] fn test_matrix_size() { let module = crate::Module::default(); assert_eq!( crate::TypeInner::Matrix { columns: crate::VectorSize::Tri, rows: crate::VectorSize::Tri, scalar: crate::Scalar::F32, } .size(module.to_ctx()), 48, ); } impl crate::Module { /// Extracts mesh shader info from a mesh output global variable. Used in frontends /// and by validators. This only validates the output variable itself, and not the /// vertex and primitive output types. /// /// The output contains the extracted mesh stage info, with overrides unset, /// and then the overrides separately. This is because the overrides should be /// treated as expressions elsewhere, but that requires mutably modifying the /// module and the expressions should only be created at parse time, not validation /// time. #[allow(clippy::type_complexity)] pub fn analyze_mesh_shader_info( &self, gv: crate::Handle, ) -> ( crate::MeshStageInfo, [Option>; 2], Option>, ) { use crate::span::AddSpan; use crate::valid::EntryPointError; #[derive(Default)] struct OutError { pub inner: Option, } impl OutError { pub fn set(&mut self, err: EntryPointError) { if self.inner.is_none() { self.inner = Some(err); } } } // Used to temporarily initialize stuff let null_type = crate::Handle::new(NonMaxU32::new(0).unwrap()); let mut output = crate::MeshStageInfo { topology: crate::MeshOutputTopology::Triangles, max_vertices: 0, max_vertices_override: None, max_primitives: 0, max_primitives_override: None, vertex_output_type: null_type, primitive_output_type: null_type, output_variable: gv, }; // Stores the error to output, if any. let mut error = OutError::default(); let r#type = &self.types[self.global_variables[gv].ty].inner; let mut topology = output.topology; // Max, max override, type let mut vertex_info = (0, None, null_type); let mut primitive_info = (0, None, null_type); match r#type { &crate::TypeInner::Struct { ref members, .. } => { let mut builtins = crate::FastHashSet::default(); for member in members { match member.binding { Some(crate::Binding::BuiltIn(crate::BuiltIn::VertexCount)) => { // Must have type u32 if self.types[member.ty].inner.scalar() != Some(crate::Scalar::U32) { error.set(EntryPointError::BadMeshOutputVariableField); } // Each builtin should only occur once if builtins.contains(&crate::BuiltIn::VertexCount) { error.set(EntryPointError::BadMeshOutputVariableType); } builtins.insert(crate::BuiltIn::VertexCount); } Some(crate::Binding::BuiltIn(crate::BuiltIn::PrimitiveCount)) => { // Must have type u32 if self.types[member.ty].inner.scalar() != Some(crate::Scalar::U32) { error.set(EntryPointError::BadMeshOutputVariableField); } // Each builtin should only occur once if builtins.contains(&crate::BuiltIn::PrimitiveCount) { error.set(EntryPointError::BadMeshOutputVariableType); } builtins.insert(crate::BuiltIn::PrimitiveCount); } Some(crate::Binding::BuiltIn( crate::BuiltIn::Vertices | crate::BuiltIn::Primitives, )) => { let ty = &self.types[member.ty].inner; // Analyze the array type to determine size and vertex/primitive type let (a, b, c) = match ty { &crate::TypeInner::Array { base, size, .. } => { let ty = base; let (max, max_override) = match size { crate::ArraySize::Constant(a) => (a.get(), None), crate::ArraySize::Pending(o) => (0, Some(o)), crate::ArraySize::Dynamic => { error.set(EntryPointError::BadMeshOutputVariableField); (0, None) } }; (max, max_override, ty) } _ => { error.set(EntryPointError::BadMeshOutputVariableField); (0, None, null_type) } }; if matches!( member.binding, Some(crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) ) { // Primitives require special analysis to determine topology primitive_info = (a, b, c); match self.types[c].inner { crate::TypeInner::Struct { ref members, .. } => { for member in members { match member.binding { Some(crate::Binding::BuiltIn( crate::BuiltIn::PointIndex, )) => { topology = crate::MeshOutputTopology::Points; } Some(crate::Binding::BuiltIn( crate::BuiltIn::LineIndices, )) => { topology = crate::MeshOutputTopology::Lines; } Some(crate::Binding::BuiltIn( crate::BuiltIn::TriangleIndices, )) => { topology = crate::MeshOutputTopology::Triangles; } _ => (), } } } _ => (), } // Each builtin should only occur once if builtins.contains(&crate::BuiltIn::Primitives) { error.set(EntryPointError::BadMeshOutputVariableType); } builtins.insert(crate::BuiltIn::Primitives); } else { vertex_info = (a, b, c); // Each builtin should only occur once if builtins.contains(&crate::BuiltIn::Vertices) { error.set(EntryPointError::BadMeshOutputVariableType); } builtins.insert(crate::BuiltIn::Vertices); } } _ => error.set(EntryPointError::BadMeshOutputVariableType), } } output = crate::MeshStageInfo { topology, max_vertices: vertex_info.0, max_vertices_override: None, vertex_output_type: vertex_info.2, max_primitives: primitive_info.0, max_primitives_override: None, primitive_output_type: primitive_info.2, ..output } } _ => error.set(EntryPointError::BadMeshOutputVariableType), } ( output, [vertex_info.1, primitive_info.1], error .inner .map(|a| a.with_span_handle(self.global_variables[gv].ty, &self.types)), ) } pub fn uses_mesh_shaders(&self) -> bool { let binding_uses_mesh = |b: &crate::Binding| { matches!( b, crate::Binding::BuiltIn( crate::BuiltIn::MeshTaskSize | crate::BuiltIn::CullPrimitive | crate::BuiltIn::PointIndex | crate::BuiltIn::LineIndices | crate::BuiltIn::TriangleIndices | crate::BuiltIn::VertexCount | crate::BuiltIn::Vertices | crate::BuiltIn::PrimitiveCount | crate::BuiltIn::Primitives, ) | crate::Binding::Location { per_primitive: true, .. } ) }; for (_, ty) in self.types.iter() { match ty.inner { crate::TypeInner::Struct { ref members, .. } => { for binding in members.iter().filter_map(|m| m.binding.as_ref()) { if binding_uses_mesh(binding) { return true; } } } _ => (), } } for ep in &self.entry_points { if matches!( ep.stage, crate::ShaderStage::Mesh | crate::ShaderStage::Task ) { return true; } for binding in ep .function .arguments .iter() .filter_map(|arg| arg.binding.as_ref()) .chain( ep.function .result .iter() .filter_map(|res| res.binding.as_ref()), ) { if binding_uses_mesh(binding) { return true; } } } if self .global_variables .iter() .any(|gv| gv.1.space == crate::AddressSpace::TaskPayload) { return true; } false } } impl crate::MeshOutputTopology { pub const fn to_builtin(self) -> crate::BuiltIn { match self { Self::Points => crate::BuiltIn::PointIndex, Self::Lines => crate::BuiltIn::LineIndices, Self::Triangles => crate::BuiltIn::TriangleIndices, } } } impl crate::AddressSpace { pub const fn is_workgroup_like(self) -> bool { matches!(self, Self::WorkGroup | Self::TaskPayload) } } ================================================ FILE: naga/src/proc/namer.rs ================================================ use alloc::{ borrow::Cow, format, string::{String, ToString}, vec::Vec, }; use crate::{ arena::Handle, proc::{keyword_set::CaseInsensitiveKeywordSet, KeywordSet}, FastHashMap, }; pub type EntryPointIndex = u16; const SEPARATOR: char = '_'; /// A component of a lowered external texture. /// /// Whereas the WGSL backend implements [`ImageClass::External`] /// images directly, most other Naga backends lower them to a /// collection of ordinary textures that represent individual planes /// (as received from a video decoder, perhaps), together with a /// struct of parameters saying how they should be cropped, sampled, /// and color-converted. /// /// This lowering means that individual globals and function /// parameters in Naga IR must be split out by the backends into /// collections of globals and parameters of simpler types. /// /// A value of this enum serves as a name key for one specific /// component in the lowered representation of an external texture. /// That is, these keys are for variables/parameters that do not exist /// in the Naga IR, only in its lowered form. /// /// [`ImageClass::External`]: crate::ir::ImageClass::External #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum ExternalTextureNameKey { Plane(usize), Params, } impl ExternalTextureNameKey { const ALL: &[(&str, ExternalTextureNameKey)] = &[ ("_plane0", ExternalTextureNameKey::Plane(0)), ("_plane1", ExternalTextureNameKey::Plane(1)), ("_plane2", ExternalTextureNameKey::Plane(2)), ("_params", ExternalTextureNameKey::Params), ]; } #[derive(Debug, Eq, Hash, PartialEq)] pub enum NameKey { Constant(Handle), Override(Handle), GlobalVariable(Handle), Type(Handle), StructMember(Handle, u32), Function(Handle), FunctionArgument(Handle, u32), FunctionLocal(Handle, Handle), /// A local variable used by ReadZeroSkipWrite bounds-check policy /// when it needs to produce a pointer-typed result for an OOB access. /// These are unique per accessed type, so the second element is a /// type handle. See docs for [`crate::back::msl`]. FunctionOobLocal(Handle, Handle), EntryPoint(EntryPointIndex), EntryPointLocal(EntryPointIndex, Handle), EntryPointArgument(EntryPointIndex, u32), /// Entry point version of `FunctionOobLocal`. EntryPointOobLocal(EntryPointIndex, Handle), /// A global variable holding a component of a lowered external texture. /// /// See [`ExternalTextureNameKey`] for details. ExternalTextureGlobalVariable(Handle, ExternalTextureNameKey), /// A function argument holding a component of a lowered external /// texture. /// /// See [`ExternalTextureNameKey`] for details. ExternalTextureFunctionArgument(Handle, u32, ExternalTextureNameKey), } /// This processor assigns names to all the things in a module /// that may need identifiers in a textual backend. #[derive(Default)] pub struct Namer { /// The last numeric suffix used for each base name. Zero means "no suffix". unique: FastHashMap, keywords: &'static KeywordSet, builtin_identifiers: &'static KeywordSet, keywords_case_insensitive: &'static CaseInsensitiveKeywordSet, reserved_prefixes: Vec<&'static str>, } impl Namer { /// Return a form of `string` suitable for use as the base of an identifier. /// /// - Drop leading digits. /// - Retain only alphanumeric and `_` characters. /// - Avoid prefixes in [`Namer::reserved_prefixes`]. /// - Replace consecutive `_` characters with a single `_` character. /// /// The return value is a valid identifier prefix in all of Naga's output languages, /// and it never ends with a `SEPARATOR` character. /// It is used as a key into the unique table. fn sanitize<'s>(&self, string: &'s str) -> Cow<'s, str> { let string = string .trim_start_matches(|c: char| c.is_numeric()) .trim_end_matches(SEPARATOR); let base = if !string.is_empty() && !string.contains("__") && string .chars() .all(|c: char| c.is_ascii_alphanumeric() || c == '_') { Cow::Borrowed(string) } else { let mut filtered = string.chars().fold(String::new(), |mut s, c| { let c = match c { // Make several common characters in C++-ish types become snake case // separators. ':' | '<' | '>' | ',' => '_', c => c, }; let had_underscore_at_end = s.ends_with('_'); if had_underscore_at_end && c == '_' { return s; } if c.is_ascii_alphanumeric() || c == '_' { s.push(c); } else { use core::fmt::Write as _; if !s.is_empty() && !had_underscore_at_end { s.push('_'); } write!(s, "u{:04x}_", c as u32).unwrap(); } s }); let stripped_len = filtered.trim_end_matches(SEPARATOR).len(); filtered.truncate(stripped_len); if filtered.is_empty() { filtered.push_str("unnamed"); } else if filtered.starts_with(|c: char| c.is_ascii_digit()) { unreachable!( "internal error: invalid identifier starting with ASCII digit {:?}", filtered.chars().nth(0) ) } Cow::Owned(filtered) }; for prefix in &self.reserved_prefixes { if base.starts_with(prefix) { return format!("gen_{base}").into(); } } base } /// Return a new identifier based on `label_raw`. /// /// The result: /// - is a valid identifier even if `label_raw` is not /// - conflicts with no keywords listed in `Namer::keywords`, and /// - is different from any identifier previously constructed by this /// `Namer`. /// /// Guarantee uniqueness by applying a numeric suffix when necessary. If `label_raw` /// itself ends with digits, separate them from the suffix with an underscore. pub fn call(&mut self, label_raw: &str) -> String { use core::fmt::Write as _; // for write!-ing to Strings let base = self.sanitize(label_raw); debug_assert!(!base.is_empty() && !base.ends_with(SEPARATOR)); // This would seem to be a natural place to use `HashMap::entry`. However, `entry` // requires an owned key, and we'd like to avoid heap-allocating strings we're // just going to throw away. The approach below double-hashes only when we create // a new entry, in which case the heap allocation of the owned key was more // expensive anyway. match self.unique.get_mut(base.as_ref()) { Some(count) => { *count += 1; // Add the suffix. This may fit in base's existing allocation. let mut suffixed = base.into_owned(); write!(suffixed, "{}{}", SEPARATOR, *count).unwrap(); suffixed } None => { let mut suffixed = base.to_string(); if base.ends_with(char::is_numeric) || self.keywords.contains(base.as_ref()) || self.keywords_case_insensitive.contains(base.as_ref()) || self.builtin_identifiers.contains(base.as_ref()) { suffixed.push(SEPARATOR); } debug_assert!(!self.keywords.contains(&suffixed)); // `self.unique` wants to own its keys. This allocates only if we haven't // already done so earlier. self.unique.insert(base.into_owned(), 0); suffixed } } } pub fn call_or(&mut self, label: &Option, fallback: &str) -> String { self.call(match *label { Some(ref name) => name, None => fallback, }) } /// Enter a local namespace for things like structs. /// /// Struct member names only need to be unique amongst themselves, not /// globally. This function temporarily establishes a fresh, empty naming /// context for the duration of the call to `body`. fn namespace(&mut self, capacity: usize, body: impl FnOnce(&mut Self)) { let empty_unique = FastHashMap::with_capacity_and_hasher(capacity, Default::default()); let saved_unique = core::mem::replace(&mut self.unique, empty_unique); let saved_builtin_identifiers = core::mem::take(&mut self.builtin_identifiers); body(self); self.unique = saved_unique; self.builtin_identifiers = saved_builtin_identifiers; } pub fn reset( &mut self, module: &crate::Module, reserved_keywords: &'static KeywordSet, builtin_identifiers: &'static KeywordSet, reserved_keywords_case_insensitive: &'static CaseInsensitiveKeywordSet, reserved_prefixes: &[&'static str], output: &mut FastHashMap, ) { self.reserved_prefixes.clear(); self.reserved_prefixes.extend(reserved_prefixes.iter()); self.unique.clear(); self.keywords = reserved_keywords; self.builtin_identifiers = builtin_identifiers; self.keywords_case_insensitive = reserved_keywords_case_insensitive; // Choose fallback names for anonymous entry point return types. let mut entrypoint_type_fallbacks = FastHashMap::default(); for ep in &module.entry_points { if let Some(ref result) = ep.function.result { if let crate::Type { name: None, inner: crate::TypeInner::Struct { .. }, } = module.types[result.ty] { let label = match ep.stage { crate::ShaderStage::Vertex => "VertexOutput", crate::ShaderStage::Fragment => "FragmentOutput", crate::ShaderStage::Compute => "ComputeOutput", crate::ShaderStage::Task | crate::ShaderStage::Mesh | crate::ShaderStage::RayGeneration | crate::ShaderStage::ClosestHit | crate::ShaderStage::AnyHit | crate::ShaderStage::Miss => unreachable!(), }; entrypoint_type_fallbacks.insert(result.ty, label); } } } let mut temp = String::new(); for (ty_handle, ty) in module.types.iter() { // If the type is anonymous, check `entrypoint_types` for // something better than just `"type"`. let raw_label = match ty.name { Some(ref given_name) => given_name.as_str(), None => entrypoint_type_fallbacks .get(&ty_handle) .cloned() .unwrap_or("type"), }; let ty_name = self.call(raw_label); output.insert(NameKey::Type(ty_handle), ty_name); if let crate::TypeInner::Struct { ref members, .. } = ty.inner { // struct members have their own namespace, because access is always prefixed self.namespace(members.len(), |namer| { for (index, member) in members.iter().enumerate() { let name = namer.call_or(&member.name, "member"); output.insert(NameKey::StructMember(ty_handle, index as u32), name); } }) } } for (ep_index, ep) in module.entry_points.iter().enumerate() { let ep_name = self.call(&ep.name); output.insert(NameKey::EntryPoint(ep_index as _), ep_name); for (index, arg) in ep.function.arguments.iter().enumerate() { let name = self.call_or(&arg.name, "param"); output.insert( NameKey::EntryPointArgument(ep_index as _, index as u32), name, ); } for (handle, var) in ep.function.local_variables.iter() { let name = self.call_or(&var.name, "local"); output.insert(NameKey::EntryPointLocal(ep_index as _, handle), name); } } for (fun_handle, fun) in module.functions.iter() { let fun_name = self.call_or(&fun.name, "function"); output.insert(NameKey::Function(fun_handle), fun_name); for (index, arg) in fun.arguments.iter().enumerate() { let name = self.call_or(&arg.name, "param"); output.insert(NameKey::FunctionArgument(fun_handle, index as u32), name); if matches!( module.types[arg.ty].inner, crate::TypeInner::Image { class: crate::ImageClass::External, .. } ) { let base = arg.name.as_deref().unwrap_or("param"); for &(suffix, ext_key) in ExternalTextureNameKey::ALL { let name = self.call(&format!("{base}_{suffix}")); output.insert( NameKey::ExternalTextureFunctionArgument( fun_handle, index as u32, ext_key, ), name, ); } } } for (handle, var) in fun.local_variables.iter() { let name = self.call_or(&var.name, "local"); output.insert(NameKey::FunctionLocal(fun_handle, handle), name); } } for (handle, var) in module.global_variables.iter() { let name = self.call_or(&var.name, "global"); output.insert(NameKey::GlobalVariable(handle), name); if matches!( module.types[var.ty].inner, crate::TypeInner::Image { class: crate::ImageClass::External, .. } ) { let base = var.name.as_deref().unwrap_or("global"); for &(suffix, ext_key) in ExternalTextureNameKey::ALL { let name = self.call(&format!("{base}_{suffix}")); output.insert( NameKey::ExternalTextureGlobalVariable(handle, ext_key), name, ); } } } for (handle, constant) in module.constants.iter() { let label = match constant.name { Some(ref name) => name, None => { use core::fmt::Write; // Try to be more descriptive about the constant values temp.clear(); write!(temp, "const_{}", output[&NameKey::Type(constant.ty)]).unwrap(); &temp } }; let name = self.call(label); output.insert(NameKey::Constant(handle), name); } for (handle, override_) in module.overrides.iter() { let label = match override_.name { Some(ref name) => name, None => { use core::fmt::Write; // Try to be more descriptive about the override values temp.clear(); write!(temp, "override_{}", output[&NameKey::Type(override_.ty)]).unwrap(); &temp } }; let name = self.call(label); output.insert(NameKey::Override(handle), name); } } } #[test] fn test() { let mut namer = Namer::default(); assert_eq!(namer.call("x"), "x"); assert_eq!(namer.call("x"), "x_1"); assert_eq!(namer.call("x1"), "x1_"); assert_eq!(namer.call("__x"), "_x"); assert_eq!(namer.call("1___x"), "_x_1"); } ================================================ FILE: naga/src/proc/overloads/any_overload_set.rs ================================================ //! Dynamically dispatched [`OverloadSet`]s. use crate::common::DiagnosticDebug; use crate::ir; use crate::proc::overloads::{list, regular, OverloadSet, Rule}; use crate::proc::{GlobalCtx, TypeResolution}; use alloc::vec::Vec; use core::fmt; macro_rules! define_any_overload_set { { $( $module:ident :: $name:ident, )* } => { /// An [`OverloadSet`] that dynamically dispatches to concrete implementations. #[derive(Clone)] pub(in crate::proc::overloads) enum AnyOverloadSet { $( $name ( $module :: $name ), )* } $( impl From<$module::$name> for AnyOverloadSet { fn from(concrete: $module::$name) -> Self { AnyOverloadSet::$name(concrete) } } )* impl OverloadSet for AnyOverloadSet { fn is_empty(&self) -> bool { match *self { $( AnyOverloadSet::$name(ref x) => x.is_empty(), )* } } fn min_arguments(&self) -> usize { match *self { $( AnyOverloadSet::$name(ref x) => x.min_arguments(), )* } } fn max_arguments(&self) -> usize { match *self { $( AnyOverloadSet::$name(ref x) => x.max_arguments(), )* } } fn arg( &self, i: usize, ty: &ir::TypeInner, types: &crate::UniqueArena, ) -> Self { match *self { $( AnyOverloadSet::$name(ref x) => AnyOverloadSet::$name(x.arg(i, ty, types)), )* } } fn concrete_only(self, types: &crate::UniqueArena) -> Self { match self { $( AnyOverloadSet::$name(x) => AnyOverloadSet::$name(x.concrete_only(types)), )* } } fn most_preferred(&self) -> Rule { match *self { $( AnyOverloadSet::$name(ref x) => x.most_preferred(), )* } } fn overload_list(&self, gctx: &GlobalCtx<'_>) -> Vec { match *self { $( AnyOverloadSet::$name(ref x) => x.overload_list(gctx), )* } } fn allowed_args(&self, i: usize, gctx: &GlobalCtx<'_>) -> Vec { match *self { $( AnyOverloadSet::$name(ref x) => x.allowed_args(i, gctx), )* } } fn for_debug(&self, types: &crate::UniqueArena) -> impl fmt::Debug { DiagnosticDebug((self, types)) } } impl fmt::Debug for DiagnosticDebug<(&AnyOverloadSet, &crate::UniqueArena)> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (set, types) = self.0; match *set { $( AnyOverloadSet::$name(ref x) => DiagnosticDebug((x, types)).fmt(f), )* } } } } } define_any_overload_set! { list::List, regular::Regular, } ================================================ FILE: naga/src/proc/overloads/constructor_set.rs ================================================ //! A set of type constructors, represented as a bitset. use crate::ir; use crate::proc::overloads::one_bits_iter::OneBitsIter; bitflags::bitflags! { /// A set of type constructors. #[derive(Copy, Clone, Debug, PartialEq)] pub(crate) struct ConstructorSet: u16 { const SCALAR = 1 << 0; const VEC2 = 1 << 1; const VEC3 = 1 << 2; const VEC4 = 1 << 3; const MAT2X2 = 1 << 4; const MAT2X3 = 1 << 5; const MAT2X4 = 1 << 6; const MAT3X2 = 1 << 7; const MAT3X3 = 1 << 8; const MAT3X4 = 1 << 9; const MAT4X2 = 1 << 10; const MAT4X3 = 1 << 11; const MAT4X4 = 1 << 12; const VECN = Self::VEC2.bits() | Self::VEC3.bits() | Self::VEC4.bits(); } } impl ConstructorSet { /// Return the single-member set containing `inner`'s constructor. pub const fn singleton(inner: &ir::TypeInner) -> ConstructorSet { use ir::TypeInner as Ti; use ir::VectorSize as Vs; match *inner { Ti::Scalar(_) => Self::SCALAR, Ti::Vector { size, scalar: _ } => match size { Vs::Bi => Self::VEC2, Vs::Tri => Self::VEC3, Vs::Quad => Self::VEC4, }, Ti::Matrix { columns, rows, scalar: _, } => match (columns, rows) { (Vs::Bi, Vs::Bi) => Self::MAT2X2, (Vs::Bi, Vs::Tri) => Self::MAT2X3, (Vs::Bi, Vs::Quad) => Self::MAT2X4, (Vs::Tri, Vs::Bi) => Self::MAT3X2, (Vs::Tri, Vs::Tri) => Self::MAT3X3, (Vs::Tri, Vs::Quad) => Self::MAT3X4, (Vs::Quad, Vs::Bi) => Self::MAT4X2, (Vs::Quad, Vs::Tri) => Self::MAT4X3, (Vs::Quad, Vs::Quad) => Self::MAT4X4, }, _ => Self::empty(), } } pub const fn is_singleton(self) -> bool { self.bits().is_power_of_two() } /// Return an iterator over this set's members. /// /// Members are produced as singleton, in order from most general to least. pub fn members(self) -> impl Iterator { OneBitsIter::new(self.bits() as u64).map(|bit| Self::from_bits(bit as u16).unwrap()) } /// Return the size of the sole element of `self`. /// /// # Panics /// /// Panic if `self` is not a singleton. pub fn size(self) -> ConstructorSize { use ir::VectorSize as Vs; use ConstructorSize as Cs; match self { ConstructorSet::SCALAR => Cs::Scalar, ConstructorSet::VEC2 => Cs::Vector(Vs::Bi), ConstructorSet::VEC3 => Cs::Vector(Vs::Tri), ConstructorSet::VEC4 => Cs::Vector(Vs::Quad), ConstructorSet::MAT2X2 => Cs::Matrix { columns: Vs::Bi, rows: Vs::Bi, }, ConstructorSet::MAT2X3 => Cs::Matrix { columns: Vs::Bi, rows: Vs::Tri, }, ConstructorSet::MAT2X4 => Cs::Matrix { columns: Vs::Bi, rows: Vs::Quad, }, ConstructorSet::MAT3X2 => Cs::Matrix { columns: Vs::Tri, rows: Vs::Bi, }, ConstructorSet::MAT3X3 => Cs::Matrix { columns: Vs::Tri, rows: Vs::Tri, }, ConstructorSet::MAT3X4 => Cs::Matrix { columns: Vs::Tri, rows: Vs::Quad, }, ConstructorSet::MAT4X2 => Cs::Matrix { columns: Vs::Quad, rows: Vs::Bi, }, ConstructorSet::MAT4X3 => Cs::Matrix { columns: Vs::Quad, rows: Vs::Tri, }, ConstructorSet::MAT4X4 => Cs::Matrix { columns: Vs::Quad, rows: Vs::Quad, }, _ => unreachable!("ConstructorSet was not a singleton"), } } } /// The sizes a member of [`ConstructorSet`] might have. #[derive(Clone, Copy)] pub enum ConstructorSize { /// The constructor is [`SCALAR`]. /// /// [`SCALAR`]: ConstructorSet::SCALAR Scalar, /// The constructor is `VECN` for some `N`. Vector(ir::VectorSize), /// The constructor is `MATCXR` for some `C` and `R`. Matrix { columns: ir::VectorSize, rows: ir::VectorSize, }, } impl ConstructorSize { /// Construct a [`TypeInner`] for a type with this size and the given `scalar`. /// /// [`TypeInner`]: ir::TypeInner pub const fn to_inner(self, scalar: ir::Scalar) -> ir::TypeInner { match self { Self::Scalar => ir::TypeInner::Scalar(scalar), Self::Vector(size) => ir::TypeInner::Vector { size, scalar }, Self::Matrix { columns, rows } => ir::TypeInner::Matrix { columns, rows, scalar, }, } } } macro_rules! constructor_set { ( $( $constr:ident )|* ) => { { use $crate::proc::overloads::constructor_set::ConstructorSet; ConstructorSet::empty() $( .union(ConstructorSet::$constr) )* } } } pub(in crate::proc::overloads) use constructor_set; ================================================ FILE: naga/src/proc/overloads/list.rs ================================================ //! An [`OverloadSet`] represented as a vector of rules. //! //! [`OverloadSet`]: crate::proc::overloads::OverloadSet use crate::common::{DiagnosticDebug, ForDebug, ForDebugWithTypes}; use crate::ir; use crate::proc::overloads::one_bits_iter::OneBitsIter; use crate::proc::overloads::Rule; use crate::proc::{GlobalCtx, TypeResolution}; use alloc::rc::Rc; use alloc::vec::Vec; use core::fmt; /// A simple list of overloads. /// /// Note that this type is not quite as general as it looks, in that /// the implementation of `most_preferred` doesn't work for arbitrary /// lists of overloads. See the documentation for [`List::rules`] for /// details. #[derive(Clone)] pub(in crate::proc::overloads) struct List { /// A bitmask of which elements of `rules` are included in the set. members: u64, /// A list of type rules that are members of the set. /// /// These must be listed in order such that every rule in the list /// is always more preferred than all subsequent rules in the /// list. If there is no such arrangement of rules, then you /// cannot use `List` to represent the overload set. rules: Rc>, } impl List { pub(in crate::proc::overloads) fn from_rules(rules: Vec) -> List { List { members: len_to_full_mask(rules.len()), rules: Rc::new(rules), } } fn members(&self) -> impl Iterator { OneBitsIter::new(self.members).map(|mask| { let index = mask.trailing_zeros() as usize; (mask, &self.rules[index]) }) } fn filter(&self, mut pred: F) -> List where F: FnMut(&Rule) -> bool, { let mut filtered_members = 0; for (mask, rule) in self.members() { if pred(rule) { filtered_members |= mask; } } List { members: filtered_members, rules: self.rules.clone(), } } } impl crate::proc::overloads::OverloadSet for List { fn is_empty(&self) -> bool { self.members == 0 } fn min_arguments(&self) -> usize { self.members() .fold(None, |best, (_, rule)| { // This is different from `max_arguments` because // `, desc: &DeviceDescriptor<'_>, ) -> Result<(Device, Queue), RequestDeviceError> { let core_adapter = self.inner.as_core(); let (device, queue) = unsafe { core_adapter .context .create_device_from_hal(core_adapter, hal_device, desc) }?; Ok(( Device { inner: device.into(), }, Queue { inner: queue.into(), }, )) } /// Get the [`wgpu_hal`] adapter from this `Adapter`. /// /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`], /// and pass that struct to the to the `A` type parameter. /// /// Returns a guard that dereferences to the type of the hal backend /// which implements [`A::Adapter`]. /// /// # Types /// /// The returned type depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("Adapter")] #[doc = crate::macros::hal_type_metal!("Adapter")] #[doc = crate::macros::hal_type_dx12!("Adapter")] #[doc = crate::macros::hal_type_gles!("Adapter")] /// /// # Errors /// /// This method will return None if: /// - The adapter is not from the backend specified by `A`. /// - The adapter is from the `webgpu` or `custom` backend. /// /// # Safety /// /// - The returned resource must not be destroyed unless the guard /// is the last reference to it and it is not in use by the GPU. /// The guard and handle may be dropped at any time however. /// - All the safety requirements of wgpu-hal must be upheld. /// /// [`A::Adapter`]: hal::Api::Adapter #[cfg(wgpu_core)] pub unsafe fn as_hal( &self, ) -> Option + WasmNotSendSync> { let adapter = self.inner.as_core_opt()?; unsafe { adapter.context.adapter_as_hal::(adapter) } } #[cfg(custom)] /// Returns custom implementation of adapter (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } #[cfg(custom)] /// Creates Adapter from custom implementation pub fn from_custom(adapter: T) -> Self { Self { inner: dispatch::DispatchAdapter::custom(adapter), } } /// Returns whether this adapter may present to the passed surface. pub fn is_surface_supported(&self, surface: &Surface<'_>) -> bool { self.inner.is_surface_supported(&surface.inner) } /// The features which can be used to create devices on this adapter. pub fn features(&self) -> Features { self.inner.features() } /// The best limits which can be used to create devices on this adapter. pub fn limits(&self) -> Limits { self.inner.limits() } /// Get info about the adapter itself. pub fn get_info(&self) -> AdapterInfo { self.inner.get_info() } /// Get info about the adapter itself. pub fn get_downlevel_capabilities(&self) -> DownlevelCapabilities { self.inner.downlevel_capabilities() } /// Returns the features supported for a given texture format by this adapter. /// /// Note that the WebGPU spec further restricts the available usages/features. /// To disable these restrictions on a device, request the [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] feature. pub fn get_texture_format_features(&self, format: TextureFormat) -> TextureFormatFeatures { self.inner.get_texture_format_features(format) } /// Generates a timestamp using the clock used by the presentation engine. /// /// When comparing completely opaque timestamp systems, we need a way of generating timestamps that signal /// the exact same time. You can do this by calling your own timestamp function immediately after a call to /// this function. This should result in timestamps that are 0.5 to 5 microseconds apart. There are locks /// that must be taken during the call, so don't call your function before. /// /// ```no_run /// # let adapter: wgpu::Adapter = panic!(); /// # let some_code = || wgpu::PresentationTimestamp::INVALID_TIMESTAMP; /// use std::time::{Duration, Instant}; /// let presentation = adapter.get_presentation_timestamp(); /// let instant = Instant::now(); /// /// // We can now turn a new presentation timestamp into an Instant. /// let some_pres_timestamp = some_code(); /// let duration = Duration::from_nanos((some_pres_timestamp.0 - presentation.0) as u64); /// let new_instant: Instant = instant + duration; /// ``` // /// [Instant]: std::time::Instant pub fn get_presentation_timestamp(&self) -> PresentationTimestamp { self.inner.get_presentation_timestamp() } /// Returns the supported cooperative matrix configurations for this adapter. /// /// Cooperative matrices enable hardware-accelerated matrix multiply-accumulate /// operations where threads in a subgroup collectively process matrix tiles. /// /// Returns an empty vector if cooperative matrices are not supported. /// /// Requires [`Features::EXPERIMENTAL_COOPERATIVE_MATRIX`] to be meaningful. pub fn cooperative_matrix_properties(&self) -> Vec { self.inner.cooperative_matrix_properties() } } ================================================ FILE: wgpu/src/api/bind_group.rs ================================================ use crate::*; /// Handle to a binding group. /// /// A `BindGroup` represents the set of resources bound to the bindings described by a /// [`BindGroupLayout`]. It can be created with [`Device::create_bind_group`]. A `BindGroup` can /// be bound to a particular [`RenderPass`] with [`RenderPass::set_bind_group`], or to a /// [`ComputePass`] with [`ComputePass::set_bind_group`]. /// /// Corresponds to [WebGPU `GPUBindGroup`](https://gpuweb.github.io/gpuweb/#gpubindgroup). #[derive(Debug, Clone)] pub struct BindGroup { pub(crate) inner: dispatch::DispatchBindGroup, } #[cfg(send_sync)] static_assertions::assert_impl_all!(BindGroup: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(BindGroup => .inner); impl BindGroup { #[cfg(custom)] /// Returns custom implementation of BindGroup (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// Resource to be bound by a [`BindGroup`] for use with a pipeline. /// /// The pipeline’s [`BindGroupLayout`] must contain a matching [`BindingType`]. /// /// Corresponds to [WebGPU `GPUBindingResource`]( /// https://gpuweb.github.io/gpuweb/#typedefdef-gpubindingresource). #[non_exhaustive] #[derive(Clone, Debug)] pub enum BindingResource<'a> { /// Binding is backed by a buffer. /// /// Corresponds to [`wgt::BufferBindingType::Uniform`] and [`wgt::BufferBindingType::Storage`] /// with [`BindGroupLayoutEntry::count`] set to None. Buffer(BufferBinding<'a>), /// Binding is backed by an array of buffers. /// /// [`Features::BUFFER_BINDING_ARRAY`] must be supported to use this feature. /// /// Corresponds to [`wgt::BufferBindingType::Uniform`] and [`wgt::BufferBindingType::Storage`] /// with [`BindGroupLayoutEntry::count`] set to Some. BufferArray(&'a [BufferBinding<'a>]), /// Binding is a sampler. /// /// Corresponds to [`wgt::BindingType::Sampler`] with [`BindGroupLayoutEntry::count`] set to None. Sampler(&'a Sampler), /// Binding is backed by an array of samplers. /// /// [`Features::TEXTURE_BINDING_ARRAY`] must be supported to use this feature. /// /// Corresponds to [`wgt::BindingType::Sampler`] with [`BindGroupLayoutEntry::count`] set /// to Some. SamplerArray(&'a [&'a Sampler]), /// Binding is backed by a texture. /// /// Corresponds to [`wgt::BindingType::Texture`] and [`wgt::BindingType::StorageTexture`] with /// [`BindGroupLayoutEntry::count`] set to None. TextureView(&'a TextureView), /// Binding is backed by an array of textures. /// /// [`Features::TEXTURE_BINDING_ARRAY`] must be supported to use this feature. /// /// Corresponds to [`wgt::BindingType::Texture`] and [`wgt::BindingType::StorageTexture`] with /// [`BindGroupLayoutEntry::count`] set to Some. TextureViewArray(&'a [&'a TextureView]), /// Binding is backed by a top level acceleration structure /// /// Corresponds to [`wgt::BindingType::AccelerationStructure`] with [`BindGroupLayoutEntry::count`] set to None. /// /// # Validation /// When using (e.g. with `set_bind_group`) a bind group that has been created with one or more of this binding /// resource certain checks take place. /// - TLAS must have been built, if not a validation error is generated /// - All BLASes that were built into the TLAS must be built before the TLAS, if this was not satisfied and TLAS was /// built using `build_acceleration_structures` a validation error is generated otherwise this is a part of the /// safety section of `build_acceleration_structures_unsafe_tlas` and so undefined behavior occurs. AccelerationStructure(&'a Tlas), /// Binding is backed by an array of top level acceleration structures. /// /// Corresponds to [`wgt::BindingType::AccelerationStructure`] with [`BindGroupLayoutEntry::count`] set to Some. /// /// # Validation /// The same validation rules apply as for [`BindingResource::AccelerationStructure`], for each element. /// /// Note: backend support may vary; this is primarily intended for native backends. AccelerationStructureArray(&'a [&'a Tlas]), /// Binding is backed by an external texture. /// /// [`Features::EXTERNAL_TEXTURE`] must be supported to use this feature. /// /// Corresponds to [`wgt::BindingType::ExternalTexture`]. ExternalTexture(&'a ExternalTexture), } #[cfg(send_sync)] static_assertions::assert_impl_all!(BindingResource<'_>: Send, Sync); /// Describes the segment of a buffer to bind. /// /// Corresponds to [WebGPU `GPUBufferBinding`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpubufferbinding). #[derive(Clone, Debug)] pub struct BufferBinding<'a> { /// The buffer to bind. pub buffer: &'a Buffer, /// Base offset of the buffer, in bytes. /// /// If the [`has_dynamic_offset`] field of this buffer's layout entry is /// `true`, the offset here will be added to the dynamic offset passed to /// [`RenderPass::set_bind_group`] or [`ComputePass::set_bind_group`]. /// /// If the buffer was created with [`BufferUsages::UNIFORM`], then this /// offset must be a multiple of /// [`Limits::min_uniform_buffer_offset_alignment`]. /// /// If the buffer was created with [`BufferUsages::STORAGE`], then this /// offset must be a multiple of /// [`Limits::min_storage_buffer_offset_alignment`]. /// /// [`has_dynamic_offset`]: BindingType::Buffer::has_dynamic_offset pub offset: BufferAddress, /// Size of the binding in bytes, or `None` for using the rest of the buffer. pub size: Option, } #[cfg(send_sync)] static_assertions::assert_impl_all!(BufferBinding<'_>: Send, Sync); /// An element of a [`BindGroupDescriptor`], consisting of a bindable resource /// and the slot to bind it to. /// /// Corresponds to [WebGPU `GPUBindGroupEntry`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpubindgroupentry). #[derive(Clone, Debug)] pub struct BindGroupEntry<'a> { /// Slot for which binding provides resource. Corresponds to an entry of the same /// binding index in the [`BindGroupLayoutDescriptor`]. pub binding: u32, /// Resource to attach to the binding pub resource: BindingResource<'a>, } #[cfg(send_sync)] static_assertions::assert_impl_all!(BindGroupEntry<'_>: Send, Sync); /// Describes a group of bindings and the resources to be bound. /// /// For use with [`Device::create_bind_group`]. /// /// Corresponds to [WebGPU `GPUBindGroupDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpubindgroupdescriptor). #[derive(Clone, Debug)] pub struct BindGroupDescriptor<'a> { /// Debug label of the bind group. This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// The [`BindGroupLayout`] that corresponds to this bind group. pub layout: &'a BindGroupLayout, /// The resources to bind to this bind group. pub entries: &'a [BindGroupEntry<'a>], } #[cfg(send_sync)] static_assertions::assert_impl_all!(BindGroupDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/bind_group_layout.rs ================================================ use crate::*; /// Handle to a binding group layout. /// /// A `BindGroupLayout` is a handle to the GPU-side layout of a binding group. It can be used to /// create a [`BindGroupDescriptor`] object, which in turn can be used to create a [`BindGroup`] /// object with [`Device::create_bind_group`]. A series of `BindGroupLayout`s can also be used to /// create a [`PipelineLayoutDescriptor`], which can be used to create a [`PipelineLayout`]. /// /// It can be created with [`Device::create_bind_group_layout`]. /// /// Corresponds to [WebGPU `GPUBindGroupLayout`]( /// https://gpuweb.github.io/gpuweb/#gpubindgrouplayout). #[derive(Debug, Clone)] pub struct BindGroupLayout { pub(crate) inner: dispatch::DispatchBindGroupLayout, } #[cfg(send_sync)] static_assertions::assert_impl_all!(BindGroupLayout: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(BindGroupLayout => .inner); impl BindGroupLayout { #[cfg(custom)] /// Returns custom implementation of BindGroupLayout (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// Describes a [`BindGroupLayout`]. /// /// For use with [`Device::create_bind_group_layout`]. /// /// Corresponds to [WebGPU `GPUBindGroupLayoutDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpubindgrouplayoutdescriptor). #[derive(Clone, Debug)] pub struct BindGroupLayoutDescriptor<'a> { /// Debug label of the bind group layout. This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// Array of entries in this BindGroupLayout pub entries: &'a [BindGroupLayoutEntry], } static_assertions::assert_impl_all!(BindGroupLayoutDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/blas.rs ================================================ #[cfg(wgpu_core)] use core::ops::Deref; use alloc::{boxed::Box, vec::Vec}; use wgt::{WasmNotSend, WasmNotSendSync}; use crate::dispatch; use crate::{Buffer, Label}; /// Descriptor for the size defining attributes of a triangle geometry, for a bottom level acceleration structure. pub type BlasTriangleGeometrySizeDescriptor = wgt::BlasTriangleGeometrySizeDescriptor; static_assertions::assert_impl_all!(BlasTriangleGeometrySizeDescriptor: Send, Sync); /// Descriptor for the size defining attributes, for a bottom level acceleration structure. pub type BlasGeometrySizeDescriptors = wgt::BlasGeometrySizeDescriptors; static_assertions::assert_impl_all!(BlasGeometrySizeDescriptors: Send, Sync); /// Flags for an acceleration structure. pub type AccelerationStructureFlags = wgt::AccelerationStructureFlags; static_assertions::assert_impl_all!(AccelerationStructureFlags: Send, Sync); /// Flags for a geometry inside a bottom level acceleration structure. pub type AccelerationStructureGeometryFlags = wgt::AccelerationStructureGeometryFlags; static_assertions::assert_impl_all!(AccelerationStructureGeometryFlags: Send, Sync); /// Update mode for acceleration structure builds. pub type AccelerationStructureUpdateMode = wgt::AccelerationStructureUpdateMode; static_assertions::assert_impl_all!(AccelerationStructureUpdateMode: Send, Sync); /// Descriptor to create bottom level acceleration structures. pub type CreateBlasDescriptor<'a> = wgt::CreateBlasDescriptor>; static_assertions::assert_impl_all!(CreateBlasDescriptor<'_>: Send, Sync); /// Safe instance for a [Tlas]. /// /// A TlasInstance may be made invalid, if a TlasInstance is invalid, any attempt to build a [Tlas] containing an /// invalid TlasInstance will generate a validation error /// /// Each one contains: /// - A reference to a BLAS, this ***must*** be interacted with using [TlasInstance::new] or [TlasInstance::set_blas], a /// TlasInstance that references a BLAS keeps that BLAS from being dropped /// - A user accessible transformation matrix /// - A user accessible mask /// - A user accessible custom index /// /// [Tlas]: crate::Tlas #[derive(Debug, Clone)] pub struct TlasInstance { pub(crate) blas: dispatch::DispatchBlas, /// Affine transform matrix 3x4 (rows x columns, row major order). pub transform: [f32; 12], /// Custom index for the instance used inside the shader. /// /// This must only use the lower 24 bits, if any bits are outside that range (byte 4 does not equal 0) the TlasInstance becomes /// invalid and generates a validation error when built pub custom_data: u32, /// Mask for the instance used inside the shader to filter instances. /// Reports hit only if `(shader_cull_mask & tlas_instance.mask) != 0u`. pub mask: u8, } impl TlasInstance { /// Construct TlasInstance. /// - blas: Reference to the bottom level acceleration structure /// - transform: Transform buffer offset in bytes (optional, required if transform buffer is present) /// - custom_data: Custom index for the instance used inside the shader (max 24 bits) /// - mask: Mask for the instance used inside the shader to filter instances /// /// Note: while one of these contains a reference to a BLAS that BLAS will not be dropped, /// but it can still be destroyed. Destroying a BLAS that is referenced by one or more /// TlasInstance(s) will immediately make them invalid. If one or more of those invalid /// TlasInstances is inside a TlasPackage that is attempted to be built, the build will /// generate a validation error. pub fn new(blas: &Blas, transform: [f32; 12], custom_data: u32, mask: u8) -> Self { Self { blas: blas.inner.clone(), transform, custom_data, mask, } } /// Set the bottom level acceleration structure. /// /// See the note on [TlasInstance] about the /// guarantees of keeping a BLAS alive. pub fn set_blas(&mut self, blas: &Blas) { self.blas = blas.inner.clone(); } } #[derive(Debug)] /// Definition for a triangle geometry for a Bottom Level Acceleration Structure (BLAS). /// /// The size must match the rest of the structures fields, otherwise the build will fail. /// (e.g. if index count is present in the size, the index buffer must be present as well.) pub struct BlasTriangleGeometry<'a> { /// Sub descriptor for the size defining attributes of a triangle geometry. pub size: &'a BlasTriangleGeometrySizeDescriptor, /// Vertex buffer. pub vertex_buffer: &'a Buffer, /// Offset into the vertex buffer as a factor of the vertex stride. pub first_vertex: u32, /// Vertex stride, must be greater than [`wgpu_types::VertexFormat::min_acceleration_structure_vertex_stride`] /// of the format and must be a multiple of [`wgpu_types::VertexFormat::acceleration_structure_stride_alignment`]. pub vertex_stride: wgt::BufferAddress, /// Index buffer (optional). pub index_buffer: Option<&'a Buffer>, /// Number of indexes to skip in the index buffer (optional, required if index buffer is present). pub first_index: Option, /// Transform buffer containing 3x4 (rows x columns, row major) affine transform matrices `[f32; 12]` (optional). pub transform_buffer: Option<&'a Buffer>, /// Transform buffer offset in bytes (optional, required if transform buffer is present). pub transform_buffer_offset: Option, } static_assertions::assert_impl_all!(BlasTriangleGeometry<'_>: WasmNotSendSync); /// Contains the sets of geometry that go into a [Blas]. pub enum BlasGeometries<'a> { /// Triangle geometry variant. TriangleGeometries(Vec>), } static_assertions::assert_impl_all!(BlasGeometries<'_>: WasmNotSendSync); /// Builds the given sets of geometry into the given [Blas]. pub struct BlasBuildEntry<'a> { /// Reference to the acceleration structure. pub blas: &'a Blas, /// Geometries. pub geometry: BlasGeometries<'a>, } static_assertions::assert_impl_all!(BlasBuildEntry<'_>: WasmNotSendSync); #[derive(Debug, Clone)] /// Bottom Level Acceleration Structure (BLAS). /// /// A BLAS is a device-specific raytracing acceleration structure that contains geometry data. /// /// These BLASes are combined with transform in a [TlasInstance] to create a [Tlas]. /// /// [Tlas]: crate::Tlas pub struct Blas { pub(crate) handle: Option, pub(crate) inner: dispatch::DispatchBlas, } static_assertions::assert_impl_all!(Blas: WasmNotSendSync); crate::cmp::impl_eq_ord_hash_proxy!(Blas => .inner); impl Blas { /// Raw handle to the acceleration structure, used inside raw instance buffers. pub fn handle(&self) -> Option { self.handle } /// Get the [`wgpu_hal`] acceleration structure from this `Blas`. /// /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`], /// and pass that struct to the to the `A` type parameter. /// /// Returns a guard that dereferences to the type of the hal backend /// which implements [`A::AccelerationStructure`]. /// /// # Types /// /// The returned type depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("AccelerationStructure")] #[doc = crate::macros::hal_type_metal!("AccelerationStructure")] #[doc = crate::macros::hal_type_dx12!("AccelerationStructure")] #[doc = crate::macros::hal_type_gles!("AccelerationStructure")] /// /// # Deadlocks /// /// - The returned guard holds a read-lock on a device-local "destruction" /// lock, which will cause all calls to `destroy` to block until the /// guard is released. /// /// # Errors /// /// This method will return None if: /// - The acceleration structure is not from the backend specified by `A`. /// - The acceleration structure is from the `webgpu` or `custom` backend. /// /// # Safety /// /// - The returned resource must not be destroyed unless the guard /// is the last reference to it and it is not in use by the GPU. /// The guard and handle may be dropped at any time however. /// - All the safety requirements of wgpu-hal must be upheld. /// /// [`A::AccelerationStructure`]: hal::Api::AccelerationStructure #[cfg(wgpu_core)] pub unsafe fn as_hal( &mut self, ) -> Option + WasmNotSendSync> { let blas = self.inner.as_core_opt()?; unsafe { blas.context.blas_as_hal::(blas) } } #[cfg(custom)] /// Returns custom implementation of Blas (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// Context version of [BlasTriangleGeometry]. pub struct ContextBlasTriangleGeometry<'a> { #[expect(dead_code)] pub(crate) size: &'a BlasTriangleGeometrySizeDescriptor, #[expect(dead_code)] pub(crate) vertex_buffer: &'a dispatch::DispatchBuffer, #[expect(dead_code)] pub(crate) index_buffer: Option<&'a dispatch::DispatchBuffer>, #[expect(dead_code)] pub(crate) transform_buffer: Option<&'a dispatch::DispatchBuffer>, #[expect(dead_code)] pub(crate) first_vertex: u32, #[expect(dead_code)] pub(crate) vertex_stride: wgt::BufferAddress, #[expect(dead_code)] pub(crate) index_buffer_offset: Option, #[expect(dead_code)] pub(crate) transform_buffer_offset: Option, } /// Context version of [BlasGeometries]. pub enum ContextBlasGeometries<'a> { /// Triangle geometries. TriangleGeometries(Box> + 'a>), } /// Context version see [BlasBuildEntry]. pub struct ContextBlasBuildEntry<'a> { #[expect(dead_code)] pub(crate) blas: &'a dispatch::DispatchBlas, #[expect(dead_code)] pub(crate) geometries: ContextBlasGeometries<'a>, } /// Error occurred when trying to asynchronously prepare a blas for compaction. #[derive(Clone, PartialEq, Eq, Debug)] pub struct BlasAsyncError; static_assertions::assert_impl_all!(BlasAsyncError: Send, Sync); impl core::fmt::Display for BlasAsyncError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, "Error occurred when trying to asynchronously prepare a blas for compaction" ) } } impl core::error::Error for BlasAsyncError {} impl Blas { /// Asynchronously prepares this BLAS for compaction. The callback is called once all builds /// using this BLAS are finished and the BLAS is compactable. This can be checked using /// [`Blas::ready_for_compaction`]. Rebuilding this BLAS will reset its compacted state, and it /// will need to be prepared again. /// /// ### Interaction with other functions /// On native, `queue.submit(..)` and polling devices (that is calling `instance.poll_all` or /// `device.poll`) with [`PollType::Poll`] may call the callback. On native, polling devices with /// [`PollType::Wait`] (optionally with a submission index greater /// than the last submit the BLAS was used in) will guarantee callback is called. /// /// [`PollType::Poll`]: wgpu_types::PollType::Poll /// [`PollType::Wait`]: wgpu_types::PollType::Wait pub fn prepare_compaction_async( &self, callback: impl FnOnce(Result<(), BlasAsyncError>) + WasmNotSend + 'static, ) { self.inner.prepare_compact_async(Box::new(callback)); } /// Checks whether this BLAS is ready for compaction. The returned value is `true` if /// [`Blas::prepare_compaction_async`]'s callback was called with a non-error value, otherwise /// this is `false`. pub fn ready_for_compaction(&self) -> bool { self.inner.ready_for_compaction() } } ================================================ FILE: wgpu/src/api/buffer.rs ================================================ use alloc::{boxed::Box, sync::Arc, vec::Vec}; use core::{ error, fmt, ops::{Bound, Deref, Range, RangeBounds}, }; use crate::util::Mutex; use crate::*; /// Handle to a GPU-accessible buffer. /// /// A `Buffer` is a memory allocation for use by the GPU, somewhat analogous to /// [Box]<[\[u8\]][primitive@slice]> in Rust. /// The contents of buffers are untyped bytes; it is up to the application to /// specify the interpretation of the bytes when the buffer is used, in ways /// such as [`VertexBufferLayout`]. /// A single buffer can be used to hold multiple independent pieces of data at /// different offsets (e.g. both vertices and indices for one or more meshes). /// /// A `Buffer`'s bytes have "interior mutability": functions like /// [`Queue::write_buffer`] or [mapping] a buffer for writing only require a /// `&Buffer`, not a `&mut Buffer`, even though they modify its contents. `wgpu` /// prevents simultaneous reads and writes of buffer contents using run-time /// checks. /// /// Created with [`Device::create_buffer()`] or /// [`DeviceExt::create_buffer_init()`]. /// /// Corresponds to [WebGPU `GPUBuffer`](https://gpuweb.github.io/gpuweb/#buffer-interface). /// /// [mapping]: Buffer#mapping-buffers /// /// # How to get your data into a buffer /// /// Every `Buffer` starts with all bytes zeroed. /// There are many ways to load data into a `Buffer`: /// /// - When creating a buffer, you may set the [`mapped_at_creation`][mac] flag, /// then write to its [`get_mapped_range_mut()`][Buffer::get_mapped_range_mut]. /// This only works when the buffer is created and has not yet been used by /// the GPU, but it is all you need for buffers whose contents do not change /// after creation. /// - You may use [`DeviceExt::create_buffer_init()`] as a convenient way to /// do that and copy data from a `&[u8]` you provide. /// - After creation, you may use [`Buffer::map_async()`] to map it again; /// however, you then need to wait until the GPU is no longer using the buffer /// before you begin writing. /// - You may use [`CommandEncoder::copy_buffer_to_buffer()`] to copy data into /// this buffer from another buffer. /// - You may use [`Queue::write_buffer()`] to copy data into the buffer from a /// `&[u8]`. This uses a temporary “staging” buffer managed by `wgpu` to hold /// the data. /// - [`Queue::write_buffer_with()`] allows you to write directly into temporary /// storage instead of providing a slice you already prepared, which may /// allow *your* code to save the allocation of a [`Vec`] or such. /// - You may use [`util::StagingBelt`] to manage a set of temporary buffers. /// This may be more efficient than [`Queue::write_buffer_with()`] when you /// have many small copies to perform, but requires more steps to use, and /// tuning of the belt buffer size. /// - You may write your own staging buffer management customized to your /// application, based on mapped buffers and /// [`CommandEncoder::copy_buffer_to_buffer()`]. /// - A GPU computation’s results can be stored in a buffer: /// - A [compute shader][ComputePipeline] may write to a buffer bound as a /// [storage buffer][BufferBindingType::Storage]. /// - A render pass may render to a texture which is then copied to a buffer /// using [`CommandEncoder::copy_texture_to_buffer()`]. /// /// # Mapping buffers /// /// If a `Buffer` is created with the appropriate [`usage`], it can be *mapped*: /// you can make its contents accessible to the CPU as an ordinary `&[u8]` or /// `&mut [u8]` slice of bytes. Buffers created with the /// [`mapped_at_creation`][mac] flag set are also mapped initially. /// /// Depending on the hardware, the buffer could be memory shared between CPU and /// GPU, so that the CPU has direct access to the same bytes the GPU will /// consult; or it may be ordinary CPU memory, whose contents the system must /// copy to/from the GPU as needed. This crate's API is designed to work the /// same way in either case: at any given time, a buffer is either mapped and /// available to the CPU, or unmapped and ready for use by the GPU, but never /// both. This makes it impossible for either side to observe changes by the /// other immediately, and any necessary transfers can be carried out when the /// buffer transitions from one state to the other. /// /// There are two ways to map a buffer: /// /// - If [`BufferDescriptor::mapped_at_creation`] is `true`, then the entire /// buffer is mapped when it is created. This is the easiest way to initialize /// a new buffer. You can set `mapped_at_creation` on any kind of buffer, /// regardless of its [`usage`] flags. /// /// - If the buffer's [`usage`] includes the [`MAP_READ`] or [`MAP_WRITE`] /// flags, then you can call `buffer.slice(range).map_async(mode, callback)` /// to map the portion of `buffer` given by `range`. This waits for the GPU to /// finish using the buffer, and invokes `callback` as soon as the buffer is /// safe for the CPU to access. /// /// Once a buffer is mapped: /// /// - You can call `buffer.slice(range).get_mapped_range()` to obtain a /// [`BufferView`], which dereferences to a `&[u8]` that you can use to read /// the buffer's contents. /// /// - Or, you can call `buffer.slice(range).get_mapped_range_mut()` to obtain a /// [`BufferViewMut`], which dereferences to a `&mut [u8]` that you can use to /// read and write the buffer's contents. /// /// The given `range` must fall within the mapped portion of the buffer. If you /// attempt to access overlapping ranges, even for shared access only, these /// methods panic. /// /// While a buffer is mapped, you may not submit any commands to the GPU that /// access it. You may record command buffers that use the buffer, but if you /// submit them while the buffer is mapped, submission will panic. /// /// When you are done using the buffer on the CPU, you must call /// [`Buffer::unmap`] to make it available for use by the GPU again. All /// [`BufferView`] and [`BufferViewMut`] views referring to the buffer must be /// dropped before you unmap it; otherwise, [`Buffer::unmap`] will panic. /// /// # Example /// /// If `buffer` was created with [`BufferUsages::MAP_WRITE`], we could fill it /// with `f32` values like this: /// /// ``` /// # #[cfg(feature = "noop")] /// # let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default()); /// # #[cfg(not(feature = "noop"))] /// # let device: wgpu::Device = { return; }; /// # /// # let buffer = device.create_buffer(&wgpu::BufferDescriptor { /// # label: None, /// # size: 400, /// # usage: wgpu::BufferUsages::MAP_WRITE, /// # mapped_at_creation: false, /// # }); /// let capturable = buffer.clone(); /// buffer.map_async(wgpu::MapMode::Write, .., move |result| { /// if result.is_ok() { /// let mut view = capturable.get_mapped_range_mut(..); /// let mut floats: wgpu::WriteOnly<[[u8; 4]]> = view.slice(..).into_chunks::<4>().0; /// floats.fill(42.0f32.to_ne_bytes()); /// drop(view); /// capturable.unmap(); /// } /// }); /// ``` /// /// This code takes the following steps: /// /// - First, it makes a cloned handle to the buffer for capture by /// the callback passed to [`map_async`]. Since a [`map_async`] callback may be /// invoked from another thread, interaction between the callback and the /// thread calling [`map_async`] generally requires some sort of shared heap /// data like this. In real code, there might be an [`Arc`] to some larger /// structure that itself owns `buffer`. /// /// - Then, it calls [`Buffer::slice`] to make a [`BufferSlice`] referring to /// the buffer's entire contents. /// /// - Next, it calls [`BufferSlice::map_async`] to request that the bytes to /// which the slice refers be made accessible to the CPU ("mapped"). This may /// entail waiting for previously enqueued operations on `buffer` to finish. /// Although [`map_async`] itself always returns immediately, it saves the /// callback function to be invoked later. /// /// - When some later call to [`Device::poll`] or [`Instance::poll_all`] (not /// shown in this example) determines that the buffer is mapped and ready for /// the CPU to use, it invokes the callback function. /// /// - The callback function calls [`Buffer::slice`] and then /// [`BufferSlice::get_mapped_range_mut`] to obtain a [`BufferViewMut`], which /// dereferences to a `&mut [u8]` slice referring to the buffer's bytes. /// /// - It then uses the [`bytemuck`] crate to turn the `&mut [u8]` into a `&mut /// [f32]`, and calls the slice [`fill`] method to fill the buffer with a /// useful value. /// /// - Finally, the callback drops the view and calls [`Buffer::unmap`] to unmap /// the buffer. In real code, the callback would also need to do some sort of /// synchronization to let the rest of the program know that it has completed /// its work. /// /// If using [`map_async`] directly is awkward, you may find it more convenient to /// use [`Queue::write_buffer`] and [`util::DownloadBuffer::read_buffer`]. /// However, those each have their own tradeoffs; the asynchronous nature of GPU /// execution makes it hard to avoid friction altogether. /// /// [`Arc`]: std::sync::Arc /// [`map_async`]: BufferSlice::map_async /// [`bytemuck`]: https://crates.io/crates/bytemuck /// [`fill`]: slice::fill /// /// ## Mapping buffers on the web /// /// When compiled to WebAssembly and running in a browser content process, /// `wgpu` implements its API in terms of the browser's WebGPU implementation. /// In this context, `wgpu` is further isolated from the GPU: /// /// - Depending on the browser's WebGPU implementation, mapping and unmapping /// buffers probably entails copies between WebAssembly linear memory and the /// graphics driver's buffers. /// /// - All modern web browsers isolate web content in its own sandboxed process, /// which can only interact with the GPU via interprocess communication (IPC). /// Although most browsers' IPC systems use shared memory for large data /// transfers, there will still probably need to be copies into and out of the /// shared memory buffers. /// /// All of these copies contribute to the cost of buffer mapping in this /// configuration. /// /// [`usage`]: BufferDescriptor::usage /// [mac]: BufferDescriptor::mapped_at_creation /// [`MAP_READ`]: BufferUsages::MAP_READ /// [`MAP_WRITE`]: BufferUsages::MAP_WRITE /// [`DeviceExt::create_buffer_init()`]: util::DeviceExt::create_buffer_init #[derive(Debug, Clone)] pub struct Buffer { pub(crate) inner: dispatch::DispatchBuffer, pub(crate) map_context: Arc>, pub(crate) size: wgt::BufferAddress, pub(crate) usage: BufferUsages, // Todo: missing map_state https://www.w3.org/TR/webgpu/#dom-gpubuffer-mapstate } #[cfg(send_sync)] static_assertions::assert_impl_all!(Buffer: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(Buffer => .inner); impl Buffer { /// Return the binding view of the entire buffer. pub fn as_entire_binding(&self) -> BindingResource<'_> { BindingResource::Buffer(self.as_entire_buffer_binding()) } /// Return the binding view of the entire buffer. pub fn as_entire_buffer_binding(&self) -> BufferBinding<'_> { BufferBinding { buffer: self, offset: 0, size: None, } } /// Get the [`wgpu_hal`] buffer from this `Buffer`. /// /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`], /// and pass that struct to the to the `A` type parameter. /// /// Returns a guard that dereferences to the type of the hal backend /// which implements [`A::Buffer`]. /// /// # Types /// /// The returned type depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("Buffer")] #[doc = crate::macros::hal_type_metal!("Buffer")] #[doc = crate::macros::hal_type_dx12!("Buffer")] #[doc = crate::macros::hal_type_gles!("Buffer")] /// /// # Deadlocks /// /// - The returned guard holds a read-lock on a device-local "destruction" /// lock, which will cause all calls to `destroy` to block until the /// guard is released. /// /// # Errors /// /// This method will return None if: /// - The buffer is not from the backend specified by `A`. /// - The buffer is from the `webgpu` or `custom` backend. /// - The buffer has had [`Self::destroy()`] called on it. /// /// # Safety /// /// - The returned resource must not be destroyed unless the guard /// is the last reference to it and it is not in use by the GPU. /// The guard and handle may be dropped at any time however. /// - All the safety requirements of wgpu-hal must be upheld. /// /// [`A::Buffer`]: hal::Api::Buffer #[cfg(wgpu_core)] pub unsafe fn as_hal( &self, ) -> Option + WasmNotSendSync> { let buffer = self.inner.as_core_opt()?; unsafe { buffer.context.buffer_as_hal::(buffer) } } /// Returns a [`BufferSlice`] referring to the portion of `self`'s contents /// indicated by `bounds`. Regardless of what sort of data `self` stores, /// `bounds` start and end are given in bytes. /// /// A [`BufferSlice`] can be used to supply vertex and index data, or to map /// buffer contents for access from the CPU. See the [`BufferSlice`] /// documentation for details. /// /// The `range` argument can be half or fully unbounded: for example, /// `buffer.slice(..)` refers to the entire buffer, and `buffer.slice(n..)` /// refers to the portion starting at the `n`th byte and extending to the /// end of the buffer. /// /// # Panics /// /// - If `bounds` is outside of the bounds of `self`. /// - If `bounds` has a length less than 1. #[track_caller] pub fn slice>(&self, bounds: S) -> BufferSlice<'_> { let (offset, size) = range_to_offset_size(bounds, self.size); check_buffer_bounds(self.size, offset, size); BufferSlice { buffer: self, offset, size, } } /// Unmaps the buffer from host memory. /// /// This terminates the effect of all previous [`map_async()`](Self::map_async) operations and /// makes the buffer available for use by the GPU again. pub fn unmap(&self) { self.map_context.lock().reset(); self.inner.unmap(); } /// Destroy the associated native resources as soon as possible. pub fn destroy(&self) { self.inner.destroy(); } /// Returns the length of the buffer allocation in bytes. /// /// This is always equal to the `size` that was specified when creating the buffer. pub fn size(&self) -> BufferAddress { self.size } /// Returns the allowed usages for this `Buffer`. /// /// This is always equal to the `usage` that was specified when creating the buffer. pub fn usage(&self) -> BufferUsages { self.usage } /// Map the buffer to host (CPU) memory, making it available for reading or writing via /// [`get_mapped_range()`](Self::get_mapped_range). The buffer becomes accessible once the /// `callback` is invoked with [`Ok`]. /// /// Use this when you want to map the buffer immediately. If you need to submit GPU work that /// uses the buffer before mapping it, use `map_buffer_on_submit` on /// [`CommandEncoder`][CEmbos], [`CommandBuffer`][CBmbos], [`RenderPass`][RPmbos], or /// [`ComputePass`][CPmbos] to schedule the mapping after submission. This avoids extra calls to /// [`Buffer::map_async()`] or [`BufferSlice::map_async()`] and lets you initiate mapping from a /// more convenient place. /// /// For the callback to run, either [`queue.submit(..)`][q::s], [`instance.poll_all(..)`][i::p_a], /// or [`device.poll(..)`][d::p] must be called elsewhere in the runtime, possibly integrated into /// an event loop or run on a separate thread. /// /// The callback runs on the thread that first calls one of the above functions after the GPU work /// completes. There are no restrictions on the code you can run in the callback; however, on native /// the polling call will not return until the callback finishes, so keep callbacks short (set flags, /// send messages, etc.). /// /// While a buffer is mapped, it cannot be used by other commands; at any time, either the GPU or /// the CPU has exclusive access to the buffer’s contents. /// /// This can also be performed using [`BufferSlice::map_async()`]. /// /// # Panics /// /// - If the buffer is already mapped. /// - If the buffer’s [`BufferUsages`] do not allow the requested [`MapMode`]. /// - If `bounds` is outside of the bounds of `self`. /// - If `bounds` does not start at a multiple of [`MAP_ALIGNMENT`]. /// - If `bounds` has a length that is not a multiple of 4 greater than 0. /// /// [CEmbos]: CommandEncoder::map_buffer_on_submit /// [CBmbos]: CommandBuffer::map_buffer_on_submit /// [RPmbos]: RenderPass::map_buffer_on_submit /// [CPmbos]: ComputePass::map_buffer_on_submit /// [q::s]: Queue::submit /// [i::p_a]: Instance::poll_all /// [d::p]: Device::poll pub fn map_async>( &self, mode: MapMode, bounds: S, callback: impl FnOnce(Result<(), BufferAsyncError>) + WasmNotSend + 'static, ) { self.slice(bounds).map_async(mode, callback) } /// Gain read-only access to the bytes of a [mapped] [`Buffer`]. /// /// Returns a [`BufferView`] referring to the buffer range represented by /// `self`. See the documentation for [`BufferView`] for details. /// /// `bounds` may be less than the bounds passed to [`Self::map_async()`], /// and multiple views may be obtained and used simultaneously as long as they do not overlap. /// /// This can also be performed using [`BufferSlice::get_mapped_range()`]. /// /// # Panics /// /// - If `bounds` is outside of the bounds of `self`. /// - If `bounds` does not start at a multiple of [`MAP_ALIGNMENT`]. /// - If `bounds` has a length that is not a multiple of 4 greater than 0. /// - If the buffer to which `self` refers is not currently [mapped]. /// - If you try to create a view which overlaps an existing [`BufferViewMut`]. /// /// [mapped]: Buffer#mapping-buffers #[track_caller] pub fn get_mapped_range>(&self, bounds: S) -> BufferView { self.slice(bounds).get_mapped_range() } /// Gain write access to the bytes of a [mapped] [`Buffer`]. /// /// Returns a [`BufferViewMut`] referring to the buffer range represented by /// `self`. See the documentation for [`BufferViewMut`] for more details. /// /// `bounds` may be less than the bounds passed to [`Self::map_async()`], /// and multiple views may be obtained and used simultaneously as long as they do not overlap. /// /// This can also be performed using [`BufferSlice::get_mapped_range_mut()`]. /// /// # Panics /// /// - If `bounds` is outside of the bounds of `self`. /// - If `bounds` does not start at a multiple of [`MAP_ALIGNMENT`]. /// - If `bounds` has a length that is not a multiple of 4 greater than 0. /// - If the buffer to which `self` refers is not currently [mapped]. /// - If you try to create a view which overlaps an existing [`BufferView`] or [`BufferViewMut`]. /// /// [mapped]: Buffer#mapping-buffers #[track_caller] pub fn get_mapped_range_mut>(&self, bounds: S) -> BufferViewMut { self.slice(bounds).get_mapped_range_mut() } #[cfg(custom)] /// Returns custom implementation of Buffer (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// A slice of a [`Buffer`], to be mapped, used for vertex or index data, or the like. /// /// You can create a `BufferSlice` by calling [`Buffer::slice`]: /// /// ```no_run /// # let buffer: wgpu::Buffer = todo!(); /// let slice = buffer.slice(10..20); /// ``` /// /// This returns a slice referring to the second ten bytes of `buffer`. To get a /// slice of the entire `Buffer`: /// /// ```no_run /// # let buffer: wgpu::Buffer = todo!(); /// let whole_buffer_slice = buffer.slice(..); /// ``` /// /// You can pass buffer slices to methods like [`RenderPass::set_vertex_buffer`] /// and [`RenderPass::set_index_buffer`] to indicate which portion of the buffer /// a draw call should consult. You can also convert it to a [`BufferBinding`] /// with `.into()`. /// /// To access the slice's contents on the CPU, you must first [map] the buffer, /// and then call [`BufferSlice::get_mapped_range`] or /// [`BufferSlice::get_mapped_range_mut`] to obtain a view of the slice's /// contents. See the documentation on [mapping][map] for more details, /// including example code. /// /// Unlike a Rust shared slice `&[T]`, whose existence guarantees that /// nobody else is modifying the `T` values to which it refers, a /// [`BufferSlice`] doesn't guarantee that the buffer's contents aren't /// changing. You can still record and submit commands operating on the /// buffer while holding a [`BufferSlice`]. A [`BufferSlice`] simply /// represents a certain range of the buffer's bytes. /// /// The `BufferSlice` type is unique to the Rust API of `wgpu`. In the WebGPU /// specification, an offset and size are specified as arguments to each call /// working with the [`Buffer`], instead. /// /// [map]: Buffer#mapping-buffers #[derive(Copy, Clone, Debug, PartialEq)] pub struct BufferSlice<'a> { pub(crate) buffer: &'a Buffer, pub(crate) offset: BufferAddress, pub(crate) size: BufferSize, } #[cfg(send_sync)] static_assertions::assert_impl_all!(BufferSlice<'_>: Send, Sync); impl<'a> BufferSlice<'a> { /// Return another [`BufferSlice`] referring to the portion of `self`'s contents /// indicated by `bounds`. /// /// The `range` argument can be half or fully unbounded: for example, /// `buffer.slice(..)` refers to the entire buffer, and `buffer.slice(n..)` /// refers to the portion starting at the `n`th byte and extending to the /// end of the buffer. /// /// # Panics /// /// - If `bounds` is outside of the bounds of `self`. /// - If `bounds` has a length less than 1. #[track_caller] pub fn slice>(&self, bounds: S) -> BufferSlice<'a> { let (offset, size) = range_to_offset_size(bounds, self.size.get()); check_buffer_bounds(self.size.get(), offset, size); BufferSlice { buffer: self.buffer, offset: self.offset + offset, // check_buffer_bounds ensures this does not overflow size, // check_buffer_bounds ensures this is essentially min() } } /// Map the buffer to host (CPU) memory, making it available for reading or writing via /// [`get_mapped_range()`](Self::get_mapped_range). The buffer becomes accessible once the /// `callback` is invoked with [`Ok`]. /// /// Use this when you want to map the buffer immediately. If you need to submit GPU work that /// uses the buffer before mapping it, use `map_buffer_on_submit` on /// [`CommandEncoder`][CEmbos], [`CommandBuffer`][CBmbos], [`RenderPass`][RPmbos], or /// [`ComputePass`][CPmbos] to schedule the mapping after submission. This avoids extra calls to /// [`Buffer::map_async()`] or [`BufferSlice::map_async()`] and lets you initiate mapping from a /// more convenient place. /// /// For the callback to run, either [`queue.submit(..)`][q::s], [`instance.poll_all(..)`][i::p_a], /// or [`device.poll(..)`][d::p] must be called elsewhere in the runtime, possibly integrated into /// an event loop or run on a separate thread. /// /// The callback runs on the thread that first calls one of the above functions after the GPU work /// completes. There are no restrictions on the code you can run in the callback; however, on native /// the polling call will not return until the callback finishes, so keep callbacks short (set flags, /// send messages, etc.). /// /// While a buffer is mapped, it cannot be used by other commands; at any time, either the GPU or /// the CPU has exclusive access to the buffer’s contents. /// /// This can also be performed using [`Buffer::map_async()`]. /// /// # Panics /// /// - If the buffer is already mapped. /// - If the buffer’s [`BufferUsages`] do not allow the requested [`MapMode`]. /// - If the beginning of this slice is not aligned to [`MAP_ALIGNMENT`] within the buffer. /// - If the length of this slice is not a multiple of 4. /// /// [CEmbos]: CommandEncoder::map_buffer_on_submit /// [CBmbos]: CommandBuffer::map_buffer_on_submit /// [RPmbos]: RenderPass::map_buffer_on_submit /// [CPmbos]: ComputePass::map_buffer_on_submit /// [q::s]: Queue::submit /// [i::p_a]: Instance::poll_all /// [d::p]: Device::poll pub fn map_async( &self, mode: MapMode, callback: impl FnOnce(Result<(), BufferAsyncError>) + WasmNotSend + 'static, ) { let mut mc = self.buffer.map_context.lock(); assert_eq!(mc.mapped_range, 0..0, "Buffer is already mapped"); let end = self.offset + self.size.get(); mc.mapped_range = self.offset..end; drop(mc); // release the lock of map_context as callback can call lock it again self.buffer .inner .map_async(mode, self.offset..end, Box::new(callback)); } /// Gain read-only access to the bytes of a [mapped] [`Buffer`]. /// /// Returns a [`BufferView`] referring to the buffer range represented by /// `self`. See the documentation for [`BufferView`] for details. /// /// Multiple views may be obtained and used simultaneously as long as they are from /// non-overlapping slices. /// /// This can also be performed using [`Buffer::get_mapped_range()`]. /// /// # Panics /// /// - If the beginning of this slice is not aligned to [`MAP_ALIGNMENT`] within the buffer. /// - If the length of this slice is not a multiple of 4. /// - If the buffer to which `self` refers is not currently [mapped]. /// - If you try to create a view which overlaps an existing [`BufferViewMut`]. /// /// [mapped]: Buffer#mapping-buffers #[track_caller] pub fn get_mapped_range(&self) -> BufferView { let subrange = Subrange::new(self.offset, self.size, RangeMappingKind::Immutable); self.buffer .map_context .lock() .validate_and_add(subrange.clone()); let range = self.buffer.inner.get_mapped_range(subrange.index); BufferView { buffer: self.buffer.clone(), size: self.size, offset: self.offset, inner: range, } } /// Gain write-only access to the bytes of a [mapped] [`Buffer`]. /// /// Returns a [`BufferViewMut`] referring to the buffer range represented by /// `self`. See the documentation for [`BufferViewMut`] for more details. /// /// Multiple views may be obtained and used simultaneously as long as they are from /// non-overlapping slices. /// /// This can also be performed using [`Buffer::get_mapped_range_mut()`]. /// /// # Panics /// /// - If the beginning of this slice is not aligned to [`MAP_ALIGNMENT`] within the buffer. /// - If the length of this slice is not a multiple of 4. /// - If the buffer to which `self` refers is not currently [mapped]. /// - If you try to create a view which overlaps an existing [`BufferView`] or [`BufferViewMut`]. /// /// [mapped]: Buffer#mapping-buffers #[track_caller] pub fn get_mapped_range_mut(&self) -> BufferViewMut { let subrange = Subrange::new(self.offset, self.size, RangeMappingKind::Mutable); self.buffer .map_context .lock() .validate_and_add(subrange.clone()); let range = self.buffer.inner.get_mapped_range(subrange.index); BufferViewMut { buffer: self.buffer.clone(), size: self.size, offset: self.offset, inner: range, } } /// Returns the buffer this is a slice of. /// /// You should usually not need to call this, and if you received the buffer from code you /// do not control, you should refrain from accessing the buffer outside the bounds of the /// slice. Nevertheless, it’s possible to get this access, so this method makes it simple. pub fn buffer(&self) -> &'a Buffer { self.buffer } /// Returns the offset in [`Self::buffer()`] this slice starts at. pub fn offset(&self) -> BufferAddress { self.offset } /// Returns the size of this slice. pub fn size(&self) -> BufferSize { self.size } } impl<'a> From> for crate::BufferBinding<'a> { /// Convert a [`BufferSlice`] to an equivalent [`BufferBinding`], /// provided that it will be used without a dynamic offset. fn from(value: BufferSlice<'a>) -> Self { BufferBinding { buffer: value.buffer, offset: value.offset, size: Some(value.size), } } } impl<'a> From> for crate::BindingResource<'a> { /// Convert a [`BufferSlice`] to an equivalent [`BindingResource::Buffer`], /// provided that it will be used without a dynamic offset. fn from(value: BufferSlice<'a>) -> Self { crate::BindingResource::Buffer(crate::BufferBinding::from(value)) } } fn range_overlaps(a: &Range, b: &Range) -> bool { a.start < b.end && b.start < a.end } fn range_contains(a: &Range, b: &Range) -> bool { a.start <= b.start && a.end >= b.end } #[derive(Debug, Copy, Clone)] enum RangeMappingKind { Mutable, Immutable, } impl RangeMappingKind { /// Returns true if a range of this kind can touch the same bytes as a range of the other kind. /// /// This is Rust's Mutable XOR Shared rule. fn allowed_concurrently_with(self, other: Self) -> bool { matches!( (self, other), (RangeMappingKind::Immutable, RangeMappingKind::Immutable) ) } } #[derive(Debug, Clone)] struct Subrange { index: Range, kind: RangeMappingKind, } impl Subrange { fn new(offset: BufferAddress, size: BufferSize, kind: RangeMappingKind) -> Self { Self { index: offset..(offset + size.get()), kind, } } } impl fmt::Display for Subrange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}..{} ({:?})", self.index.start, self.index.end, self.kind ) } } /// The mapped portion of a buffer, if any, and its outstanding views. /// /// This ensures that views fall within the mapped range and don't overlap. #[derive(Debug)] pub(crate) struct MapContext { /// The range of the buffer that is mapped. /// /// This is `0..0` if the buffer is not mapped. This becomes non-empty when /// the buffer is mapped at creation time, and when you call `map_async` on /// some [`BufferSlice`] (so technically, it indicates the portion that is /// *or has been requested to be* mapped.) /// /// All [`BufferView`]s and [`BufferViewMut`]s must fall within this range. mapped_range: Range, /// The ranges covered by all outstanding [`BufferView`]s and /// [`BufferViewMut`]s. These are non-overlapping, and are all contained /// within `mapped_range`. sub_ranges: Vec, } impl MapContext { /// Creates a new `MapContext`. /// /// For [`mapped_at_creation`] buffers, pass the full buffer range in the /// `mapped_range` argument. For other buffers, pass `None`. /// /// [`mapped_at_creation`]: BufferDescriptor::mapped_at_creation pub(crate) fn new(mapped_range: Option>) -> Self { Self { mapped_range: mapped_range.unwrap_or(0..0), sub_ranges: Vec::new(), } } /// Record that the buffer is no longer mapped. fn reset(&mut self) { self.mapped_range = 0..0; assert!( self.sub_ranges.is_empty(), "You cannot unmap a buffer that still has accessible mapped views" ); } /// Record that the `size` bytes of the buffer at `offset` are now viewed. /// /// # Panics /// /// This panics if the given range is invalid. #[track_caller] fn validate_and_add(&mut self, new_sub: Subrange) { if self.mapped_range.is_empty() { panic!("tried to call get_mapped_range(_mut) on an unmapped buffer"); } if !range_contains(&self.mapped_range, &new_sub.index) { panic!( "tried to call get_mapped_range(_mut) on a range that is not entirely mapped. \ Attempted to get range {}, but the mapped range is {}..{}", new_sub, self.mapped_range.start, self.mapped_range.end ); } // This check is essential for avoiding undefined behavior: it is the // only thing that ensures that `&mut` references to the buffer's // contents don't alias anything else. for sub in self.sub_ranges.iter() { if range_overlaps(&sub.index, &new_sub.index) && !sub.kind.allowed_concurrently_with(new_sub.kind) { panic!( "tried to call get_mapped_range(_mut) on a range that has already \ been mapped and would break Rust memory aliasing rules. Attempted \ to get range {}, and the conflicting range is {}", new_sub, sub ); } } self.sub_ranges.push(new_sub); } /// Record that the `size` bytes of the buffer at `offset` are no longer viewed. /// /// # Panics /// /// This panics if the given range does not exactly match one previously /// passed to [`MapContext::validate_and_add`]. pub(crate) fn remove(&mut self, offset: BufferAddress, size: BufferSize) { let end = offset + size.get(); let index = self .sub_ranges .iter() .position(|r| r.index == (offset..end)) .expect("unable to remove range from map context"); self.sub_ranges.swap_remove(index); } } /// Describes a [`Buffer`]. /// /// For use with [`Device::create_buffer`]. /// /// Corresponds to [WebGPU `GPUBufferDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpubufferdescriptor). pub type BufferDescriptor<'a> = wgt::BufferDescriptor>; static_assertions::assert_impl_all!(BufferDescriptor<'_>: Send, Sync); /// Error occurred when trying to async map a buffer. #[derive(Clone, PartialEq, Eq, Debug)] pub struct BufferAsyncError; static_assertions::assert_impl_all!(BufferAsyncError: Send, Sync); impl fmt::Display for BufferAsyncError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Error occurred when trying to async map a buffer") } } impl error::Error for BufferAsyncError {} /// Type of buffer mapping. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum MapMode { /// Map only for reading Read, /// Map only for writing Write, } static_assertions::assert_impl_all!(MapMode: Send, Sync); /// A read-only view of a mapped buffer's bytes. /// /// To get a `BufferView`, first [map] the buffer, and then /// call `buffer.slice(range).get_mapped_range()`. /// /// `BufferView` dereferences to `&[u8]`, so you can use all the usual Rust /// slice methods to access the buffer's contents. It also implements /// `AsRef<[u8]>`, if that's more convenient. /// /// Before the buffer can be unmapped, all `BufferView`s observing it /// must be dropped. Otherwise, the call to [`Buffer::unmap`] will panic. /// /// For example code, see the documentation on [mapping buffers][map]. /// /// [map]: Buffer#mapping-buffers /// [`map_async`]: BufferSlice::map_async #[derive(Debug)] pub struct BufferView { // `buffer, offset, size` are similar to `BufferSlice`, except that they own the buffer. buffer: Buffer, offset: BufferAddress, size: BufferSize, inner: dispatch::DispatchBufferMappedRange, } /// A write-only view of a mapped buffer's bytes. /// /// To get a `BufferViewMut`, first [map] the buffer, and then /// call `buffer.slice(range).get_mapped_range_mut()`. /// /// Because Rust has no write-only reference type /// (`&[u8]` is read-only and `&mut [u8]` is read-write), /// this type does not dereference to a slice in the way that [`BufferView`] does. /// Instead, [`.slice()`][BufferViewMut::slice] returns a special [`WriteOnly`] pointer type, /// and there are also a few convenience methods such as [`BufferViewMut::copy_from_slice()`]. /// /// Before the buffer can be unmapped, all `BufferViewMut`s observing it /// must be dropped. Otherwise, the call to [`Buffer::unmap`] will panic. /// /// For example code, see the documentation on [mapping buffers][map]. /// /// [map]: Buffer#mapping-buffers #[derive(Debug)] pub struct BufferViewMut { // `buffer, offset, size` are similar to `BufferSlice`, except that they own the buffer. buffer: Buffer, offset: BufferAddress, size: BufferSize, inner: dispatch::DispatchBufferMappedRange, } // `BufferView` simply dereferences. `BufferViewMut` cannot, because mapped memory may be // write-combining memory , // and not support the expected behavior of atomic accesses. // Further context: impl core::ops::Deref for BufferView { type Target = [u8]; #[inline] fn deref(&self) -> &[u8] { // SAFETY: this is a read mapping unsafe { self.inner.read_slice() } } } impl AsRef<[u8]> for BufferView { #[inline] fn as_ref(&self) -> &[u8] { self } } impl Drop for BufferView { fn drop(&mut self) { self.buffer .map_context .lock() .remove(self.offset, self.size); } } impl Drop for BufferViewMut { fn drop(&mut self) { self.buffer .map_context .lock() .remove(self.offset, self.size); } } #[cfg(webgpu)] impl BufferView { /// Provides the same data as dereferencing the view, but as a `Uint8Array` in js. /// This can be MUCH faster than dereferencing the view which copies the data into /// the Rust / wasm heap. pub fn as_uint8array(&self) -> &js_sys::Uint8Array { self.inner.as_uint8array() } } /// These methods are equivalent to the methods of the same names on [`WriteOnly`]. impl BufferViewMut { /// Returns the length of this view; the number of bytes to be written. pub fn len(&self) -> usize { // cannot fail because we can't actually map more than isize::MAX bytes usize::try_from(self.size.get()).unwrap() } /// Returns `true` if the view has a length of 0. /// /// Note that this is currently impossible. pub fn is_empty(&self) -> bool { self.len() == 0 } /// Returns a [`WriteOnly`] reference to a portion of this. /// /// `.slice(..)` can be used to access the whole data. pub fn slice<'a, S: RangeBounds>(&'a mut self, bounds: S) -> WriteOnly<'a, [u8]> { // SAFETY: this is a write mapping unsafe { self.inner.write_slice() }.into_slice(bounds) } /// Copies all elements from src into `self`. /// /// The length of `src` must be the same as `self`. /// /// This method is equivalent to /// [`self.slice(..).copy_from_slice(src)`][WriteOnly::copy_from_slice]. pub fn copy_from_slice(&mut self, src: &[u8]) { self.slice(..).copy_from_slice(src) } } #[track_caller] fn check_buffer_bounds( buffer_size: BufferAddress, slice_offset: BufferAddress, slice_size: BufferSize, ) { // A slice of length 0 is invalid, so the offset must not be equal to or greater than the buffer size. if slice_offset >= buffer_size { panic!( "slice offset {} is out of range for buffer of size {}", slice_offset, buffer_size ); } // Detect integer overflow. let end = slice_offset.checked_add(slice_size.get()); if end.is_none_or(|end| end > buffer_size) { panic!( "slice offset {} size {} is out of range for buffer of size {}", slice_offset, slice_size, buffer_size ); } } #[track_caller] pub(crate) fn range_to_offset_size>( bounds: S, whole_size: BufferAddress, ) -> (BufferAddress, BufferSize) { let offset = match bounds.start_bound() { Bound::Included(&bound) => bound, Bound::Excluded(&bound) => bound + 1, Bound::Unbounded => 0, }; let size = BufferSize::new(match bounds.end_bound() { Bound::Included(&bound) => bound + 1 - offset, Bound::Excluded(&bound) => bound - offset, Bound::Unbounded => whole_size - offset, }) .expect("buffer slices can not be empty"); (offset, size) } #[cfg(test)] mod tests { use super::{ check_buffer_bounds, range_overlaps, range_to_offset_size, BufferAddress, BufferSize, }; fn bs(value: BufferAddress) -> BufferSize { BufferSize::new(value).unwrap() } #[test] fn range_to_offset_size_works() { let whole = 100; assert_eq!(range_to_offset_size(0..2, whole), (0, bs(2))); assert_eq!(range_to_offset_size(2..5, whole), (2, bs(3))); assert_eq!(range_to_offset_size(.., whole), (0, bs(whole))); assert_eq!(range_to_offset_size(21.., whole), (21, bs(whole - 21))); assert_eq!(range_to_offset_size(0.., whole), (0, bs(whole))); assert_eq!(range_to_offset_size(..21, whole), (0, bs(21))); } #[test] #[should_panic = "buffer slices can not be empty"] fn range_to_offset_size_panics_for_empty_range() { range_to_offset_size(123..123, 200); } #[test] #[should_panic = "buffer slices can not be empty"] fn range_to_offset_size_panics_for_unbounded_empty_range() { range_to_offset_size(..0, 100); } #[test] fn check_buffer_bounds_works_for_end_in_range() { check_buffer_bounds(200, 100, bs(50)); check_buffer_bounds(200, 100, bs(100)); check_buffer_bounds(u64::MAX, u64::MAX - 100, bs(100)); check_buffer_bounds(u64::MAX, 0, bs(u64::MAX)); check_buffer_bounds(u64::MAX, 1, bs(u64::MAX - 1)); } #[test] #[should_panic] fn check_buffer_bounds_panics_for_end_over_size() { check_buffer_bounds(200, 100, bs(101)); } #[test] #[should_panic] fn check_buffer_bounds_panics_for_end_wraparound() { check_buffer_bounds(u64::MAX, 1, bs(u64::MAX)); } #[test] fn range_overlapping() { // First range to the left assert_eq!(range_overlaps(&(0..1), &(1..3)), false); // First range overlaps left edge assert_eq!(range_overlaps(&(0..2), &(1..3)), true); // First range completely inside second assert_eq!(range_overlaps(&(1..2), &(0..3)), true); // First range completely surrounds second assert_eq!(range_overlaps(&(0..3), &(1..2)), true); // First range overlaps right edge assert_eq!(range_overlaps(&(1..3), &(0..2)), true); // First range entirely to the right assert_eq!(range_overlaps(&(2..3), &(0..2)), false); } } ================================================ FILE: wgpu/src/api/command_buffer.rs ================================================ use crate::{ api::{impl_deferred_command_buffer_actions, SharedDeferredCommandBufferActions}, *, }; /// Handle to a command buffer on the GPU. /// /// A `CommandBuffer` represents a complete sequence of commands that may be submitted to a command /// queue with [`Queue::submit`]. A `CommandBuffer` is obtained by recording a series of commands to /// a [`CommandEncoder`] and then calling [`CommandEncoder::finish`]. /// /// Corresponds to [WebGPU `GPUCommandBuffer`](https://gpuweb.github.io/gpuweb/#command-buffer). #[derive(Debug)] pub struct CommandBuffer { pub(crate) buffer: dispatch::DispatchCommandBuffer, /// Deferred actions recorded at encode time, to run at Queue::submit. pub(crate) actions: SharedDeferredCommandBufferActions, } #[cfg(send_sync)] static_assertions::assert_impl_all!(CommandBuffer: Send, Sync); impl CommandBuffer { #[cfg(custom)] /// Returns custom implementation of CommandBuffer (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.buffer.as_custom() } // Expose map_buffer_on_submit/on_submitted_work_done on CommandBuffer as well, // so callers can schedule after finishing encoding. impl_deferred_command_buffer_actions!(); } ================================================ FILE: wgpu/src/api/command_buffer_actions.rs ================================================ use alloc::{sync::Arc, vec::Vec}; use core::num::NonZeroU64; use crate::{util::Mutex, *}; /// A deferred buffer mapping request captured during encoding (or a pass) /// and executed later when the command buffer is submitted. pub(crate) struct DeferredBufferMapping { pub buffer: api::Buffer, pub mode: MapMode, pub offset: u64, pub size: NonZeroU64, pub callback: dispatch::BufferMapCallback, } pub(super) type SharedDeferredCommandBufferActions = Arc>; /// Set of actions to take when the command buffer is submitted. #[derive(Default)] pub(crate) struct DeferredCommandBufferActions { pub buffer_mappings: Vec, pub on_submitted_work_done_callbacks: Vec, } impl DeferredCommandBufferActions { pub fn append(&mut self, other: &mut Self) { self.buffer_mappings.append(&mut other.buffer_mappings); self.on_submitted_work_done_callbacks .append(&mut other.on_submitted_work_done_callbacks); } pub fn execute(self, queue: &dispatch::DispatchQueue) { for mapping in self.buffer_mappings { mapping.buffer.map_async( mapping.mode, mapping.offset..mapping.offset + mapping.size.get(), mapping.callback, ); } for callback in self.on_submitted_work_done_callbacks { queue.on_submitted_work_done(callback); } } } impl core::fmt::Debug for DeferredCommandBufferActions { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("DeferredCommandBufferActions") .field("buffer_mappings.len()", &self.buffer_mappings.len()) .field( "on_submitted_work_done_callbacks.len()", &self.on_submitted_work_done_callbacks.len(), ) .finish() } } // We can't just implement this on CommandEncoders as by default passes make it so that // you can't call any commands on the encoder while this is happening. As such, we need // to implement these methods on the passes too. Use a macro to avoid massive code duplication macro_rules! impl_deferred_command_buffer_actions { () => { /// On submission, maps the buffer to host (CPU) memory, making it available /// for reading or writing via [`get_mapped_range()`](Buffer::get_mapped_range). /// The buffer becomes accessible once the `callback` is invoked with [`Ok`]. /// /// Use this when you need to submit work that uses the buffer before mapping it. /// Because that submission must happen before calling `map_async`, this method /// schedules the mapping for after submission, avoiding extra calls to /// [`Buffer::map_async()`] or [`BufferSlice::map_async()`] and letting you start /// the mapping from a more convenient place. /// /// For the callback to run, either [`queue.submit(..)`][q::s], [`instance.poll_all(..)`][i::p_a], /// or [`device.poll(..)`][d::p] must be called elsewhere in the runtime, possibly integrated /// into an event loop or run on a separate thread. /// /// The callback runs on the thread that first calls one of the above functions /// after the GPU work completes. There are no restrictions on the code you can run /// in the callback; however, on native the polling call will not return until the /// callback finishes, so keep callbacks short (set flags, send messages, etc.). /// /// While a buffer is mapped, it cannot be used by other commands; at any time, /// either the GPU or the CPU has exclusive access to the buffer’s contents. /// /// # Panics /// /// - If `bounds` is outside the bounds of `buffer`. /// - If `bounds` has a length less than 1. /// /// # Panics During Submit /// /// - If the buffer is already mapped. /// - If the buffer’s [`BufferUsages`] do not allow the requested [`MapMode`]. /// - If `bounds` is outside of the bounds of `buffer`. /// - If `bounds` does not start at a multiple of [`MAP_ALIGNMENT`]. /// - If `bounds` has a length that is not a multiple of 4 greater than 0. /// /// [q::s]: Queue::submit /// [i::p_a]: Instance::poll_all /// [d::p]: Device::poll /// [CEmbos]: CommandEncoder::map_buffer_on_submit /// [CBmbos]: CommandBuffer::map_buffer_on_submit /// [RPmbos]: RenderPass::map_buffer_on_submit /// [CPmbos]: ComputePass::map_buffer_on_submit pub fn map_buffer_on_submit>( &self, buffer: &api::Buffer, mode: MapMode, bounds: S, callback: impl FnOnce(Result<(), BufferAsyncError>) + WasmNotSend + 'static, ) { let (offset, size) = range_to_offset_size(bounds, buffer.size); self.actions.lock().buffer_mappings.push( crate::api::command_buffer_actions::DeferredBufferMapping { buffer: buffer.clone(), mode, offset, size, callback: alloc::boxed::Box::new(callback), }, ); } /// Registers a callback that is invoked when this command buffer’s work finishes /// executing on the GPU. When this callback runs, all mapped-buffer callbacks /// registered for the same submission are guaranteed to have been called. /// /// For the callback to run, either [`queue.submit(..)`][q::s], [`instance.poll_all(..)`][i::p_a], /// or [`device.poll(..)`][d::p] must be called elsewhere in the runtime, possibly integrated /// into an event loop or run on a separate thread. /// /// The callback runs on the thread that first calls one of the above functions /// after the GPU work completes. There are no restrictions on the code you can run /// in the callback; however, on native the polling call will not return until the /// callback finishes, so keep callbacks short (set flags, send messages, etc.). /// /// [q::s]: Queue::submit /// [i::p_a]: Instance::poll_all /// [d::p]: Device::poll pub fn on_submitted_work_done(&self, callback: impl FnOnce() + Send + 'static) { self.actions .lock() .on_submitted_work_done_callbacks .push(alloc::boxed::Box::new(callback)); } }; } pub(crate) use impl_deferred_command_buffer_actions; ================================================ FILE: wgpu/src/api/command_encoder.rs ================================================ use alloc::sync::Arc; use core::ops::Range; use crate::{ api::{ blas::BlasBuildEntry, impl_deferred_command_buffer_actions, tlas::Tlas, SharedDeferredCommandBufferActions, }, *, }; /// Encodes a series of GPU operations. /// /// A command encoder can record [`RenderPass`]es, [`ComputePass`]es, /// and transfer operations between driver-managed resources like [`Buffer`]s and [`Texture`]s. /// /// When finished recording, call [`CommandEncoder::finish`] to obtain a [`CommandBuffer`] which may /// be submitted for execution. /// /// Corresponds to [WebGPU `GPUCommandEncoder`](https://gpuweb.github.io/gpuweb/#command-encoder). #[derive(Debug)] pub struct CommandEncoder { pub(crate) inner: dispatch::DispatchCommandEncoder, pub(crate) actions: SharedDeferredCommandBufferActions, } #[cfg(send_sync)] static_assertions::assert_impl_all!(CommandEncoder: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(CommandEncoder => .inner); /// Describes a [`CommandEncoder`]. /// /// For use with [`Device::create_command_encoder`]. /// /// Corresponds to [WebGPU `GPUCommandEncoderDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpucommandencoderdescriptor). pub type CommandEncoderDescriptor<'a> = wgt::CommandEncoderDescriptor>; static_assertions::assert_impl_all!(CommandEncoderDescriptor<'_>: Send, Sync); pub use wgt::TexelCopyBufferInfo as TexelCopyBufferInfoBase; /// View of a buffer which can be used to copy to/from a texture. /// /// Corresponds to [WebGPU `GPUTexelCopyBufferInfo`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopybuffer). pub type TexelCopyBufferInfo<'a> = TexelCopyBufferInfoBase<&'a Buffer>; #[cfg(send_sync)] static_assertions::assert_impl_all!(TexelCopyBufferInfo<'_>: Send, Sync); pub use wgt::TexelCopyTextureInfo as TexelCopyTextureInfoBase; /// View of a texture which can be used to copy to/from a buffer/texture. /// /// Corresponds to [WebGPU `GPUTexelCopyTextureInfo`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopytexture). pub type TexelCopyTextureInfo<'a> = TexelCopyTextureInfoBase<&'a Texture>; #[cfg(send_sync)] static_assertions::assert_impl_all!(TexelCopyTextureInfo<'_>: Send, Sync); impl CommandEncoder { /// Finishes recording and returns a [`CommandBuffer`] that can be submitted for execution. pub fn finish(self) -> CommandBuffer { let Self { mut inner, actions } = self; let buffer = inner.finish(); CommandBuffer { buffer, actions } } /// Begins recording of a render pass. /// /// This function returns a [`RenderPass`] object which records a single render pass. /// /// As long as the returned [`RenderPass`] has not ended, /// any mutating operation on this command encoder causes an error and invalidates it. /// Note that the `'encoder` lifetime relationship protects against this, /// but it is possible to opt out of it by calling [`RenderPass::forget_lifetime`]. /// This can be useful for runtime handling of the encoder->pass /// dependency e.g. when pass and encoder are stored in the same data structure. pub fn begin_render_pass<'encoder>( &'encoder mut self, desc: &RenderPassDescriptor<'_>, ) -> RenderPass<'encoder> { let rpass = self.inner.begin_render_pass(desc); RenderPass { inner: rpass, actions: Arc::clone(&self.actions), _encoder_guard: api::PhantomDrop::default(), } } /// Begins recording of a compute pass. /// /// This function returns a [`ComputePass`] object which records a single compute pass. /// /// As long as the returned [`ComputePass`] has not ended, /// any mutating operation on this command encoder causes an error and invalidates it. /// Note that the `'encoder` lifetime relationship protects against this, /// but it is possible to opt out of it by calling [`ComputePass::forget_lifetime`]. /// This can be useful for runtime handling of the encoder->pass /// dependency e.g. when pass and encoder are stored in the same data structure. pub fn begin_compute_pass<'encoder>( &'encoder mut self, desc: &ComputePassDescriptor<'_>, ) -> ComputePass<'encoder> { let cpass = self.inner.begin_compute_pass(desc); ComputePass { inner: cpass, actions: Arc::clone(&self.actions), _encoder_guard: api::PhantomDrop::default(), } } /// Copy data from one buffer to another. /// /// # Panics /// /// - Buffer offsets or copy size not a multiple of [`COPY_BUFFER_ALIGNMENT`]. /// - Copy would overrun buffer. /// - Copy within the same buffer. pub fn copy_buffer_to_buffer( &mut self, source: &Buffer, source_offset: BufferAddress, destination: &Buffer, destination_offset: BufferAddress, copy_size: impl Into>, ) { self.inner.copy_buffer_to_buffer( &source.inner, source_offset, &destination.inner, destination_offset, copy_size.into(), ); } /// Copy data from a buffer to a texture. pub fn copy_buffer_to_texture( &mut self, source: TexelCopyBufferInfo<'_>, destination: TexelCopyTextureInfo<'_>, copy_size: Extent3d, ) { self.inner .copy_buffer_to_texture(source, destination, copy_size); } /// Copy data from a texture to a buffer. pub fn copy_texture_to_buffer( &mut self, source: TexelCopyTextureInfo<'_>, destination: TexelCopyBufferInfo<'_>, copy_size: Extent3d, ) { self.inner .copy_texture_to_buffer(source, destination, copy_size); } /// Copy data from one texture to another. /// /// # Panics /// /// - Textures are not the same type /// - If a depth texture, or a multisampled texture, the entire texture must be copied /// - Copy would overrun either texture pub fn copy_texture_to_texture( &mut self, source: TexelCopyTextureInfo<'_>, destination: TexelCopyTextureInfo<'_>, copy_size: Extent3d, ) { self.inner .copy_texture_to_texture(source, destination, copy_size); } /// Clears texture to zero. /// /// Note that unlike with clear_buffer, `COPY_DST` usage is not required. /// /// # Implementation notes /// /// - implemented either via buffer copies and render/depth target clear, path depends on texture usages /// - behaves like texture zero init, but is performed immediately (clearing is *not* delayed via marking it as uninitialized) /// /// # Panics /// /// - `CLEAR_TEXTURE` extension not enabled /// - Range is out of bounds pub fn clear_texture(&mut self, texture: &Texture, subresource_range: &ImageSubresourceRange) { self.inner.clear_texture(&texture.inner, subresource_range); } /// Clears buffer to zero. /// /// # Panics /// /// - Buffer does not have `COPY_DST` usage. /// - Range is out of bounds pub fn clear_buffer( &mut self, buffer: &Buffer, offset: BufferAddress, size: Option, ) { self.inner.clear_buffer(&buffer.inner, offset, size); } /// Inserts debug marker. pub fn insert_debug_marker(&mut self, label: &str) { self.inner.insert_debug_marker(label); } /// Start record commands and group it into debug marker group. pub fn push_debug_group(&mut self, label: &str) { self.inner.push_debug_group(label); } /// Stops command recording and creates debug group. pub fn pop_debug_group(&mut self) { self.inner.pop_debug_group(); } /// Copies query results stored in `query_set` into `destination` so that they can be read /// by compute shaders or buffer operations. /// /// * `query_range` is the range of query result indices to copy from `query_set`. /// Occlusion and timestamp queries occupy 1 result index each; /// for pipeline statistics queries, see [`PipelineStatisticsTypes`]. /// * `destination_offset` is the offset within `destination` to start writing at. /// It must be a multiple of [`QUERY_RESOLVE_BUFFER_ALIGNMENT`]. /// /// The length of the data written to `destination` will be 8 bytes ([`QUERY_SIZE`]) /// times the number of elements in `query_range`. /// /// For further information about using queries, see [`QuerySet`]. pub fn resolve_query_set( &mut self, query_set: &QuerySet, query_range: Range, destination: &Buffer, destination_offset: BufferAddress, ) { self.inner.resolve_query_set( &query_set.inner, query_range.start, query_range.end - query_range.start, &destination.inner, destination_offset, ); } impl_deferred_command_buffer_actions!(); /// Get the [`wgpu_hal`] command encoder from this `CommandEncoder`. /// /// The returned command encoder will be ready to record onto. /// /// # Errors /// /// This method will pass in [`None`] if: /// - The encoder is not from the backend specified by `A`. /// - The encoder is from the `webgpu` or `custom` backend. /// /// # Types /// /// The callback argument depends on the backend: /// #[doc = crate::macros::hal_type_vulkan!("CommandEncoder")] #[doc = crate::macros::hal_type_metal!("CommandEncoder")] #[doc = crate::macros::hal_type_dx12!("CommandEncoder")] #[doc = crate::macros::hal_type_gles!("CommandEncoder")] /// /// # Safety /// /// - The raw handle obtained from the `A::CommandEncoder` must not be manually destroyed. /// - You must not end the command buffer; wgpu will do it when you call finish. /// - The wgpu command encoder must not be interacted with in any way while recording is /// happening to the wgpu_hal or backend command encoder. #[cfg(wgpu_core)] pub unsafe fn as_hal_mut) -> R, R>( &mut self, hal_command_encoder_callback: F, ) -> R { if let Some(encoder) = self.inner.as_core_mut_opt() { unsafe { encoder .context .command_encoder_as_hal_mut::(encoder, hal_command_encoder_callback) } } else { hal_command_encoder_callback(None) } } #[cfg(custom)] /// Returns custom implementation of CommandEncoder (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// [`Features::TIMESTAMP_QUERY_INSIDE_ENCODERS`] must be enabled on the device in order to call these functions. impl CommandEncoder { /// Issue a timestamp command at this point in the queue. /// The timestamp will be written to the specified query set, at the specified index. /// /// Must be multiplied by [`Queue::get_timestamp_period`] to get /// the value in nanoseconds. Absolute values have no meaning, /// but timestamps can be subtracted to get the time it takes /// for a string of operations to complete. /// /// Attention: Since commands within a command recorder may be reordered, /// there is no strict guarantee that timestamps are taken after all commands /// recorded so far and all before all commands recorded after. /// This may depend both on the backend and the driver. pub fn write_timestamp(&mut self, query_set: &QuerySet, query_index: u32) { self.inner.write_timestamp(&query_set.inner, query_index); } } /// [`Features::EXPERIMENTAL_RAY_QUERY`] must be enabled on the device in order to call these functions. impl CommandEncoder { /// When encoding the acceleration structure build with the raw Hal encoder /// (obtained from [`CommandEncoder::as_hal_mut`]), this function marks the /// acceleration structures as having been built. /// /// This function must only be used with the raw encoder API. When using the /// wgpu encoding API, acceleration structure build is tracked automatically. /// /// # Panics /// /// - If the encoder is being used with the wgpu encoding API. /// /// # Safety /// /// - All acceleration structures must have been build in this command encoder. /// - All BLASes inputted must have been built before all TLASes that were inputted here and /// which use them. pub unsafe fn mark_acceleration_structures_built<'a>( &self, blas: impl IntoIterator, tlas: impl IntoIterator, ) { self.inner .mark_acceleration_structures_built(&mut blas.into_iter(), &mut tlas.into_iter()) } /// Build bottom and top level acceleration structures. /// /// Builds the BLASes then the TLASes, but does ***not*** build the BLASes into the TLASes, /// that must be done by setting a TLAS instance in the TLAS package to one that contains the BLAS (and with an appropriate transform) /// /// # Validation /// /// - blas: Iterator of bottom level acceleration structure entries to build. /// For each entry, the provided size descriptor must be strictly smaller or equal to the descriptor given at BLAS creation, this means: /// - Less or equal number of geometries /// - Same kind of geometry (with index buffer or without) (same vertex/index format) /// - Same flags /// - Less or equal number of vertices /// - Less or equal number of indices (if applicable) /// - tlas: iterator of top level acceleration structure packages to build /// For each entry: /// - Each BLAS in each TLAS instance must have been being built in the current call or in a previous call to `build_acceleration_structures` or `build_acceleration_structures_unsafe_tlas` /// - The number of TLAS instances must be less than or equal to the max number of tlas instances when creating (if creating a package with `TlasPackage::new()` this is already satisfied) /// /// If the device the command encoder is created from does not have [Features::EXPERIMENTAL_RAY_QUERY] enabled then a validation error is generated /// /// A bottom level acceleration structure may be build and used as a reference in a top level acceleration structure in the same invocation of this function. /// /// # Bind group usage /// /// When a top level acceleration structure is used in a bind group, some validation takes place: /// - The top level acceleration structure is valid and has been built. /// - All the bottom level acceleration structures referenced by the top level acceleration structure are valid and have been built prior, /// or at same time as the containing top level acceleration structure. /// /// [Features::EXPERIMENTAL_RAY_QUERY]: wgt::Features::EXPERIMENTAL_RAY_QUERY pub fn build_acceleration_structures<'a>( &mut self, blas: impl IntoIterator>, tlas: impl IntoIterator, ) { self.inner .build_acceleration_structures(&mut blas.into_iter(), &mut tlas.into_iter()); } /// Transition resources to an underlying hal resource state. /// /// This is an advanced, native-only API (no-op on web) that has two main use cases: /// /// # Batching Barriers /// /// Wgpu does not have a global view of the frame when recording command buffers. When you submit multiple command buffers in a single queue submission, wgpu may need to record and /// insert new command buffers (holding 1 or more barrier commands) in between the user-supplied command buffers in order to ensure that resources are transitioned to the correct state /// for the start of the next user-supplied command buffer. /// /// Wgpu does not currently attempt to batch multiple of these generated command buffers/barriers together, which may lead to suboptimal barrier placement. /// /// Consider the following scenario, where the user does `queue.submit(&[a, b, c])`: /// * CommandBuffer A: Use resource X as a render pass attachment /// * CommandBuffer B: Use resource Y as a render pass attachment /// * CommandBuffer C: Use resources X and Y in a bind group /// /// At submission time, wgpu will record and insert some new command buffers, resulting in a submission that looks like `queue.submit(&[0, a, 1, b, 2, c])`: /// * CommandBuffer 0: Barrier to transition resource X from TextureUses::RESOURCE (from last frame) to TextureUses::COLOR_TARGET /// * CommandBuffer A: Use resource X as a render pass attachment /// * CommandBuffer 1: Barrier to transition resource Y from TextureUses::RESOURCE (from last frame) to TextureUses::COLOR_TARGET /// * CommandBuffer B: Use resource Y as a render pass attachment /// * CommandBuffer 2: Barrier to transition resources X and Y from TextureUses::COLOR_TARGET to TextureUses::RESOURCE /// * CommandBuffer C: Use resources X and Y in a bind group /// /// To prevent this, after profiling their app, an advanced user might choose to instead do `queue.submit(&[a, b, c])`: /// * CommandBuffer A: /// * Use [`CommandEncoder::transition_resources`] to transition resources X and Y from TextureUses::RESOURCE (from last frame) to TextureUses::COLOR_TARGET /// * Use resource X as a render pass attachment /// * CommandBuffer B: Use resource Y as a render pass attachment /// * CommandBuffer C: /// * Use [`CommandEncoder::transition_resources`] to transition resources X and Y from TextureUses::COLOR_TARGET to TextureUses::RESOURCE /// * Use resources X and Y in a bind group /// /// At submission time, wgpu will record and insert some new command buffers, resulting in a submission that looks like `queue.submit(&[0, a, b, 1, c])`: /// * CommandBuffer 0: Barrier to transition resources X and Y from TextureUses::RESOURCE (from last frame) to TextureUses::COLOR_TARGET /// * CommandBuffer A: Use resource X as a render pass attachment /// * CommandBuffer B: Use resource Y as a render pass attachment /// * CommandBuffer 1: Barrier to transition resources X and Y from TextureUses::COLOR_TARGET to TextureUses::RESOURCE /// * CommandBuffer C: Use resources X and Y in a bind group /// /// Which eliminates the extra command buffer and barrier between command buffers A and B. /// /// # Native Interoperability /// /// A user wanting to interoperate with the underlying native graphics APIs (Vulkan, DirectX12, Metal, etc) can use this API to generate barriers between wgpu commands and /// the native API commands, for synchronization and resource state transition purposes. pub fn transition_resources<'a>( &mut self, buffer_transitions: impl Iterator>, texture_transitions: impl Iterator>, ) { self.inner.transition_resources( &mut buffer_transitions.map(|t| wgt::BufferTransition { buffer: &t.buffer.inner, state: t.state, }), &mut texture_transitions.map(|t| wgt::TextureTransition { texture: &t.texture.inner, selector: t.selector, state: t.state, }), ); } } ================================================ FILE: wgpu/src/api/common_pipeline.rs ================================================ use crate::*; #[derive(Clone, Debug)] /// Advanced options for use when a pipeline is compiled /// /// This implements `Default`, and for most users can be set to `Default::default()` pub struct PipelineCompilationOptions<'a> { /// Specifies the values of pipeline-overridable constants in the shader module. /// /// If an `@id` attribute was specified on the declaration, /// the key must be the pipeline constant ID as a decimal ASCII number; if not, /// the key must be the constant's identifier name. /// /// If the given constant is specified more than once, the last value specified is used. /// /// The value may represent any of WGSL's concrete scalar types. pub constants: &'a [(&'a str, f64)], /// Whether workgroup scoped memory will be initialized with zero values for this stage. /// /// This is required by the WebGPU spec, but may have overhead which can be avoided /// for cross-platform applications pub zero_initialize_workgroup_memory: bool, } impl Default for PipelineCompilationOptions<'_> { fn default() -> Self { Self { constants: Default::default(), zero_initialize_workgroup_memory: true, } } } /// Describes a pipeline cache, which allows reusing compilation work /// between program runs. /// /// For use with [`Device::create_pipeline_cache`]. /// /// This type is unique to the Rust API of `wgpu`. #[derive(Clone, Debug)] pub struct PipelineCacheDescriptor<'a> { /// Debug label of the pipeline cache. This might show up in some logs from `wgpu` pub label: Label<'a>, /// The data used to initialise the cache initialise /// /// # Safety /// /// This data must have been provided from a previous call to /// [`PipelineCache::get_data`], if not `None` pub data: Option<&'a [u8]>, /// Whether to create a cache without data when the provided data /// is invalid. /// /// Recommended to set to true pub fallback: bool, } #[cfg(send_sync)] static_assertions::assert_impl_all!(PipelineCacheDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/compute_pass.rs ================================================ use crate::{ api::{impl_deferred_command_buffer_actions, SharedDeferredCommandBufferActions}, *, }; /// In-progress recording of a compute pass. /// /// It can be created with [`CommandEncoder::begin_compute_pass`]. /// /// Corresponds to [WebGPU `GPUComputePassEncoder`]( /// https://gpuweb.github.io/gpuweb/#compute-pass-encoder). #[derive(Debug)] pub struct ComputePass<'encoder> { pub(crate) inner: dispatch::DispatchComputePass, /// Shared with CommandEncoder to enqueue deferred actions from within a pass. pub(crate) actions: SharedDeferredCommandBufferActions, /// This lifetime is used to protect the [`CommandEncoder`] from being used /// while the pass is alive. This needs to be PhantomDrop to prevent the lifetime /// from being shortened. pub(crate) _encoder_guard: crate::api::PhantomDrop<&'encoder ()>, } #[cfg(send_sync)] static_assertions::assert_impl_all!(ComputePass<'_>: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(ComputePass<'_> => .inner); impl ComputePass<'_> { /// Drops the lifetime relationship to the parent command encoder, making usage of /// the encoder while this pass is recorded a run-time error instead. /// /// Attention: As long as the compute pass has not been ended, any mutating operation on the parent /// command encoder will cause a run-time error and invalidate it! /// By default, the lifetime constraint prevents this, but it can be useful /// to handle this at run time, such as when storing the pass and encoder in the same /// data structure. /// /// This operation has no effect on pass recording. /// It's a safe operation, since [`CommandEncoder`] is in a locked state as long as the pass is active /// regardless of the lifetime constraint or its absence. pub fn forget_lifetime(self) -> ComputePass<'static> { ComputePass { inner: self.inner, actions: self.actions, _encoder_guard: crate::api::PhantomDrop::default(), } } /// Sets the active bind group for a given bind group index. The bind group layout /// in the active pipeline when the `dispatch()` function is called must match the layout of this bind group. /// /// If the bind group have dynamic offsets, provide them in the binding order. /// These offsets have to be aligned to [`Limits::min_uniform_buffer_offset_alignment`] /// or [`Limits::min_storage_buffer_offset_alignment`] appropriately. pub fn set_bind_group<'a, BG>(&mut self, index: u32, bind_group: BG, offsets: &[DynamicOffset]) where Option<&'a BindGroup>: From, { let bg: Option<&BindGroup> = bind_group.into(); let bg = bg.map(|bg| &bg.inner); self.inner.set_bind_group(index, bg, offsets); } /// Sets the active compute pipeline. pub fn set_pipeline(&mut self, pipeline: &ComputePipeline) { self.inner.set_pipeline(&pipeline.inner); } /// Inserts debug marker. pub fn insert_debug_marker(&mut self, label: &str) { self.inner.insert_debug_marker(label); } /// Start record commands and group it into debug marker group. pub fn push_debug_group(&mut self, label: &str) { self.inner.push_debug_group(label); } /// Stops command recording and creates debug group. pub fn pop_debug_group(&mut self) { self.inner.pop_debug_group(); } /// Dispatches compute work operations. /// /// `x`, `y` and `z` denote the number of work groups to dispatch in each dimension. pub fn dispatch_workgroups(&mut self, x: u32, y: u32, z: u32) { self.inner.dispatch_workgroups(x, y, z); } /// Dispatches compute work operations, based on the contents of the `indirect_buffer`. /// /// The structure expected in `indirect_buffer` must conform to [`DispatchIndirectArgs`](crate::util::DispatchIndirectArgs). pub fn dispatch_workgroups_indirect( &mut self, indirect_buffer: &Buffer, indirect_offset: BufferAddress, ) { self.inner .dispatch_workgroups_indirect(&indirect_buffer.inner, indirect_offset); } impl_deferred_command_buffer_actions!(); #[cfg(custom)] /// Returns custom implementation of ComputePass (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// [`Features::IMMEDIATES`] must be enabled on the device in order to call these functions. impl ComputePass<'_> { /// Set immediate data for subsequent dispatch calls. /// /// Write the bytes in `data` at offset `offset` within immediate data /// storage. Both `offset` and the length of `data` must be /// multiples of [`crate::IMMEDIATE_DATA_ALIGNMENT`], which is always 4. /// /// For example, if `offset` is `4` and `data` is eight bytes long, this /// call will write `data` to bytes `4..12` of immediate data storage. pub fn set_immediates(&mut self, offset: u32, data: &[u8]) { self.inner.set_immediates(offset, data); } } /// [`Features::TIMESTAMP_QUERY_INSIDE_PASSES`] must be enabled on the device in order to call these functions. impl ComputePass<'_> { /// Issue a timestamp command at this point in the queue. The timestamp will be written to the specified query set, at the specified index. /// /// Must be multiplied by [`Queue::get_timestamp_period`] to get /// the value in nanoseconds. Absolute values have no meaning, /// but timestamps can be subtracted to get the time it takes /// for a string of operations to complete. pub fn write_timestamp(&mut self, query_set: &QuerySet, query_index: u32) { self.inner.write_timestamp(&query_set.inner, query_index); } } /// [`Features::PIPELINE_STATISTICS_QUERY`] must be enabled on the device in order to call these functions. impl ComputePass<'_> { /// Start a pipeline statistics query on this compute pass. It can be ended with /// `end_pipeline_statistics_query`. Pipeline statistics queries may not be nested. /// /// The amount of information collected by this query, and the space occupied in the query set, /// is determined by the [`PipelineStatisticsTypes`] the query set was created with. /// `query_index` is the index of the first query result slot that will be written to, and /// `query_set` must have sufficient size to hold all results written starting at that slot. pub fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, query_index: u32) { self.inner .begin_pipeline_statistics_query(&query_set.inner, query_index); } /// End the pipeline statistics query on this compute pass. It can be started with /// `begin_pipeline_statistics_query`. Pipeline statistics queries may not be nested. pub fn end_pipeline_statistics_query(&mut self) { self.inner.end_pipeline_statistics_query(); } } /// Describes the timestamp writes of a compute pass. /// /// For use with [`ComputePassDescriptor`]. /// At least one of `beginning_of_pass_write_index` and `end_of_pass_write_index` must be `Some`. /// /// Corresponds to [WebGPU `GPUComputePassTimestampWrites`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpucomputepasstimestampwrites). #[derive(Clone, Debug)] pub struct ComputePassTimestampWrites<'a> { /// The query set to write to. pub query_set: &'a QuerySet, /// The index of the query set at which a start timestamp of this pass is written, if any. pub beginning_of_pass_write_index: Option, /// The index of the query set at which an end timestamp of this pass is written, if any. pub end_of_pass_write_index: Option, } #[cfg(send_sync)] static_assertions::assert_impl_all!(ComputePassTimestampWrites<'_>: Send, Sync); /// Describes the attachments of a compute pass. /// /// For use with [`CommandEncoder::begin_compute_pass`]. /// /// Corresponds to [WebGPU `GPUComputePassDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpucomputepassdescriptor). #[derive(Clone, Default, Debug)] pub struct ComputePassDescriptor<'a> { /// Debug label of the compute pass. This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// Defines which timestamp values will be written for this pass, and where to write them to. /// /// Requires [`Features::TIMESTAMP_QUERY`] to be enabled. pub timestamp_writes: Option>, } #[cfg(send_sync)] static_assertions::assert_impl_all!(ComputePassDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/compute_pipeline.rs ================================================ use crate::*; /// Handle to a compute pipeline. /// /// A `ComputePipeline` object represents a compute pipeline and its single shader stage. /// It can be created with [`Device::create_compute_pipeline`]. /// /// Corresponds to [WebGPU `GPUComputePipeline`](https://gpuweb.github.io/gpuweb/#compute-pipeline). #[derive(Debug, Clone)] pub struct ComputePipeline { pub(crate) inner: dispatch::DispatchComputePipeline, } #[cfg(send_sync)] static_assertions::assert_impl_all!(ComputePipeline: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(ComputePipeline => .inner); impl ComputePipeline { /// Get an object representing the bind group layout at a given index. /// /// If this pipeline was created with a [default layout][ComputePipelineDescriptor::layout], /// then bind groups created with the returned `BindGroupLayout` can only be used with this /// pipeline. /// /// This method will raise a validation error if there is no bind group layout at `index`. pub fn get_bind_group_layout(&self, index: u32) -> BindGroupLayout { let bind_group = self.inner.get_bind_group_layout(index); BindGroupLayout { inner: bind_group } } #[cfg(custom)] /// Returns custom implementation of ComputePipeline (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } } /// Describes a compute pipeline. /// /// For use with [`Device::create_compute_pipeline`]. /// /// Corresponds to [WebGPU `GPUComputePipelineDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpucomputepipelinedescriptor). #[derive(Clone, Debug)] pub struct ComputePipelineDescriptor<'a> { /// Debug label of the pipeline. This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// The layout of bind groups for this pipeline. /// /// If this is set, then [`Device::create_compute_pipeline`] will raise a validation error if /// the layout doesn't match what the shader module(s) expect. /// /// Using the same [`PipelineLayout`] for many [`RenderPipeline`] or [`ComputePipeline`] /// pipelines guarantees that you don't have to rebind any resources when switching between /// those pipelines. /// /// ## Default pipeline layout /// /// If `layout` is `None`, then the pipeline has a [default layout] created and used instead. /// The default layout is deduced from the shader modules. /// /// You can use [`ComputePipeline::get_bind_group_layout`] to create bind groups for use with /// the default layout. However, these bind groups cannot be used with any other pipelines. This /// is convenient for simple pipelines, but using an explicit layout is recommended in most /// cases. /// /// [default layout]: https://www.w3.org/TR/webgpu/#default-pipeline-layout pub layout: Option<&'a PipelineLayout>, /// The compiled shader module for this stage. pub module: &'a ShaderModule, /// The name of the entry point in the compiled shader to use. /// /// If [`Some`], there must be a compute shader entry point with this name in `module`. /// Otherwise, expect exactly one compute shader entry point in `module`, which will be /// selected. // NOTE: keep phrasing in sync. with `FragmentState::entry_point` // NOTE: keep phrasing in sync. with `VertexState::entry_point` pub entry_point: Option<&'a str>, /// Advanced options for when this pipeline is compiled /// /// This implements `Default`, and for most users can be set to `Default::default()` pub compilation_options: PipelineCompilationOptions<'a>, /// The pipeline cache to use when creating this pipeline. pub cache: Option<&'a PipelineCache>, } #[cfg(send_sync)] static_assertions::assert_impl_all!(ComputePipelineDescriptor<'_>: Send, Sync); ================================================ FILE: wgpu/src/api/device.rs ================================================ use alloc::{boxed::Box, string::String, sync::Arc, vec}; #[cfg(wgpu_core)] use core::ops::Deref; use core::{error, fmt, future::Future, marker::PhantomData}; use crate::api::blas::{Blas, BlasGeometrySizeDescriptors, CreateBlasDescriptor}; use crate::api::tlas::{CreateTlasDescriptor, Tlas}; use crate::util::Mutex; use crate::*; /// Open connection to a graphics and/or compute device. /// /// Responsible for the creation of most rendering and compute resources. /// These are then used in commands, which are submitted to a [`Queue`]. /// /// A device may be requested from an adapter with [`Adapter::request_device`]. /// /// Corresponds to [WebGPU `GPUDevice`](https://gpuweb.github.io/gpuweb/#gpu-device). #[derive(Debug, Clone)] pub struct Device { pub(crate) inner: dispatch::DispatchDevice, } #[cfg(send_sync)] static_assertions::assert_impl_all!(Device: Send, Sync); crate::cmp::impl_eq_ord_hash_proxy!(Device => .inner); /// Describes a [`Device`]. /// /// For use with [`Adapter::request_device`]. /// /// Corresponds to [WebGPU `GPUDeviceDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpudevicedescriptor). pub type DeviceDescriptor<'a> = wgt::DeviceDescriptor>; static_assertions::assert_impl_all!(DeviceDescriptor<'_>: Send, Sync); impl Device { #[cfg(custom)] /// Returns custom implementation of Device (if custom backend and is internally T) pub fn as_custom(&self) -> Option<&T> { self.inner.as_custom() } #[cfg(custom)] /// Creates Device from custom implementation pub fn from_custom(device: T) -> Self { Self { inner: dispatch::DispatchDevice::custom(device), } } /// Constructs a stub device for testing using [`Backend::Noop`]. /// /// This is a convenience function which avoids the configuration, `async`, and fallibility /// aspects of constructing a device through `Instance`. #[cfg(feature = "noop")] pub fn noop(desc: &DeviceDescriptor<'_>) -> (Device, Queue) { use core::future::Future as _; use core::pin::pin; use core::task; let ctx = &mut task::Context::from_waker(task::Waker::noop()); let instance = Instance::new(InstanceDescriptor { backends: Backends::NOOP, backend_options: BackendOptions { noop: NoopBackendOptions { enable: true }, ..Default::default() }, ..InstanceDescriptor::new_without_display_handle() }); // Both of these futures are trivial and should complete instantaneously, // so we do not need an executor and can just poll them once. let task::Poll::Ready(Ok(adapter)) = pin!(instance.request_adapter(&RequestAdapterOptions::default())).poll(ctx) else { unreachable!() }; let task::Poll::Ready(Ok(device_and_queue)) = pin!(adapter.request_device(desc)).poll(ctx) else { unreachable!() }; device_and_queue } /// Check for resource cleanups and mapping callbacks. Will block if [`PollType::Wait`] is passed. /// /// Return `true` if the queue is empty, or `false` if there are more queue /// submissions still in flight. (Note that, unless access to the [`Queue`] is /// coordinated somehow, this information could be out of date by the time /// the caller receives it. `Queue`s can be shared between threads, so /// other threads could submit new work at any time.) /// /// When running on WebGPU, this is a no-op. `Device`s are automatically polled. pub fn poll(&self, poll_type: PollType) -> Result { self.inner.poll(poll_type.map_index(|s| s.index)) } /// The [features][Features] which can be used on this device. /// /// This will be equal to the [`required_features`][DeviceDescriptor::required_features] /// specified when creating the device. /// No additional features can be used, even if the underlying adapter can support them. #[must_use] pub fn features(&self) -> Features { self.inner.features() } /// The limits which can be used on this device. /// /// This will be equal to the [`required_limits`][DeviceDescriptor::required_limits] /// specified when creating the device. /// No better limits can be used, even if the underlying adapter can support them. #[must_use] pub fn limits(&self) -> Limits { self.inner.limits() } /// Get info about the adapter that this device was created from. pub fn adapter_info(&self) -> AdapterInfo { self.inner.adapter_info() } /// Creates a shader module. /// ///