@woosh/meep-engine 3.9.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/bundle-worker-image-decoder.js +1 -1
- package/build/bundle-worker-terrain.js +1 -1
- package/package.json +3 -2
- package/src/avif/encode_image_source.d.ts +70 -0
- package/src/avif/encode_image_source.d.ts.map +1 -0
- package/src/avif/encode_image_source.js +117 -0
- package/src/avif/encoder_worker.js +15 -126
- package/src/avif/index.d.ts +4 -2
- package/src/avif/index.js +21 -2
- package/src/avif/native/DECISIONS.md +537 -0
- package/src/avif/native/NOTICE.md +61 -0
- package/src/avif/native/api/AvifDecoder.d.ts +67 -0
- package/src/avif/native/api/AvifDecoder.d.ts.map +1 -0
- package/src/avif/native/api/AvifDecoder.js +121 -0
- package/src/avif/native/api/apply_transformations.d.ts +30 -0
- package/src/avif/native/api/apply_transformations.d.ts.map +1 -0
- package/src/avif/native/api/apply_transformations.js +120 -0
- package/src/avif/native/api/avif_to_sampler2d.d.ts +25 -0
- package/src/avif/native/api/avif_to_sampler2d.d.ts.map +1 -0
- package/src/avif/native/api/avif_to_sampler2d.js +42 -0
- package/src/avif/native/api/convert_to_rgba.d.ts +54 -0
- package/src/avif/native/api/convert_to_rgba.d.ts.map +1 -0
- package/src/avif/native/api/convert_to_rgba.js +204 -0
- package/src/avif/native/api/decode_avif.d.ts +24 -0
- package/src/avif/native/api/decode_avif.d.ts.map +1 -0
- package/src/avif/native/api/decode_avif.js +111 -0
- package/src/avif/native/api/decode_image_item.d.ts +18 -0
- package/src/avif/native/api/decode_image_item.d.ts.map +1 -0
- package/src/avif/native/api/decode_image_item.js +234 -0
- package/src/avif/native/api/encode_avif.d.ts +73 -0
- package/src/avif/native/api/encode_avif.d.ts.map +1 -0
- package/src/avif/native/api/encode_avif.js +488 -0
- package/src/avif/native/api/sampler2d_to_avif.d.ts +22 -0
- package/src/avif/native/api/sampler2d_to_avif.d.ts.map +1 -0
- package/src/avif/native/api/sampler2d_to_avif.js +108 -0
- package/src/avif/native/av1/decode/Av1FrameContext.d.ts +286 -0
- package/src/avif/native/av1/decode/Av1FrameContext.d.ts.map +1 -0
- package/src/avif/native/av1/decode/Av1FrameContext.js +622 -0
- package/src/avif/native/av1/decode/block_decoded.d.ts +27 -0
- package/src/avif/native/av1/decode/block_decoded.d.ts.map +1 -0
- package/src/avif/native/av1/decode/block_decoded.js +59 -0
- package/src/avif/native/av1/decode/decode_av1_still.d.ts +22 -0
- package/src/avif/native/av1/decode/decode_av1_still.d.ts.map +1 -0
- package/src/avif/native/av1/decode/decode_av1_still.js +309 -0
- package/src/avif/native/av1/decode/decode_coefficients.d.ts +20 -0
- package/src/avif/native/av1/decode/decode_coefficients.d.ts.map +1 -0
- package/src/avif/native/av1/decode/decode_coefficients.js +259 -0
- package/src/avif/native/av1/decode/decode_palette.d.ts +28 -0
- package/src/avif/native/av1/decode/decode_palette.d.ts.map +1 -0
- package/src/avif/native/av1/decode/decode_palette.js +442 -0
- package/src/avif/native/av1/decode/decode_tile.d.ts +14 -0
- package/src/avif/native/av1/decode/decode_tile.d.ts.map +1 -0
- package/src/avif/native/av1/decode/decode_tile.js +1213 -0
- package/src/avif/native/av1/decode/get_scan.d.ts +16 -0
- package/src/avif/native/av1/decode/get_scan.d.ts.map +1 -0
- package/src/avif/native/av1/decode/get_scan.js +138 -0
- package/src/avif/native/av1/decode/read_lr.d.ts +35 -0
- package/src/avif/native/av1/decode/read_lr.d.ts.map +1 -0
- package/src/avif/native/av1/decode/read_lr.js +277 -0
- package/src/avif/native/av1/decode/read_transform_type.d.ts +19 -0
- package/src/avif/native/av1/decode/read_transform_type.d.ts.map +1 -0
- package/src/avif/native/av1/decode/read_transform_type.js +92 -0
- package/src/avif/native/av1/decode/reconstruct.d.ts +36 -0
- package/src/avif/native/av1/decode/reconstruct.d.ts.map +1 -0
- package/src/avif/native/av1/decode/reconstruct.js +241 -0
- package/src/avif/native/av1/decode/transform_type.d.ts +49 -0
- package/src/avif/native/av1/decode/transform_type.d.ts.map +1 -0
- package/src/avif/native/av1/decode/transform_type.js +137 -0
- package/src/avif/native/av1/encode/Av1BlockSnapshot.d.ts +81 -0
- package/src/avif/native/av1/encode/Av1BlockSnapshot.d.ts.map +1 -0
- package/src/avif/native/av1/encode/Av1BlockSnapshot.js +299 -0
- package/src/avif/native/av1/encode/Av1EncodeContext.d.ts +384 -0
- package/src/avif/native/av1/encode/Av1EncodeContext.d.ts.map +1 -0
- package/src/avif/native/av1/encode/Av1EncodeContext.js +590 -0
- package/src/avif/native/av1/encode/build_headers.d.ts +50 -0
- package/src/avif/native/av1/encode/build_headers.d.ts.map +1 -0
- package/src/avif/native/av1/encode/build_headers.js +359 -0
- package/src/avif/native/av1/encode/choose_chroma_alpha.d.ts +26 -0
- package/src/avif/native/av1/encode/choose_chroma_alpha.d.ts.map +1 -0
- package/src/avif/native/av1/encode/choose_chroma_alpha.js +90 -0
- package/src/avif/native/av1/encode/choose_filters.d.ts +21 -0
- package/src/avif/native/av1/encode/choose_filters.d.ts.map +1 -0
- package/src/avif/native/av1/encode/choose_filters.js +318 -0
- package/src/avif/native/av1/encode/choose_restoration.d.ts +35 -0
- package/src/avif/native/av1/encode/choose_restoration.d.ts.map +1 -0
- package/src/avif/native/av1/encode/choose_restoration.js +616 -0
- package/src/avif/native/av1/encode/decode_effort.d.ts +57 -0
- package/src/avif/native/av1/encode/decode_effort.d.ts.map +1 -0
- package/src/avif/native/av1/encode/decode_effort.js +59 -0
- package/src/avif/native/av1/encode/encode_av1_still.d.ts +17 -0
- package/src/avif/native/av1/encode/encode_av1_still.d.ts.map +1 -0
- package/src/avif/native/av1/encode/encode_av1_still.js +194 -0
- package/src/avif/native/av1/encode/encode_effort.d.ts +56 -0
- package/src/avif/native/av1/encode/encode_effort.d.ts.map +1 -0
- package/src/avif/native/av1/encode/encode_effort.js +97 -0
- package/src/avif/native/av1/encode/encode_tile.d.ts +26 -0
- package/src/avif/native/av1/encode/encode_tile.d.ts.map +1 -0
- package/src/avif/native/av1/encode/encode_tile.js +2331 -0
- package/src/avif/native/av1/encode/quantise.d.ts +51 -0
- package/src/avif/native/av1/encode/quantise.d.ts.map +1 -0
- package/src/avif/native/av1/encode/quantise.js +212 -0
- package/src/avif/native/av1/encode/write_coefficients.d.ts +42 -0
- package/src/avif/native/av1/encode/write_coefficients.d.ts.map +1 -0
- package/src/avif/native/av1/encode/write_coefficients.js +367 -0
- package/src/avif/native/av1/encode/write_lr.d.ts +33 -0
- package/src/avif/native/av1/encode/write_lr.d.ts.map +1 -0
- package/src/avif/native/av1/encode/write_lr.js +174 -0
- package/src/avif/native/av1/encode/write_transform_type.d.ts +21 -0
- package/src/avif/native/av1/encode/write_transform_type.d.ts.map +1 -0
- package/src/avif/native/av1/encode/write_transform_type.js +70 -0
- package/src/avif/native/av1/entropy/CdfContext.d.ts +167 -0
- package/src/avif/native/av1/entropy/CdfContext.d.ts.map +1 -0
- package/src/avif/native/av1/entropy/CdfContext.js +433 -0
- package/src/avif/native/av1/entropy/SymbolReader.d.ts +103 -0
- package/src/avif/native/av1/entropy/SymbolReader.d.ts.map +1 -0
- package/src/avif/native/av1/entropy/SymbolReader.js +317 -0
- package/src/avif/native/av1/entropy/SymbolWriter.d.ts +107 -0
- package/src/avif/native/av1/entropy/SymbolWriter.d.ts.map +1 -0
- package/src/avif/native/av1/entropy/SymbolWriter.js +352 -0
- package/src/avif/native/av1/entropy/coefficient_context.d.ts +71 -0
- package/src/avif/native/av1/entropy/coefficient_context.d.ts.map +1 -0
- package/src/avif/native/av1/entropy/coefficient_context.js +325 -0
- package/src/avif/native/av1/entropy/partition_cdf.d.ts +54 -0
- package/src/avif/native/av1/entropy/partition_cdf.d.ts.map +1 -0
- package/src/avif/native/av1/entropy/partition_cdf.js +98 -0
- package/src/avif/native/av1/entropy/symbol_cost.d.ts +37 -0
- package/src/avif/native/av1/entropy/symbol_cost.d.ts.map +1 -0
- package/src/avif/native/av1/entropy/symbol_cost.js +76 -0
- package/src/avif/native/av1/filter/cdef.d.ts +15 -0
- package/src/avif/native/av1/filter/cdef.d.ts.map +1 -0
- package/src/avif/native/av1/filter/cdef.js +313 -0
- package/src/avif/native/av1/filter/loop_filter.d.ts +14 -0
- package/src/avif/native/av1/filter/loop_filter.d.ts.map +1 -0
- package/src/avif/native/av1/filter/loop_filter.js +377 -0
- package/src/avif/native/av1/filter/loop_restoration.d.ts +42 -0
- package/src/avif/native/av1/filter/loop_restoration.d.ts.map +1 -0
- package/src/avif/native/av1/filter/loop_restoration.js +451 -0
- package/src/avif/native/av1/filter/superres.d.ts +18 -0
- package/src/avif/native/av1/filter/superres.d.ts.map +1 -0
- package/src/avif/native/av1/filter/superres.js +86 -0
- package/src/avif/native/av1/grain/FilmGrainState.d.ts +62 -0
- package/src/avif/native/av1/grain/FilmGrainState.d.ts.map +1 -0
- package/src/avif/native/av1/grain/FilmGrainState.js +87 -0
- package/src/avif/native/av1/grain/film_grain.d.ts +16 -0
- package/src/avif/native/av1/grain/film_grain.d.ts.map +1 -0
- package/src/avif/native/av1/grain/film_grain.js +623 -0
- package/src/avif/native/av1/obu/FrameHeader.d.ts +248 -0
- package/src/avif/native/av1/obu/FrameHeader.d.ts.map +1 -0
- package/src/avif/native/av1/obu/FrameHeader.js +381 -0
- package/src/avif/native/av1/obu/ObuHeader.d.ts +53 -0
- package/src/avif/native/av1/obu/ObuHeader.d.ts.map +1 -0
- package/src/avif/native/av1/obu/ObuHeader.js +62 -0
- package/src/avif/native/av1/obu/SequenceHeader.d.ts +157 -0
- package/src/avif/native/av1/obu/SequenceHeader.d.ts.map +1 -0
- package/src/avif/native/av1/obu/SequenceHeader.js +225 -0
- package/src/avif/native/av1/obu/for_each_obu.d.ts +24 -0
- package/src/avif/native/av1/obu/for_each_obu.d.ts.map +1 -0
- package/src/avif/native/av1/obu/for_each_obu.js +50 -0
- package/src/avif/native/av1/obu/parse_frame_header.d.ts +18 -0
- package/src/avif/native/av1/obu/parse_frame_header.d.ts.map +1 -0
- package/src/avif/native/av1/obu/parse_frame_header.js +841 -0
- package/src/avif/native/av1/obu/parse_obu_header.d.ts +23 -0
- package/src/avif/native/av1/obu/parse_obu_header.d.ts.map +1 -0
- package/src/avif/native/av1/obu/parse_obu_header.js +69 -0
- package/src/avif/native/av1/obu/parse_sequence_header.d.ts +17 -0
- package/src/avif/native/av1/obu/parse_sequence_header.d.ts.map +1 -0
- package/src/avif/native/av1/obu/parse_sequence_header.js +263 -0
- package/src/avif/native/av1/obu/write_frame_header.d.ts +16 -0
- package/src/avif/native/av1/obu/write_frame_header.d.ts.map +1 -0
- package/src/avif/native/av1/obu/write_frame_header.js +579 -0
- package/src/avif/native/av1/obu/write_sequence_header.d.ts +17 -0
- package/src/avif/native/av1/obu/write_sequence_header.d.ts.map +1 -0
- package/src/avif/native/av1/obu/write_sequence_header.js +189 -0
- package/src/avif/native/av1/predict/IntraPredictionState.d.ts +49 -0
- package/src/avif/native/av1/predict/IntraPredictionState.d.ts.map +1 -0
- package/src/avif/native/av1/predict/IntraPredictionState.js +58 -0
- package/src/avif/native/av1/predict/intra_filter_type.d.ts +17 -0
- package/src/avif/native/av1/predict/intra_filter_type.d.ts.map +1 -0
- package/src/avif/native/av1/predict/intra_filter_type.js +79 -0
- package/src/avif/native/av1/predict/predict_chroma_from_luma.d.ts +50 -0
- package/src/avif/native/av1/predict/predict_chroma_from_luma.d.ts.map +1 -0
- package/src/avif/native/av1/predict/predict_chroma_from_luma.js +153 -0
- package/src/avif/native/av1/predict/predict_intra.d.ts +42 -0
- package/src/avif/native/av1/predict/predict_intra.d.ts.map +1 -0
- package/src/avif/native/av1/predict/predict_intra.js +803 -0
- package/src/avif/native/av1/predict/predict_palette.d.ts +23 -0
- package/src/avif/native/av1/predict/predict_palette.d.ts.map +1 -0
- package/src/avif/native/av1/predict/predict_palette.js +36 -0
- package/src/avif/native/av1/tables/av1_symbols.d.ts +411 -0
- package/src/avif/native/av1/tables/av1_symbols.d.ts.map +1 -0
- package/src/avif/native/av1/tables/av1_symbols.js +419 -0
- package/src/avif/native/av1/tables/block_tables.d.ts +181 -0
- package/src/avif/native/av1/tables/block_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/block_tables.js +270 -0
- package/src/avif/native/av1/tables/cdf_tables.d.ts +961 -0
- package/src/avif/native/av1/tables/cdf_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/cdf_tables.js +1893 -0
- package/src/avif/native/av1/tables/coefficient_tables.d.ts +41 -0
- package/src/avif/native/av1/tables/coefficient_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/coefficient_tables.js +82 -0
- package/src/avif/native/av1/tables/derived_block_tables.d.ts +15 -0
- package/src/avif/native/av1/tables/derived_block_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/derived_block_tables.js +23 -0
- package/src/avif/native/av1/tables/filter_tables.d.ts +171 -0
- package/src/avif/native/av1/tables/filter_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/filter_tables.js +313 -0
- package/src/avif/native/av1/tables/grain_tables.d.ts +11 -0
- package/src/avif/native/av1/tables/grain_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/grain_tables.js +190 -0
- package/src/avif/native/av1/tables/prediction_tables.d.ts +141 -0
- package/src/avif/native/av1/tables/prediction_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/prediction_tables.js +223 -0
- package/src/avif/native/av1/tables/quantizer_matrix_tables.d.ts +21 -0
- package/src/avif/native/av1/tables/quantizer_matrix_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/quantizer_matrix_tables.js +38 -0
- package/src/avif/native/av1/tables/quantizer_tables.d.ts +21 -0
- package/src/avif/native/av1/tables/quantizer_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/quantizer_tables.js +160 -0
- package/src/avif/native/av1/tables/scan_tables.d.ts +321 -0
- package/src/avif/native/av1/tables/scan_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/scan_tables.js +727 -0
- package/src/avif/native/av1/tables/segmentation_tables.d.ts +31 -0
- package/src/avif/native/av1/tables/segmentation_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/segmentation_tables.js +48 -0
- package/src/avif/native/av1/tables/transform_tables.d.ts +91 -0
- package/src/avif/native/av1/tables/transform_tables.d.ts.map +1 -0
- package/src/avif/native/av1/tables/transform_tables.js +140 -0
- package/src/avif/native/av1/tables/unpack_table.d.ts +33 -0
- package/src/avif/native/av1/tables/unpack_table.d.ts.map +1 -0
- package/src/avif/native/av1/tables/unpack_table.js +73 -0
- package/src/avif/native/av1/transform/forward_transform_2d.d.ts +61 -0
- package/src/avif/native/av1/transform/forward_transform_2d.d.ts.map +1 -0
- package/src/avif/native/av1/transform/forward_transform_2d.js +574 -0
- package/src/avif/native/av1/transform/inverse_transform_1d.d.ts +76 -0
- package/src/avif/native/av1/transform/inverse_transform_1d.d.ts.map +1 -0
- package/src/avif/native/av1/transform/inverse_transform_1d.js +590 -0
- package/src/avif/native/av1/transform/inverse_transform_2d.d.ts +46 -0
- package/src/avif/native/av1/transform/inverse_transform_2d.d.ts.map +1 -0
- package/src/avif/native/av1/transform/inverse_transform_2d.js +180 -0
- package/src/avif/native/av1/util/ceil_log2.d.ts +13 -0
- package/src/avif/native/av1/util/ceil_log2.d.ts.map +1 -0
- package/src/avif/native/av1/util/ceil_log2.js +18 -0
- package/src/avif/native/av1/util/floor_log2.d.ts +20 -0
- package/src/avif/native/av1/util/floor_log2.d.ts.map +1 -0
- package/src/avif/native/av1/util/floor_log2.js +20 -0
- package/src/avif/native/av1/util/round2.d.ts +22 -0
- package/src/avif/native/av1/util/round2.d.ts.map +1 -0
- package/src/avif/native/av1/util/round2.js +27 -0
- package/src/avif/native/av1/util/round2_signed.d.ts +14 -0
- package/src/avif/native/av1/util/round2_signed.d.ts.map +1 -0
- package/src/avif/native/av1/util/round2_signed.js +17 -0
- package/src/avif/native/av1/util/tile_log2.d.ts +16 -0
- package/src/avif/native/av1/util/tile_log2.d.ts.map +1 -0
- package/src/avif/native/av1/util/tile_log2.js +23 -0
- package/src/avif/native/bitstream/BitReader.d.ts +174 -0
- package/src/avif/native/bitstream/BitReader.d.ts.map +1 -0
- package/src/avif/native/bitstream/BitReader.js +391 -0
- package/src/avif/native/bitstream/BitWriter.d.ts +122 -0
- package/src/avif/native/bitstream/BitWriter.d.ts.map +1 -0
- package/src/avif/native/bitstream/BitWriter.js +351 -0
- package/src/avif/native/color/ColourTransform.d.ts +62 -0
- package/src/avif/native/color/ColourTransform.d.ts.map +1 -0
- package/src/avif/native/color/ColourTransform.js +189 -0
- package/src/avif/native/color/YuvImage.d.ts +51 -0
- package/src/avif/native/color/YuvImage.d.ts.map +1 -0
- package/src/avif/native/color/YuvImage.js +66 -0
- package/src/avif/native/color/clamp_sample.d.ts +18 -0
- package/src/avif/native/color/clamp_sample.d.ts.map +1 -0
- package/src/avif/native/color/clamp_sample.js +25 -0
- package/src/avif/native/color/linear_to_transfer.d.ts +24 -0
- package/src/avif/native/color/linear_to_transfer.d.ts.map +1 -0
- package/src/avif/native/color/linear_to_transfer.js +126 -0
- package/src/avif/native/color/primaries.d.ts +20 -0
- package/src/avif/native/color/primaries.d.ts.map +1 -0
- package/src/avif/native/color/primaries.js +94 -0
- package/src/avif/native/color/rgb_row_to_ycbcr.d.ts +20 -0
- package/src/avif/native/color/rgb_row_to_ycbcr.d.ts.map +1 -0
- package/src/avif/native/color/rgb_row_to_ycbcr.js +78 -0
- package/src/avif/native/color/transfer_to_linear.d.ts +49 -0
- package/src/avif/native/color/transfer_to_linear.d.ts.map +1 -0
- package/src/avif/native/color/transfer_to_linear.js +171 -0
- package/src/avif/native/color/upsample_chroma_row.d.ts +35 -0
- package/src/avif/native/color/upsample_chroma_row.d.ts.map +1 -0
- package/src/avif/native/color/upsample_chroma_row.js +89 -0
- package/src/avif/native/color/ycbcr_row_to_rgb.d.ts +23 -0
- package/src/avif/native/color/ycbcr_row_to_rgb.d.ts.map +1 -0
- package/src/avif/native/color/ycbcr_row_to_rgb.js +78 -0
- package/src/avif/native/heif/AvifFile.d.ts +112 -0
- package/src/avif/native/heif/AvifFile.d.ts.map +1 -0
- package/src/avif/native/heif/AvifFile.js +142 -0
- package/src/avif/native/heif/ItemProperty.d.ts +244 -0
- package/src/avif/native/heif/ItemProperty.d.ts.map +1 -0
- package/src/avif/native/heif/ItemProperty.js +321 -0
- package/src/avif/native/heif/find_item_property.d.ts +17 -0
- package/src/avif/native/heif/find_item_property.d.ts.map +1 -0
- package/src/avif/native/heif/find_item_property.js +28 -0
- package/src/avif/native/heif/find_item_references.d.ts +16 -0
- package/src/avif/native/heif/find_item_references.d.ts.map +1 -0
- package/src/avif/native/heif/find_item_references.js +29 -0
- package/src/avif/native/heif/find_items_referencing.d.ts +17 -0
- package/src/avif/native/heif/find_items_referencing.d.ts.map +1 -0
- package/src/avif/native/heif/find_items_referencing.js +36 -0
- package/src/avif/native/heif/parse_avif_file.d.ts +15 -0
- package/src/avif/native/heif/parse_avif_file.d.ts.map +1 -0
- package/src/avif/native/heif/parse_avif_file.js +383 -0
- package/src/avif/native/heif/parse_image_grid.d.ts +18 -0
- package/src/avif/native/heif/parse_image_grid.d.ts.map +1 -0
- package/src/avif/native/heif/parse_image_grid.js +48 -0
- package/src/avif/native/heif/parse_item_property.d.ts +18 -0
- package/src/avif/native/heif/parse_item_property.d.ts.map +1 -0
- package/src/avif/native/heif/parse_item_property.js +243 -0
- package/src/avif/native/heif/read_item_data.d.ts +22 -0
- package/src/avif/native/heif/read_item_data.d.ts.map +1 -0
- package/src/avif/native/heif/read_item_data.js +97 -0
- package/src/avif/native/heif/write_avif_file.d.ts +50 -0
- package/src/avif/native/heif/write_avif_file.d.ts.map +1 -0
- package/src/avif/native/heif/write_avif_file.js +348 -0
- package/src/avif/native/heif/write_item_property.d.ts +16 -0
- package/src/avif/native/heif/write_item_property.d.ts.map +1 -0
- package/src/avif/native/heif/write_item_property.js +182 -0
- package/src/avif/native/index.d.ts +14 -0
- package/src/avif/native/index.d.ts.map +1 -0
- package/src/avif/native/index.js +56 -0
- package/src/avif/native/isobmff/BoxHeader.d.ts +50 -0
- package/src/avif/native/isobmff/BoxHeader.d.ts.map +1 -0
- package/src/avif/native/isobmff/BoxHeader.js +57 -0
- package/src/avif/native/isobmff/BoxWriter.d.ts +118 -0
- package/src/avif/native/isobmff/BoxWriter.d.ts.map +1 -0
- package/src/avif/native/isobmff/BoxWriter.js +264 -0
- package/src/avif/native/isobmff/ByteCursor.d.ts +125 -0
- package/src/avif/native/isobmff/ByteCursor.d.ts.map +1 -0
- package/src/avif/native/isobmff/ByteCursor.js +298 -0
- package/src/avif/native/isobmff/for_each_box.d.ts +23 -0
- package/src/avif/native/isobmff/for_each_box.d.ts.map +1 -0
- package/src/avif/native/isobmff/for_each_box.js +39 -0
- package/src/avif/native/isobmff/read_box_header.d.ts +22 -0
- package/src/avif/native/isobmff/read_box_header.d.ts.map +1 -0
- package/src/avif/native/isobmff/read_box_header.js +59 -0
- package/src/avif/native/isobmff/read_full_box_header.d.ts +26 -0
- package/src/avif/native/isobmff/read_full_box_header.d.ts.map +1 -0
- package/src/avif/native/isobmff/read_full_box_header.js +33 -0
- package/src/avif/threaded_image_encoder.d.ts +7 -2
- package/src/avif/threaded_image_encoder.d.ts.map +1 -1
- package/src/avif/threaded_image_encoder.js +107 -21
- package/src/core/binary/BinaryBuffer.d.ts +40 -0
- package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
- package/src/core/binary/BinaryBuffer.js +100 -0
- package/src/core/binary/hash/crc32.d.ts +16 -0
- package/src/core/binary/hash/crc32.d.ts.map +1 -0
- package/src/core/binary/hash/crc32.js +96 -0
- package/src/core/binary/utf8/utf8_decode.d.ts +21 -0
- package/src/core/binary/utf8/utf8_decode.d.ts.map +1 -0
- package/src/core/binary/utf8/utf8_decode.js +43 -0
- package/src/core/binary/utf8/utf8_encode_into.d.ts +20 -0
- package/src/core/binary/utf8/utf8_encode_into.d.ts.map +1 -0
- package/src/core/binary/utf8/utf8_encode_into.js +48 -0
- package/src/core/binary/utf8/utf8_encoded_length.d.ts +14 -0
- package/src/core/binary/utf8/utf8_encoded_length.d.ts.map +1 -0
- package/src/core/binary/utf8/utf8_encoded_length.js +36 -0
- package/src/core/color/COLOR_FIX_PLAN_2026_08_28.md +417 -0
- package/src/core/color/COLOR_REVIEW_2026_08_28.md +779 -0
- package/src/core/color/Color.d.ts +128 -20
- package/src/core/color/Color.d.ts.map +1 -1
- package/src/core/color/Color.js +959 -866
- package/src/core/color/PQ/PQ_constants.d.ts +10 -0
- package/src/core/color/PQ/PQ_constants.d.ts.map +1 -1
- package/src/core/color/PQ/PQ_constants.js +10 -0
- package/src/core/color/PQ/linear_to_PQ.d.ts.map +1 -1
- package/src/core/color/PQ/linear_to_PQ.js +6 -0
- package/src/core/color/REC709_PRIMARIES.d.ts +50 -0
- package/src/core/color/REC709_PRIMARIES.d.ts.map +1 -0
- package/src/core/color/REC709_PRIMARIES.js +53 -0
- package/src/core/color/construct/color_from_hex.d.ts +21 -0
- package/src/core/color/construct/color_from_hex.d.ts.map +1 -0
- package/src/core/color/construct/color_from_hex.js +33 -0
- package/src/core/color/construct/color_from_temperature.d.ts +22 -0
- package/src/core/color/construct/color_from_temperature.d.ts.map +1 -0
- package/src/core/color/construct/color_from_temperature.js +33 -0
- package/src/core/color/construct/color_from_uint24.d.ts +19 -0
- package/src/core/color/construct/color_from_uint24.d.ts.map +1 -0
- package/src/core/color/construct/color_from_uint24.js +26 -0
- package/src/core/color/construct/color_from_uint32.d.ts +20 -0
- package/src/core/color/construct/color_from_uint32.d.ts.map +1 -0
- package/src/core/color/construct/color_from_uint32.js +39 -0
- package/src/core/color/construct/color_gray.d.ts +17 -0
- package/src/core/color/construct/color_gray.d.ts.map +1 -0
- package/src/core/color/construct/color_gray.js +22 -0
- package/src/core/color/hex/hex2rgb.d.ts +1 -11
- package/src/core/color/hex/hex2rgb.d.ts.map +1 -1
- package/src/core/color/hex/hex2rgb.js +7 -24
- package/src/core/color/hex/hex_to_rgb.d.ts +18 -0
- package/src/core/color/hex/hex_to_rgb.d.ts.map +1 -0
- package/src/core/color/hex/hex_to_rgb.js +62 -0
- package/src/core/color/hex/rgb2hex.d.ts +1 -8
- package/src/core/color/hex/rgb2hex.d.ts.map +1 -1
- package/src/core/color/hex/rgb2hex.js +7 -13
- package/src/core/color/hex/rgb_to_hex.d.ts +9 -0
- package/src/core/color/hex/rgb_to_hex.d.ts.map +1 -0
- package/src/core/color/hex/rgb_to_hex.js +13 -0
- package/src/core/color/hex/rgba_to_hex.d.ts +19 -0
- package/src/core/color/hex/rgba_to_hex.d.ts.map +1 -0
- package/src/core/color/hex/rgba_to_hex.js +23 -0
- package/src/core/color/hsv/hsv2rgb.d.ts +1 -12
- package/src/core/color/hsv/hsv2rgb.d.ts.map +1 -1
- package/src/core/color/hsv/hsv2rgb.js +8 -21
- package/src/core/color/hsv/hsv2rgb_float.d.ts +1 -12
- package/src/core/color/hsv/hsv2rgb_float.d.ts.map +1 -1
- package/src/core/color/hsv/hsv2rgb_float.js +8 -56
- package/src/core/color/hsv/hsv_to_rgb.d.ts +13 -0
- package/src/core/color/hsv/hsv_to_rgb.d.ts.map +1 -0
- package/src/core/color/hsv/hsv_to_rgb.js +39 -0
- package/src/core/color/hsv/hsv_to_rgb_uint8.d.ts +13 -0
- package/src/core/color/hsv/hsv_to_rgb_uint8.d.ts.map +1 -0
- package/src/core/color/hsv/hsv_to_rgb_uint8.js +21 -0
- package/src/core/color/hsv/hue_to_rgb_sextant.d.ts +24 -0
- package/src/core/color/hsv/hue_to_rgb_sextant.d.ts.map +1 -0
- package/src/core/color/hsv/hue_to_rgb_sextant.js +67 -0
- package/src/core/color/hsv/rgb2hsv.d.ts +1 -12
- package/src/core/color/hsv/rgb2hsv.d.ts.map +1 -1
- package/src/core/color/hsv/rgb2hsv.js +7 -42
- package/src/core/color/hsv/rgb_to_hsv.d.ts +13 -0
- package/src/core/color/hsv/rgb_to_hsv.d.ts.map +1 -0
- package/src/core/color/hsv/rgb_to_hsv.js +42 -0
- package/src/core/color/hunt/hpe_to_xyz.d.ts +2 -0
- package/src/core/color/hunt/hpe_to_xyz.d.ts.map +1 -0
- package/src/core/color/hunt/hpe_to_xyz.js +4 -0
- package/src/core/color/hunt/xyz_to_hpe.d.ts +1 -6
- package/src/core/color/hunt/xyz_to_hpe.d.ts.map +1 -1
- package/src/core/color/hunt/xyz_to_hpe.js +12 -15
- package/src/core/color/illuminant/D65_spd_tabulated.d.ts.map +1 -1
- package/src/core/color/illuminant/D65_spd_tabulated.js +107 -104
- package/src/core/color/int2rgb.d.ts +1 -10
- package/src/core/color/int2rgb.d.ts.map +1 -1
- package/src/core/color/int2rgb.js +7 -12
- package/src/core/color/int_to_rgb.d.ts +21 -0
- package/src/core/color/int_to_rgb.d.ts.map +1 -0
- package/src/core/color/int_to_rgb.js +33 -0
- package/src/core/color/kelvin/kelvin_to_rgb.d.ts.map +1 -1
- package/src/core/color/kelvin/kelvin_to_rgb.js +22 -2
- package/src/core/color/kelvin/rgb_to_kelvin.d.ts +22 -2
- package/src/core/color/kelvin/rgb_to_kelvin.d.ts.map +1 -1
- package/src/core/color/kelvin/rgb_to_kelvin.js +22 -2
- package/src/core/color/lab/lab_to_xyz.d.ts +20 -0
- package/src/core/color/lab/lab_to_xyz.d.ts.map +1 -0
- package/src/core/color/lab/lab_to_xyz.js +69 -0
- package/src/core/color/oklab/gamut_clip_adaptive_l0.d.ts +34 -0
- package/src/core/color/oklab/gamut_clip_adaptive_l0.d.ts.map +1 -0
- package/src/core/color/oklab/gamut_clip_adaptive_l0.js +46 -0
- package/src/core/color/oklab/gamut_clip_at_l0.d.ts +33 -0
- package/src/core/color/oklab/gamut_clip_at_l0.d.ts.map +1 -0
- package/src/core/color/oklab/gamut_clip_at_l0.js +84 -0
- package/src/core/color/oklab/gamut_clip_preserve_chroma.d.ts +24 -0
- package/src/core/color/oklab/gamut_clip_preserve_chroma.d.ts.map +1 -0
- package/src/core/color/oklab/gamut_clip_preserve_chroma.js +37 -0
- package/src/core/color/oklab/gamut_clip_project_to_lcusp.d.ts +23 -0
- package/src/core/color/oklab/gamut_clip_project_to_lcusp.d.ts.map +1 -0
- package/src/core/color/oklab/gamut_clip_project_to_lcusp.js +46 -0
- package/src/core/color/oklab/linear_srgb_to_okhsl.d.ts +19 -0
- package/src/core/color/oklab/linear_srgb_to_okhsl.d.ts.map +1 -0
- package/src/core/color/oklab/linear_srgb_to_okhsl.js +93 -0
- package/src/core/color/oklab/linear_srgb_to_okhsv.d.ts +12 -4
- package/src/core/color/oklab/linear_srgb_to_okhsv.d.ts.map +1 -1
- package/src/core/color/oklab/linear_srgb_to_okhsv.js +108 -84
- package/src/core/color/oklab/linear_srgb_to_oklab.d.ts +13 -4
- package/src/core/color/oklab/linear_srgb_to_oklab.d.ts.map +1 -1
- package/src/core/color/oklab/linear_srgb_to_oklab.js +28 -19
- package/src/core/color/oklab/okhsl_chroma_bounds.d.ts +51 -0
- package/src/core/color/oklab/okhsl_chroma_bounds.d.ts.map +1 -0
- package/src/core/color/oklab/okhsl_chroma_bounds.js +128 -0
- package/src/core/color/oklab/okhsl_to_linear_srgb.d.ts +19 -0
- package/src/core/color/oklab/okhsl_to_linear_srgb.d.ts.map +1 -0
- package/src/core/color/oklab/okhsl_to_linear_srgb.js +85 -0
- package/src/core/color/oklab/oklab_to_oklch.d.ts +24 -0
- package/src/core/color/oklab/oklab_to_oklch.d.ts.map +1 -0
- package/src/core/color/oklab/oklab_to_oklch.js +35 -0
- package/src/core/color/oklab/oklch_to_oklab.d.ts +18 -0
- package/src/core/color/oklab/oklch_to_oklab.d.ts.map +1 -0
- package/src/core/color/oklab/oklch_to_oklab.js +26 -0
- package/src/core/color/oklab/toe.d.ts +12 -0
- package/src/core/color/oklab/toe.d.ts.map +1 -1
- package/src/core/color/oklab/toe.js +34 -22
- package/src/core/color/operations/color_add.d.ts +21 -0
- package/src/core/color/operations/color_add.d.ts.map +1 -0
- package/src/core/color/operations/color_add.js +23 -0
- package/src/core/color/operations/color_add_scaled.d.ts +20 -0
- package/src/core/color/operations/color_add_scaled.d.ts.map +1 -0
- package/src/core/color/operations/color_add_scaled.js +30 -0
- package/src/core/color/operations/color_clamp.d.ts +22 -0
- package/src/core/color/operations/color_clamp.d.ts.map +1 -0
- package/src/core/color/operations/color_clamp.js +25 -0
- package/src/core/color/operations/color_darken.d.ts +2 -1
- package/src/core/color/operations/color_darken.d.ts.map +1 -1
- package/src/core/color/operations/color_darken.js +25 -24
- package/src/core/color/operations/color_desaturate.d.ts +2 -1
- package/src/core/color/operations/color_desaturate.d.ts.map +1 -1
- package/src/core/color/operations/color_desaturate.js +25 -24
- package/src/core/color/operations/color_get_hsl.d.ts +18 -0
- package/src/core/color/operations/color_get_hsl.d.ts.map +1 -0
- package/src/core/color/operations/color_get_hsl.js +60 -0
- package/src/core/color/operations/color_is_in_gamut.d.ts +20 -0
- package/src/core/color/operations/color_is_in_gamut.d.ts.map +1 -0
- package/src/core/color/operations/color_is_in_gamut.js +30 -0
- package/src/core/color/operations/color_lerp.d.ts +12 -3
- package/src/core/color/operations/color_lerp.d.ts.map +1 -1
- package/src/core/color/operations/color_lerp.js +55 -46
- package/src/core/color/operations/color_lighten.d.ts +9 -5
- package/src/core/color/operations/color_lighten.d.ts.map +1 -1
- package/src/core/color/operations/color_lighten.js +29 -24
- package/src/core/color/operations/color_mix_okhsv_channel.d.ts +24 -0
- package/src/core/color/operations/color_mix_okhsv_channel.d.ts.map +1 -0
- package/src/core/color/operations/color_mix_okhsv_channel.js +48 -0
- package/src/core/color/operations/color_multiply_rgb.d.ts +21 -0
- package/src/core/color/operations/color_multiply_rgb.d.ts.map +1 -0
- package/src/core/color/operations/color_multiply_rgb.js +26 -0
- package/src/core/color/operations/color_saturate.d.ts +9 -5
- package/src/core/color/operations/color_saturate.d.ts.map +1 -1
- package/src/core/color/operations/color_saturate.js +29 -24
- package/src/core/color/operations/color_scale_okhsv_channel.d.ts +1 -1
- package/src/core/color/operations/color_scale_okhsv_channel.d.ts.map +1 -1
- package/src/core/color/operations/color_scale_okhsv_channel.js +58 -45
- package/src/core/color/operations/color_srgb_apply.d.ts +28 -0
- package/src/core/color/operations/color_srgb_apply.d.ts.map +1 -0
- package/src/core/color/operations/color_srgb_apply.js +40 -0
- package/src/core/color/operations/color_sub.d.ts +16 -0
- package/src/core/color/operations/color_sub.d.ts.map +1 -0
- package/src/core/color/operations/color_sub.js +18 -0
- package/src/core/color/parse_color.d.ts +10 -1
- package/src/core/color/parse_color.d.ts.map +1 -1
- package/src/core/color/parse_color.js +100 -84
- package/src/core/color/parse_color_normalized.d.ts +24 -0
- package/src/core/color/parse_color_normalized.d.ts.map +1 -0
- package/src/core/color/parse_color_normalized.js +38 -0
- package/src/core/color/rgb2uint24.d.ts +1 -8
- package/src/core/color/rgb2uint24.d.ts.map +1 -1
- package/src/core/color/rgb2uint24.js +7 -20
- package/src/core/color/rgb2uint32.d.ts +1 -9
- package/src/core/color/rgb2uint32.d.ts.map +1 -1
- package/src/core/color/rgb2uint32.js +7 -18
- package/src/core/color/rgb_to_luminance.d.ts +14 -5
- package/src/core/color/rgb_to_luminance.d.ts.map +1 -1
- package/src/core/color/rgb_to_luminance.js +24 -13
- package/src/core/color/rgb_to_uint24.d.ts +9 -0
- package/src/core/color/rgb_to_uint24.d.ts.map +1 -0
- package/src/core/color/rgb_to_uint24.js +20 -0
- package/src/core/color/rgb_to_uint32.d.ts +10 -0
- package/src/core/color/rgb_to_uint32.d.ts.map +1 -0
- package/src/core/color/rgb_to_uint32.js +21 -0
- package/src/core/color/sRGB/linear_to_sRGB.d.ts +7 -0
- package/src/core/color/sRGB/linear_to_sRGB.d.ts.map +1 -1
- package/src/core/color/sRGB/linear_to_sRGB.js +39 -32
- package/src/core/color/xyz/hpe_to_xyz.d.ts +24 -0
- package/src/core/color/xyz/hpe_to_xyz.d.ts.map +1 -0
- package/src/core/color/xyz/hpe_to_xyz.js +39 -0
- package/src/core/color/xyz/rgb_to_xyz.d.ts +17 -2
- package/src/core/color/xyz/rgb_to_xyz.d.ts.map +1 -1
- package/src/core/color/xyz/rgb_to_xyz.js +44 -18
- package/src/core/color/xyz/xyz_cmf_tabulated.d.ts.map +1 -1
- package/src/core/color/xyz/xyz_cmf_tabulated.js +217 -211
- package/src/core/color/xyz/xyz_cmf_wyman.d.ts +4 -0
- package/src/core/color/xyz/xyz_cmf_wyman.d.ts.map +1 -1
- package/src/core/color/xyz/xyz_cmf_wyman.js +74 -65
- package/src/core/color/xyz/xyz_to_hpe.d.ts +18 -0
- package/src/core/color/xyz/xyz_to_hpe.d.ts.map +1 -0
- package/src/core/color/xyz/xyz_to_hpe.js +33 -0
- package/src/core/color/xyz/xyz_to_rgb.d.ts +17 -2
- package/src/core/color/xyz/xyz_to_rgb.d.ts.map +1 -1
- package/src/core/color/xyz/xyz_to_rgb.js +39 -16
- package/src/core/color/ycxcz/xyz_to_ycxcz.d.ts +7 -2
- package/src/core/color/ycxcz/xyz_to_ycxcz.d.ts.map +1 -1
- package/src/core/color/ycxcz/xyz_to_ycxcz.js +33 -28
- package/src/core/events/signal/Signal.js +820 -820
- package/src/core/math/clamp_index.d.ts +18 -0
- package/src/core/math/clamp_index.d.ts.map +1 -0
- package/src/core/math/clamp_index.js +23 -0
- package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +803 -0
- package/src/engine/asset/loaders/image/jpeg/JpegFrame.d.ts +5 -3
- package/src/engine/asset/loaders/image/jpeg/JpegFrame.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/jpeg/JpegFrame.js +5 -3
- package/src/engine/asset/loaders/image/jpeg/JpegFrameComponent.d.ts +2 -1
- package/src/engine/asset/loaders/image/jpeg/JpegFrameComponent.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/jpeg/JpegFrameComponent.js +2 -1
- package/src/engine/asset/loaders/image/jpeg/JpegImage.d.ts +17 -0
- package/src/engine/asset/loaders/image/jpeg/JpegImage.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/jpeg/JpegImage.js +197 -157
- package/src/engine/asset/loaders/image/jpeg/idct8x8_float.d.ts +0 -1
- package/src/engine/asset/loaders/image/jpeg/idct8x8_float.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/jpeg/idct8x8_float.js +0 -1
- package/src/engine/asset/loaders/image/jpeg/jpeg_component_row.d.ts +28 -0
- package/src/engine/asset/loaders/image/jpeg/jpeg_component_row.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/jpeg/jpeg_component_row.js +31 -0
- package/src/engine/asset/loaders/image/png/PNG.d.ts +3 -18
- package/src/engine/asset/loaders/image/png/PNG.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/png/PNG.js +3 -139
- package/src/engine/asset/loaders/image/png/PNGReader.d.ts +16 -16
- package/src/engine/asset/loaders/image/png/PNGReader.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/png/PNGReader.js +143 -160
- package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_iTXt.js +3 -3
- package/src/engine/asset/loaders/image/png/chunk/png_chunk_decode_zTXt.js +3 -3
- package/src/engine/asset/loaders/image/png/crc32.d.ts +1 -15
- package/src/engine/asset/loaders/image/png/crc32.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/png/crc32.js +5 -89
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.d.ts +12 -10
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +20 -45
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.d.ts +2 -1
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +2 -0
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.d.ts +2 -1
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.d.ts.map +1 -1
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +2 -0
- package/src/engine/asset/loaders/image/png/filter/png_unfilter.d.ts +22 -0
- package/src/engine/asset/loaders/image/png/filter/png_unfilter.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/filter/png_unfilter.js +68 -0
- package/src/engine/asset/loaders/image/png/png_inflate.d.ts +20 -0
- package/src/engine/asset/loaders/image/png/png_inflate.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/png_inflate.js +85 -0
- package/src/engine/asset/loaders/image/png/png_unpack_samples.d.ts +22 -0
- package/src/engine/asset/loaders/image/png/png_unpack_samples.d.ts.map +1 -0
- package/src/engine/asset/loaders/image/png/png_unpack_samples.js +38 -0
- package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
- package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +135 -132
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +16 -3
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1157 -1149
- package/src/engine/graphics/render/frame_graph/FrameGraph.js +900 -900
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +72 -81
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +86 -77
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +451 -0
- package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +625 -0
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/scene/serialization/decode_image_source_hdr.d.ts +14 -3
- package/src/shade/renderer/scene/serialization/decode_image_source_hdr.d.ts.map +1 -1
- package/src/shade/renderer/scene/serialization/decode_image_source_hdr.js +50 -51
- package/src/shade/renderer/scene/serialization/deserialize_scene.d.ts.map +1 -1
- package/src/shade/renderer/scene/serialization/deserialize_scene.js +531 -521
- package/src/shade/renderer/scene/serialization/write_image_source.d.ts.map +1 -1
- package/src/shade/renderer/scene/serialization/write_image_source.js +6 -4
- package/src/shade/renderer/shader/chunk/color/chunk_rgb_to_luminance.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/color/chunk_rgb_to_luminance.js +34 -32
- package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_eotf_st2084.d.ts +31 -0
- package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_eotf_st2084.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_eotf_st2084.js +62 -31
- package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_inverse_eotf_st2084.d.ts +10 -0
- package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_inverse_eotf_st2084.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_inverse_eotf_st2084.js +32 -22
- package/src/shade/renderer/shader/chunk/color/sRGB/chunk_sRGBTransferOETF.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/color/sRGB/chunk_sRGBTransferOETF.js +22 -21
- package/src/shade/renderer/shader/chunk/color/tonemap/gt7/chunk_tonemap_gt7.d.ts +6 -0
- package/src/shade/renderer/shader/chunk/color/tonemap/gt7/chunk_tonemap_gt7.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/color/tonemap/gt7/chunk_tonemap_gt7.js +110 -104
- package/src/shade/renderer/shader/chunk/color/xyz/chunk_rgb_to_xyz.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/color/xyz/chunk_rgb_to_xyz.js +23 -21
- package/src/view/elements/ColorPickerView.js +296 -296
- package/src/avif/codec/dec/Readme.md +0 -14
- package/src/avif/codec/dec/avif_dec.cpp +0 -234
- package/src/avif/codec/dec/avif_dec.d.ts +0 -3
- package/src/avif/codec/dec/avif_dec.d.ts.map +0 -1
- package/src/avif/codec/dec/avif_dec.js +0 -16
- package/src/avif/codec/dec/avif_dec.wasm +0 -0
- package/src/avif/codec/enc/README.md +0 -37
- package/src/avif/codec/enc/avif_enc.cpp +0 -216
- package/src/avif/codec/enc/avif_enc.d.ts +0 -3
- package/src/avif/codec/enc/avif_enc.d.ts.map +0 -1
- package/src/avif/codec/enc/avif_enc.js +0 -6144
- package/src/avif/codec/enc/avif_enc.wasm +0 -0
- package/src/avif/codec/enc/avif_enc_mt.d.ts +0 -3
- package/src/avif/codec/enc/avif_enc_mt.d.ts.map +0 -1
- package/src/avif/codec/enc/avif_enc_mt.js +0 -6811
- package/src/avif/codec/enc/avif_enc_mt.wasm +0 -0
- package/src/avif/codec/enc/avif_enc_mt.worker.d.mts +0 -2
- package/src/avif/codec/enc/avif_enc_mt.worker.d.mts.map +0 -1
- package/src/avif/codec/enc/avif_enc_mt.worker.d.ts +0 -11
- package/src/avif/codec/enc/avif_enc_mt.worker.d.ts.map +0 -1
- package/src/avif/codec/enc/avif_enc_mt.worker.js +0 -171
- package/src/avif/codec/enc/avif_enc_mt.worker.mjs +0 -1
- package/src/avif/decode.d.ts +0 -3
- package/src/avif/decode.d.ts.map +0 -1
- package/src/avif/decode.js +0 -42
- package/src/avif/encode.d.ts +0 -3
- package/src/avif/encode.d.ts.map +0 -1
- package/src/avif/encode.js +0 -75
- package/src/avif/meta.d.ts +0 -24
- package/src/avif/meta.d.ts.map +0 -1
- package/src/avif/meta.js +0 -23
- package/src/avif/tsconfig.tsbuildinfo +0 -1
- package/src/avif/utils.d.ts +0 -17
- package/src/avif/utils.d.ts.map +0 -1
- package/src/avif/utils.js +0 -31
- package/src/engine/asset/loaders/image/jpeg/idct8x8_fixed.d.ts +0 -23
- package/src/engine/asset/loaders/image/jpeg/idct8x8_fixed.d.ts.map +0 -1
- package/src/engine/asset/loaders/image/jpeg/idct8x8_fixed.js +0 -214
- package/src/engine/asset/loaders/image/png/inflate.d.ts +0 -7
- package/src/engine/asset/loaders/image/png/inflate.d.ts.map +0 -1
- package/src/engine/asset/loaders/image/png/inflate.js +0 -20
|
@@ -1,901 +1,901 @@
|
|
|
1
|
-
import { assert } from "../../../../core/assert.js";
|
|
2
|
-
import { float_to_uint8 } from "../../../../core/binary/float_to_uint8.js";
|
|
3
|
-
import LineBuilder from "../../../../core/codegen/LineBuilder.js";
|
|
4
|
-
import {
|
|
5
|
-
import { okhsv_to_linear_srgb } from "../../../../core/color/oklab/okhsv_to_linear_srgb.js";
|
|
6
|
-
import { Signal } from "../../../../core/events/signal/Signal.js";
|
|
7
|
-
import { noop } from "../../../../core/function/noop.js";
|
|
8
|
-
import { graphviz_escape_string } from "../../../../core/graph/format/graphviz/graphviz_escape_string.js";
|
|
9
|
-
import { lerp } from "../../../../core/math/lerp.js";
|
|
10
|
-
import { computeStringHash } from "../../../../core/primitives/strings/computeStringHash.js";
|
|
11
|
-
import { FrameGraphScope } from "./FrameGraphScope.js";
|
|
12
|
-
import { FrameGraphState, frame_graph_state_name } from "./FrameGraphState.js";
|
|
13
|
-
import { FramePassBuilder } from "./FramePassBuilder.js";
|
|
14
|
-
import { FramePassNode } from "./FramePassNode.js";
|
|
15
|
-
import { FramePassResources } from "./FramePassResources.js";
|
|
16
|
-
import { ResourceEntry } from "./ResourceEntry.js";
|
|
17
|
-
import { ResourceNode } from "./ResourceNode.js";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* A high-level rendering architecture system that manages render passes and their resource dependencies.
|
|
21
|
-
* This class schedules frame execution by building a directed graph of resources (textures, buffers) and the passes that read or write to them.
|
|
22
|
-
*
|
|
23
|
-
* By deferring execution until after the graph is compiled, it automatically handles complex tasks such as:
|
|
24
|
-
* - Culling unreferenced resources and passes (dead code elimination for rendering).
|
|
25
|
-
* - Automatic resource lifetime management (allocating transient resources just-in-time and destroying them immediately after their last use).
|
|
26
|
-
* - Visualizing the rendering pipeline via GraphViz DOT exports.
|
|
27
|
-
*
|
|
28
|
-
* The graph is **one-shot**: it is recorded into, then compiled, then executed, and it never goes
|
|
29
|
-
* back. {@link compile} derives reference counts and resource lifetimes from what was recorded, so
|
|
30
|
-
* recording into a compiled graph corrupts them, and compiling twice accumulates resource reference
|
|
31
|
-
* counts instead of resetting them, which disables culling entirely. Build a new graph per frame.
|
|
32
|
-
*
|
|
33
|
-
* @see FrameGraphState
|
|
34
|
-
*
|
|
35
|
-
* @source Based on the Frostbite's GDC paper "FrameGraph: Extensible Rendering Architecture in Frostbite" by Yuriy O'Donnell
|
|
36
|
-
* @example
|
|
37
|
-
* const graph = new FrameGraph("My Graph");
|
|
38
|
-
*
|
|
39
|
-
* const pass_data = {};
|
|
40
|
-
* const pass = graph.add("GBuffer Pass", pass_data, (data, resources, context) => {
|
|
41
|
-
*
|
|
42
|
-
* context.beginRenderPass({
|
|
43
|
-
* depth_attachment: resources.get(data.depth),
|
|
44
|
-
* color_attachments:[
|
|
45
|
-
* resources.get(data.normal),
|
|
46
|
-
* resources.get(data.albedo),
|
|
47
|
-
* ]
|
|
48
|
-
* });
|
|
49
|
-
*
|
|
50
|
-
* for (const renderable in renderables){
|
|
51
|
-
* drawMesh(context, renderable.mesh, renderable.material);
|
|
52
|
-
* }
|
|
53
|
-
*
|
|
54
|
-
* context.endRenderPass();
|
|
55
|
-
*
|
|
56
|
-
* });
|
|
57
|
-
*
|
|
58
|
-
* pass_data.depth = pass.create("depth", {...});
|
|
59
|
-
* pass_data.albedo = pass.create("albedo", {...});
|
|
60
|
-
* pass_data.normal = pass.create("normal", {...});
|
|
61
|
-
*
|
|
62
|
-
* graph.compile();
|
|
63
|
-
* graph.execute(context);
|
|
64
|
-
*
|
|
65
|
-
*/
|
|
66
|
-
export class FrameGraph {
|
|
67
|
-
/**
|
|
68
|
-
* Human-readable name, used for debugging and UI primarily.
|
|
69
|
-
* There is no uniqueness guarantee
|
|
70
|
-
* @type {string}
|
|
71
|
-
*/
|
|
72
|
-
name = "";
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
*
|
|
76
|
-
* @type {Signal<IFrameGraphContext,this>}
|
|
77
|
-
*/
|
|
78
|
-
onExecuted = new Signal();
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
*
|
|
82
|
-
* @type {FramePassNode[]}
|
|
83
|
-
* @private
|
|
84
|
-
*/
|
|
85
|
-
__pass_nodes = [];
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
*
|
|
89
|
-
* @type {ResourceNode[]}
|
|
90
|
-
* @private
|
|
91
|
-
*/
|
|
92
|
-
__resource_nodes = [];
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
*
|
|
96
|
-
* @type {ResourceEntry[]}
|
|
97
|
-
* @private
|
|
98
|
-
*/
|
|
99
|
-
__resource_registry = [];
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Position in the one-shot lifecycle.
|
|
103
|
-
* @type {FrameGraphState}
|
|
104
|
-
*/
|
|
105
|
-
#state = FrameGraphState.Recording;
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Innermost open recording scope, or null. The enclosing scopes are its
|
|
109
|
-
* {@link FrameGraphScope.parent} chain, so this one field is the whole stack.
|
|
110
|
-
* @type {FrameGraphScope|null}
|
|
111
|
-
*/
|
|
112
|
-
#scope = null;
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Scopes opened so far, which is what gives each one an ID unique within this graph.
|
|
116
|
-
* @type {number}
|
|
117
|
-
*/
|
|
118
|
-
#scope_count = 0;
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
*
|
|
122
|
-
* @param {string} name
|
|
123
|
-
*/
|
|
124
|
-
constructor(name = "") {
|
|
125
|
-
assert.isString(name, 'name');
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.name = name;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Position in the one-shot lifecycle. Recording is only legal in
|
|
133
|
-
* {@link FrameGraphState.Recording}, and {@link execute} is only legal in
|
|
134
|
-
* {@link FrameGraphState.Compiled}.
|
|
135
|
-
* @returns {FrameGraphState}
|
|
136
|
-
*/
|
|
137
|
-
get state() {
|
|
138
|
-
return this.#state;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Innermost open recording scope, or null when recording outside of one.
|
|
143
|
-
* @returns {FrameGraphScope|null}
|
|
144
|
-
*/
|
|
145
|
-
get scope() {
|
|
146
|
-
return this.#scope;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Open a recording scope. Every pass and resource node created until the matching
|
|
151
|
-
* {@link pop_scope} is stamped with it, nested scopes included.
|
|
152
|
-
*
|
|
153
|
-
* This is how the graph attributes work to whoever recorded it. Node names come from the
|
|
154
|
-
* helpers that happened to build them and say nothing about the caller, so without a scope a
|
|
155
|
-
* DOT export cannot group a frame by the thing each group belongs to, and a diagnostic cannot
|
|
156
|
-
* ask whether everything one caller recorded was culled.
|
|
157
|
-
*
|
|
158
|
-
* @param {string} name
|
|
159
|
-
* @returns {FrameGraphScope} the scope opened, for later queries against node stamps
|
|
160
|
-
*/
|
|
161
|
-
push_scope(name) {
|
|
162
|
-
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': scopes can only be opened while recording, graph is ${frame_graph_state_name(this.#state)}`);
|
|
163
|
-
assert.isString(name, 'name');
|
|
164
|
-
|
|
165
|
-
const scope = new FrameGraphScope();
|
|
166
|
-
|
|
167
|
-
scope.name = name;
|
|
168
|
-
scope.id = this.#scope_count++;
|
|
169
|
-
scope.parent = this.#scope;
|
|
170
|
-
|
|
171
|
-
this.#scope = scope;
|
|
172
|
-
|
|
173
|
-
return scope;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Close the innermost open recording scope, returning to the one that encloses it.
|
|
178
|
-
*/
|
|
179
|
-
pop_scope() {
|
|
180
|
-
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': scopes can only be closed while recording, graph is ${frame_graph_state_name(this.#state)}`);
|
|
181
|
-
assert.notNull(this.#scope, `FrameGraph '${this.name}': pop_scope() without a scope open`);
|
|
182
|
-
|
|
183
|
-
this.#scope = this.#scope.parent;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
*
|
|
188
|
-
* @param {number} id Resource Node ID
|
|
189
|
-
* @returns {ResourceEntry}
|
|
190
|
-
*/
|
|
191
|
-
getResourceEntry(id) {
|
|
192
|
-
const node = this.getResourceNode(id);
|
|
193
|
-
|
|
194
|
-
assert.defined(node, 'node');
|
|
195
|
-
|
|
196
|
-
const registry = this.__resource_registry;
|
|
197
|
-
|
|
198
|
-
const entry = registry[node.resource_id];
|
|
199
|
-
|
|
200
|
-
assert.defined(entry, 'entry');
|
|
201
|
-
|
|
202
|
-
return entry;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
*
|
|
207
|
-
* @param {number} id
|
|
208
|
-
* @returns {ResourceNode}
|
|
209
|
-
*/
|
|
210
|
-
getResourceNode(id) {
|
|
211
|
-
assert.isNonNegativeInteger(id, 'id');
|
|
212
|
-
|
|
213
|
-
const resource = this.__resource_nodes[id];
|
|
214
|
-
|
|
215
|
-
if (resource === undefined) {
|
|
216
|
-
throw new Error(`Resource Node ${id} not found`);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
assert.equal(resource.id, id, 'resource.id does not match');
|
|
220
|
-
|
|
221
|
-
return resource;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* @template T
|
|
226
|
-
* @param {number} id resource ID
|
|
227
|
-
* @returns {T}
|
|
228
|
-
*/
|
|
229
|
-
getDescriptor(id) {
|
|
230
|
-
return this.getResourceEntry(id).resource_descriptor;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* @template T
|
|
235
|
-
* @param {string} name
|
|
236
|
-
* @param {T} descriptor
|
|
237
|
-
* @returns {number}
|
|
238
|
-
*/
|
|
239
|
-
create_resource(name, descriptor) {
|
|
240
|
-
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': resources can only be created while recording, graph is ${frame_graph_state_name(this.#state)}`);
|
|
241
|
-
assert.isString(name, 'name');
|
|
242
|
-
assert.defined(descriptor, 'descriptor');
|
|
243
|
-
assert.notNull(descriptor, 'descriptor');
|
|
244
|
-
assert.isObject(descriptor, 'descriptor');
|
|
245
|
-
|
|
246
|
-
const resource = this._createResourceEntry(descriptor);
|
|
247
|
-
|
|
248
|
-
return this._createResourceNode(name, resource.resource_id).id;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* @template T
|
|
253
|
-
* @param {T} descriptor
|
|
254
|
-
* @return {ResourceEntry}
|
|
255
|
-
* @private
|
|
256
|
-
*/
|
|
257
|
-
_createResourceEntry(descriptor) {
|
|
258
|
-
assert.defined(descriptor, 'descriptor');
|
|
259
|
-
assert.notNull(descriptor, 'descriptor');
|
|
260
|
-
|
|
261
|
-
const entry = new ResourceEntry();
|
|
262
|
-
|
|
263
|
-
entry.resource_id = this.__resource_registry.length;
|
|
264
|
-
entry.resource_descriptor = descriptor;
|
|
265
|
-
|
|
266
|
-
this.__resource_registry.push(entry);
|
|
267
|
-
|
|
268
|
-
return entry;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
*
|
|
273
|
-
* @param {string} name
|
|
274
|
-
* @param {number} resource_id
|
|
275
|
-
* @return {ResourceNode}
|
|
276
|
-
* @private
|
|
277
|
-
*/
|
|
278
|
-
_createResourceNode(name, resource_id) {
|
|
279
|
-
assert.isString(name, 'name');
|
|
280
|
-
assert.isNonNegativeInteger(resource_id, 'resource_id');
|
|
281
|
-
|
|
282
|
-
const n = new ResourceNode();
|
|
283
|
-
|
|
284
|
-
const id = this.__resource_nodes.length;
|
|
285
|
-
|
|
286
|
-
n.id = id;
|
|
287
|
-
n.name = name;
|
|
288
|
-
n.resource_id = resource_id;
|
|
289
|
-
n.scope = this.#scope;
|
|
290
|
-
|
|
291
|
-
this.__resource_nodes[id] = n;
|
|
292
|
-
|
|
293
|
-
return n;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
*
|
|
298
|
-
* @param {number} node_id
|
|
299
|
-
* @returns {number} ID of the cloned resource node
|
|
300
|
-
*/
|
|
301
|
-
clone_resource(node_id) {
|
|
302
|
-
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': resources can only be cloned while recording, graph is ${frame_graph_state_name(this.#state)}`);
|
|
303
|
-
|
|
304
|
-
const node = this.getResourceNode(node_id);
|
|
305
|
-
|
|
306
|
-
const entry = this.__resource_registry[node.resource_id];
|
|
307
|
-
|
|
308
|
-
entry.resource_version++;
|
|
309
|
-
|
|
310
|
-
const clone_node = new ResourceNode();
|
|
311
|
-
clone_node.id = this.__resource_nodes.length;
|
|
312
|
-
clone_node.name = node.name;
|
|
313
|
-
clone_node.resource_id = node.resource_id;
|
|
314
|
-
clone_node.version = entry.resource_version;
|
|
315
|
-
clone_node.scope = this.#scope;
|
|
316
|
-
|
|
317
|
-
this.__resource_nodes.push(clone_node);
|
|
318
|
-
|
|
319
|
-
return clone_node.id;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* @template T
|
|
324
|
-
* @param {string} name
|
|
325
|
-
* @param {ResourceDescriptor<T>} description
|
|
326
|
-
* @param {T} resource
|
|
327
|
-
* @returns {number}
|
|
328
|
-
*/
|
|
329
|
-
import_resource(name, description, resource) {
|
|
330
|
-
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': resources can only be imported while recording, graph is ${frame_graph_state_name(this.#state)}`);
|
|
331
|
-
|
|
332
|
-
const record = this._createResourceEntry(description);
|
|
333
|
-
|
|
334
|
-
record.resource = resource;
|
|
335
|
-
|
|
336
|
-
record.imported = true;
|
|
337
|
-
|
|
338
|
-
return this._createResourceNode(name, record.resource_id).id;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* @returns {boolean}
|
|
343
|
-
* @param id
|
|
344
|
-
*/
|
|
345
|
-
is_valid_resource(id) {
|
|
346
|
-
const node = this.getResourceNode(id);
|
|
347
|
-
const record = this.getResourceEntry(id);
|
|
348
|
-
|
|
349
|
-
return node.version === record.resource_version;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Add a new pass to the graph.
|
|
354
|
-
* @template T
|
|
355
|
-
* @param {string} name
|
|
356
|
-
* @param {T} data
|
|
357
|
-
* @param {(data: T, resources: FramePassResources, context: IFrameGraphContext) => void} execute
|
|
358
|
-
* @returns {FramePassBuilder}
|
|
359
|
-
*/
|
|
360
|
-
add(name, data, execute) {
|
|
361
|
-
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': passes can only be added while recording, graph is ${frame_graph_state_name(this.#state)}`);
|
|
362
|
-
assert.isString(name, 'name');
|
|
363
|
-
|
|
364
|
-
assert.defined(data, 'data');
|
|
365
|
-
assert.notNull(data, 'data');
|
|
366
|
-
assert.isObject(data, 'data');
|
|
367
|
-
|
|
368
|
-
assert.isFunction(execute, 'execute');
|
|
369
|
-
|
|
370
|
-
const pass_nodes = this.__pass_nodes;
|
|
371
|
-
|
|
372
|
-
const builder = new FramePassBuilder();
|
|
373
|
-
const node = new FramePassNode();
|
|
374
|
-
|
|
375
|
-
node.id = pass_nodes.length;
|
|
376
|
-
node.name = name;
|
|
377
|
-
node.execute = execute;
|
|
378
|
-
node.scope = this.#scope;
|
|
379
|
-
|
|
380
|
-
pass_nodes.push(node);
|
|
381
|
-
|
|
382
|
-
builder.init(this, node);
|
|
383
|
-
|
|
384
|
-
node.data = data;
|
|
385
|
-
|
|
386
|
-
return builder;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Check the recorded graph for the two ways a read can silently return the wrong contents.
|
|
391
|
-
*
|
|
392
|
-
* {@link execute} runs passes in **insertion order** and never sorts, so a pass that reads a
|
|
393
|
-
* resource written by a pass recorded after it reads whatever that resource happened to hold.
|
|
394
|
-
* And a {@link ResourceEntry} owns exactly one concrete resource — a superseded
|
|
395
|
-
* {@link ResourceNode} is not a snapshot of the old contents, it is the same resource holding
|
|
396
|
-
* the new contents — so reading an out-of-date node is reading the wrong thing.
|
|
397
|
-
*
|
|
398
|
-
* One shape is deliberately **not** a problem: reading a transient that no pass writes is
|
|
399
|
-
* fine, because a resource can be created by a pass that only reserves it, and the reader is
|
|
400
|
-
* then the pass about to fill it.
|
|
401
|
-
*
|
|
402
|
-
* Accumulating into a shared target is **not** an exemption. {@link FramePassBuilder.write}
|
|
403
|
-
* records a read of the handle it replaces, so a pass re-attaching a target it goes on to write
|
|
404
|
-
* is a reader of that target, and it has to re-attach the handle its predecessor produced
|
|
405
|
-
* rather than the one it was originally handed. Several passes rasterizing into one render
|
|
406
|
-
* target do exactly this, and each of them threads the handle forward.
|
|
407
|
-
*
|
|
408
|
-
* Describes what was recorded rather than what survives culling, so it runs before culling is
|
|
409
|
-
* computed — {@link compile} calls it.
|
|
410
|
-
*
|
|
411
|
-
* @param {function(string):*} [problem_consumer] invoked once per problem found
|
|
412
|
-
* @returns {boolean} whether the graph is free of problems
|
|
413
|
-
*/
|
|
414
|
-
validate(problem_consumer = noop) {
|
|
415
|
-
|
|
416
|
-
const pass_nodes = this.__pass_nodes;
|
|
417
|
-
const pass_node_count = pass_nodes.length;
|
|
418
|
-
|
|
419
|
-
const resource_nodes = this.__resource_nodes;
|
|
420
|
-
|
|
421
|
-
let valid = true;
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Highest resource node version of a given resource entry written so far, by entry ID.
|
|
425
|
-
* @type {Int32Array}
|
|
426
|
-
*/
|
|
427
|
-
const current_version = new Int32Array(this.__resource_registry.length);
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Index of the pass that wrote {@link current_version}, by resource entry ID, or -1.
|
|
431
|
-
* @type {Int32Array}
|
|
432
|
-
*/
|
|
433
|
-
const current_version_writer = new Int32Array(this.__resource_registry.length).fill(-1);
|
|
434
|
-
|
|
435
|
-
for (let i = 0; i < pass_node_count; i++) {
|
|
436
|
-
const pass = pass_nodes[i];
|
|
437
|
-
|
|
438
|
-
for (const id of pass.resource_reads) {
|
|
439
|
-
const node = resource_nodes[id];
|
|
440
|
-
|
|
441
|
-
const producer = node.producer;
|
|
442
|
-
|
|
443
|
-
if (producer !== null && producer.id > i) {
|
|
444
|
-
problem_consumer(`Pass ${i} '${pass.name}' reads '${node.name}', which is written by pass ${producer.id} '${producer.name}'. Passes execute in the order they were added, so the read happens before the write.`);
|
|
445
|
-
|
|
446
|
-
valid = false;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const version = current_version[node.resource_id];
|
|
450
|
-
|
|
451
|
-
if (node.version < version) {
|
|
452
|
-
const writer_index = current_version_writer[node.resource_id];
|
|
453
|
-
|
|
454
|
-
problem_consumer(`Pass ${i} '${pass.name}' reads version ${node.version} of '${node.name}', which pass ${writer_index} '${pass_nodes[writer_index].name}' has already superseded with version ${version}. Both versions name one resource, so the read returns the newer contents.`);
|
|
455
|
-
|
|
456
|
-
valid = false;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
for (const id of pass.resource_writes) {
|
|
461
|
-
const node = resource_nodes[id];
|
|
462
|
-
|
|
463
|
-
if (node.version > current_version[node.resource_id]) {
|
|
464
|
-
current_version[node.resource_id] = node.version;
|
|
465
|
-
current_version_writer[node.resource_id] = i;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
return valid;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
compile() {
|
|
474
|
-
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': compile() is one-shot, a second compile accumulates resource reference counts instead of resetting them and culling stops working, graph is ${frame_graph_state_name(this.#state)}`);
|
|
475
|
-
|
|
476
|
-
assert.equal(this.#scope, null, `FrameGraph '${this.name}': recording finished with scope '${this.#scope}' still open, every push_scope() needs its pop_scope()`);
|
|
477
|
-
assert.ok(this.validate(console.error), `FrameGraph '${this.name}': the recorded graph is invalid, the problems are logged above`);
|
|
478
|
-
|
|
479
|
-
this.#state = FrameGraphState.Compiled;
|
|
480
|
-
|
|
481
|
-
const pass_nodes = this.__pass_nodes;
|
|
482
|
-
|
|
483
|
-
// wire resources of passes
|
|
484
|
-
const pass_node_count = pass_nodes.length;
|
|
485
|
-
const resource_nodes = this.__resource_nodes;
|
|
486
|
-
|
|
487
|
-
for (let i = 0; i < pass_node_count; i++) {
|
|
488
|
-
const pass = pass_nodes[i];
|
|
489
|
-
|
|
490
|
-
// mask pass as having number of references equal to number of resources it writes to
|
|
491
|
-
pass.ref_count = pass.resource_writes.length;
|
|
492
|
-
|
|
493
|
-
for (const id of pass.resource_reads) {
|
|
494
|
-
const read_node = resource_nodes[id];
|
|
495
|
-
|
|
496
|
-
read_node.ref_count++;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// perform culling
|
|
501
|
-
/**
|
|
502
|
-
*
|
|
503
|
-
* @type {ResourceNode[]}
|
|
504
|
-
*/
|
|
505
|
-
const unreferenced_resources = [];
|
|
506
|
-
|
|
507
|
-
for (const node of resource_nodes) {
|
|
508
|
-
if (node.ref_count === 0) {
|
|
509
|
-
unreferenced_resources.push(node);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
while (unreferenced_resources.length > 0) {
|
|
514
|
-
const unreferenced_resource = unreferenced_resources.pop();
|
|
515
|
-
|
|
516
|
-
const producer = unreferenced_resource.producer;
|
|
517
|
-
|
|
518
|
-
if (producer === null || producer.has_side_effects) {
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
assert.greaterThanOrEqual(producer.ref_count, 1, 'must have at least one reference'); // Why?
|
|
523
|
-
|
|
524
|
-
// disconnect unreferenced resource from producer
|
|
525
|
-
producer.ref_count--;
|
|
526
|
-
|
|
527
|
-
if (producer.ref_count === 0) {
|
|
528
|
-
|
|
529
|
-
// producer is no longer referenced
|
|
530
|
-
|
|
531
|
-
for (const id of producer.resource_reads) {
|
|
532
|
-
const node = resource_nodes[id];
|
|
533
|
-
|
|
534
|
-
// disconnect producer from all its reads (upstream)
|
|
535
|
-
node.ref_count--;
|
|
536
|
-
|
|
537
|
-
if (node.ref_count === 0) {
|
|
538
|
-
// resource is no longer referenced
|
|
539
|
-
unreferenced_resources.push(node);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// calculate lifetimes
|
|
546
|
-
for (let i = 0; i < pass_node_count; i++) {
|
|
547
|
-
const pass = pass_nodes[i];
|
|
548
|
-
if (pass.ref_count === 0) {
|
|
549
|
-
// unused pass
|
|
550
|
-
continue;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
for (const id of pass.resource_creates) {
|
|
554
|
-
const entry = this.getResourceEntry(id);
|
|
555
|
-
entry.producer = pass;
|
|
556
|
-
entry.last = pass;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
for (const id of pass.resource_writes) {
|
|
560
|
-
this.getResourceEntry(id).last = pass;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
for (const id of pass.resource_reads) {
|
|
564
|
-
this.getResourceEntry(id).last = pass;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
*
|
|
571
|
-
* @param {IFrameGraphContext} context
|
|
572
|
-
*/
|
|
573
|
-
execute(context) {
|
|
574
|
-
assert.equal(this.#state, FrameGraphState.Compiled, `FrameGraph '${this.name}': execute() requires a compiled graph, an uncompiled one has every reference count at 0 and culls itself silently, graph is ${frame_graph_state_name(this.#state)}`);
|
|
575
|
-
assert.defined(context, 'context');
|
|
576
|
-
assert.notNull(context, 'context');
|
|
577
|
-
assert.isObject(context, 'context');
|
|
578
|
-
|
|
579
|
-
this.#state = FrameGraphState.Executed;
|
|
580
|
-
|
|
581
|
-
const pass_nodes = this.__pass_nodes;
|
|
582
|
-
const pass_node_count = pass_nodes.length;
|
|
583
|
-
|
|
584
|
-
for (let i = 0; i < pass_node_count; i++) {
|
|
585
|
-
const node = pass_nodes[i];
|
|
586
|
-
|
|
587
|
-
if (!node.can_execute()) {
|
|
588
|
-
continue;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
const node_created_resources = node.resource_creates;
|
|
592
|
-
|
|
593
|
-
for (const id of node_created_resources) {
|
|
594
|
-
|
|
595
|
-
// allocate resource
|
|
596
|
-
this.getResourceEntry(id).create(context.resource_manager);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
const resources = new FramePassResources();
|
|
600
|
-
resources.init(this, node);
|
|
601
|
-
|
|
602
|
-
// execute pass
|
|
603
|
-
try {
|
|
604
|
-
node.execute(node.data, resources, context);
|
|
605
|
-
} catch (e) {
|
|
606
|
-
// decorate
|
|
607
|
-
const error = new Error(`RenderPass '${node.name}' failed to execute`);
|
|
608
|
-
|
|
609
|
-
error.cause = e;
|
|
610
|
-
|
|
611
|
-
throw error;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const resource_entries = this.__resource_registry;
|
|
616
|
-
const resource_entry_count = resource_entries.length;
|
|
617
|
-
|
|
618
|
-
for (let entry_index = 0; entry_index < resource_entry_count; entry_index++) {
|
|
619
|
-
const entry = resource_entries[entry_index];
|
|
620
|
-
|
|
621
|
-
if (entry.last === node && entry.isTransient()) {
|
|
622
|
-
// this was the last user of the resource and the resource is transient (no external usage)
|
|
623
|
-
entry.destroy(context.resource_manager);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
this.onExecuted.send2(context, this);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Should only call after {@link compile}
|
|
635
|
-
*
|
|
636
|
-
* One entry in `resources` per resource **node**, that is per version, keyed by node ID. A pass
|
|
637
|
-
* records the node it read or wrote and not the underlying resource, so this is the id space
|
|
638
|
-
* `passes[].reads` and `passes[].writes` point into; a registry-keyed export would leave those
|
|
639
|
-
* references dangling on every version past the first.
|
|
640
|
-
*
|
|
641
|
-
* Can be visualized with this tool: https://skaarj1989.github.io/FrameGraph/
|
|
642
|
-
*
|
|
643
|
-
* @see https://github.com/skaarj1989/FrameGraph/blob/viewer/JsonWriter.hpp
|
|
644
|
-
* @return {{passes: [], resources: []}}
|
|
645
|
-
*/
|
|
646
|
-
exportToJson() {
|
|
647
|
-
|
|
648
|
-
const resources = this.__resource_nodes.map(node => {
|
|
649
|
-
const entry = this.__resource_registry[node.resource_id];
|
|
650
|
-
|
|
651
|
-
const j = {
|
|
652
|
-
id: node.id,
|
|
653
|
-
name: node.name,
|
|
654
|
-
transient: entry.isTransient(),
|
|
655
|
-
};
|
|
656
|
-
|
|
657
|
-
if (node.version > 0) {
|
|
658
|
-
j.version = node.version;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
const description = entry.resource_descriptor?.toString();
|
|
662
|
-
|
|
663
|
-
if (description) {
|
|
664
|
-
j.description = description;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
if (node.producer !== null) {
|
|
668
|
-
j.createdBy = node.producer.id;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
const readers = [];
|
|
672
|
-
const writers = [];
|
|
673
|
-
|
|
674
|
-
for (const pass of this.__pass_nodes) {
|
|
675
|
-
if (pass.reads(node.id)) {
|
|
676
|
-
readers.push(pass.id);
|
|
677
|
-
}
|
|
678
|
-
if (pass.writes(node.id)) {
|
|
679
|
-
writers.push(pass.id);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
if (readers.length > 0) {
|
|
684
|
-
j.readers = readers;
|
|
685
|
-
}
|
|
686
|
-
if (writers.length > 0) {
|
|
687
|
-
j.writers = writers;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
return j;
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
return {
|
|
694
|
-
passes: this.__pass_nodes.map(node => {
|
|
695
|
-
return {
|
|
696
|
-
id: node.id,
|
|
697
|
-
name: node.name,
|
|
698
|
-
culled: !node.can_execute(),
|
|
699
|
-
reads: node.resource_reads,
|
|
700
|
-
writes: node.resource_writes,
|
|
701
|
-
}
|
|
702
|
-
}),
|
|
703
|
-
resources
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
/**
|
|
708
|
-
* Export the graph diagram in GraphViz DOT format.
|
|
709
|
-
* Useful for debugging.
|
|
710
|
-
* @see https://en.wikipedia.org/wiki/DOT_(graph_description_language)
|
|
711
|
-
* @return {string}
|
|
712
|
-
*/
|
|
713
|
-
exportToDot() {
|
|
714
|
-
const out = new LineBuilder();
|
|
715
|
-
|
|
716
|
-
out.add("digraph FrameGraph {");
|
|
717
|
-
out.indent();
|
|
718
|
-
out.add("graph [style=invis, rankdir=\"TB\" ordering=out, splines=spline]");
|
|
719
|
-
out.add("node [shape=record, fontname=\"helvetica\", fontsize=10, margin=\"0.2,0.03\"]");
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
// -- Define pass nodes
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* @param {FramePassNode} node
|
|
726
|
-
* @returns {string}
|
|
727
|
-
*/
|
|
728
|
-
const pass_declaration = (node) => `P${node.id} [label=<{ {<B>${node.name}</B>} | {${(node.has_side_effects ? "★ " : "")} Refs: ${node.ref_count}<BR/> Index: ${node.id}} }> style=\"rounded,filled\", fillcolor=${(node.ref_count > 0 || node.has_side_effects) ? "orange" : "lightgray"}]`;
|
|
729
|
-
|
|
730
|
-
out.add("");
|
|
731
|
-
out.add("# Pass Nodes");
|
|
732
|
-
for (const node of this.__pass_nodes) {
|
|
733
|
-
if (node.scope === null) {
|
|
734
|
-
out.add(pass_declaration(node));
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// -- Define resource nodes
|
|
739
|
-
|
|
740
|
-
/**
|
|
741
|
-
*
|
|
742
|
-
* @param {string} type
|
|
743
|
-
* @returns {number}
|
|
744
|
-
*/
|
|
745
|
-
function type_to_color(type) {
|
|
746
|
-
|
|
747
|
-
const hash_32 = computeStringHash(type);
|
|
748
|
-
|
|
749
|
-
const hash_16 = (hash_32 & 0xffff) ^ (hash_32 >>> 16);
|
|
750
|
-
|
|
751
|
-
return hash_16 / 0xffff;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
/**
|
|
755
|
-
*
|
|
756
|
-
* @param {ResourceEntry} entry
|
|
757
|
-
* @return {string}
|
|
758
|
-
*/
|
|
759
|
-
function entry_to_background_color(entry) {
|
|
760
|
-
|
|
761
|
-
let color_hue = type_to_color(entry.resource_descriptor?.type ?? "");
|
|
762
|
-
|
|
763
|
-
// remap hue away from red
|
|
764
|
-
color_hue = lerp(0.10, 0.90, color_hue);
|
|
765
|
-
|
|
766
|
-
const saturation = entry.isImported() ? 0.2 : 0.07;
|
|
767
|
-
|
|
768
|
-
const color_rgb = [];
|
|
769
|
-
okhsv_to_linear_srgb(color_rgb, color_hue, saturation, 1)
|
|
770
|
-
|
|
771
|
-
const hex =
|
|
772
|
-
|
|
773
|
-
return `#${hex}`;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
/**
|
|
777
|
-
* @param {ResourceNode} node
|
|
778
|
-
* @returns {string}
|
|
779
|
-
*/
|
|
780
|
-
const resource_declaration = (node) => {
|
|
781
|
-
const entry = this.__resource_registry[node.resource_id];
|
|
782
|
-
|
|
783
|
-
const type = graphviz_escape_string(entry.resource_descriptor?.type ?? "");
|
|
784
|
-
|
|
785
|
-
const block_title = `${entry.isImported() ? "↪ " : ""}${node.name}`;
|
|
786
|
-
const block_tooltip = graphviz_escape_string(entry.toString());
|
|
787
|
-
|
|
788
|
-
return `R${entry.resource_id}_${node.version} [label=<{ {<B>${block_title}</B>${node.version > 0 ? ` <FONT>v${(node.version + 1)}</FONT>` : ""}<BR/>${type}} | {Index: ${entry.resource_id}<BR/> Refs : ${node.ref_count} } }> style=filled, fillcolor="${entry_to_background_color(entry)}" tooltip="${block_tooltip}"]`;
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
out.add("");
|
|
792
|
-
out.add("# Resource Nodes");
|
|
793
|
-
for (const node of this.__resource_nodes) {
|
|
794
|
-
if (node.scope === null) {
|
|
795
|
-
out.add(resource_declaration(node));
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// -- Define scoped nodes, inside the cluster of the scope that recorded them
|
|
800
|
-
|
|
801
|
-
/**
|
|
802
|
-
* Scopes that stamped at least one node, and the scopes enclosing those.
|
|
803
|
-
* @type {Set<FrameGraphScope>}
|
|
804
|
-
*/
|
|
805
|
-
const scopes = new Set();
|
|
806
|
-
|
|
807
|
-
for (const node of [...this.__pass_nodes, ...this.__resource_nodes]) {
|
|
808
|
-
for (let scope = node.scope; scope !== null && !scopes.has(scope); scope = scope.parent) {
|
|
809
|
-
scopes.add(scope);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
/**
|
|
814
|
-
* @param {FrameGraphScope} scope
|
|
815
|
-
*/
|
|
816
|
-
const emit_scope = (scope) => {
|
|
817
|
-
out.add(`subgraph cluster_${scope.id} {`);
|
|
818
|
-
out.indent();
|
|
819
|
-
|
|
820
|
-
// the root graph is drawn invisible, so a cluster has to ask for its own frame
|
|
821
|
-
out.add(`label="${graphviz_escape_string(scope.name)}"`);
|
|
822
|
-
out.add("style=\"rounded,dashed\", color=\"gray55\", fontname=\"helvetica\", fontsize=10, labeljust=\"l\"");
|
|
823
|
-
|
|
824
|
-
for (const node of this.__pass_nodes) {
|
|
825
|
-
if (node.scope === scope) {
|
|
826
|
-
out.add(pass_declaration(node));
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
for (const node of this.__resource_nodes) {
|
|
831
|
-
if (node.scope === scope) {
|
|
832
|
-
out.add(resource_declaration(node));
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
for (const child of scopes) {
|
|
837
|
-
if (child.parent === scope) {
|
|
838
|
-
emit_scope(child);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
out.dedent();
|
|
843
|
-
out.add("}");
|
|
844
|
-
};
|
|
845
|
-
|
|
846
|
-
if (scopes.size > 0) {
|
|
847
|
-
out.add("");
|
|
848
|
-
out.add("# Scopes");
|
|
849
|
-
|
|
850
|
-
for (const scope of scopes) {
|
|
851
|
-
if (scope.parent === null) {
|
|
852
|
-
emit_scope(scope);
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
// -- Each pass node points to the resource that it writes
|
|
858
|
-
|
|
859
|
-
out.add("");
|
|
860
|
-
out.add("# Resource Writes");
|
|
861
|
-
for (const node of this.__pass_nodes) {
|
|
862
|
-
out.add(`P${node.id} -> {`);
|
|
863
|
-
out.indent();
|
|
864
|
-
for (const id of node.resource_writes) {
|
|
865
|
-
const written = this.__resource_nodes[id];
|
|
866
|
-
out.add(`R${written.resource_id}_${written.version} `);
|
|
867
|
-
}
|
|
868
|
-
out.dedent();
|
|
869
|
-
out.add("} [color=orangered]");
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
// -- Each resource node points to the pass where it's consumed
|
|
873
|
-
|
|
874
|
-
out.add("");
|
|
875
|
-
out.add("# Resource Reads");
|
|
876
|
-
|
|
877
|
-
for (const node of this.__resource_nodes) {
|
|
878
|
-
out.add(`R${node.resource_id}_${node.version} -> {`);
|
|
879
|
-
out.indent();
|
|
880
|
-
// find all readers of this resource node
|
|
881
|
-
for (const pass of this.__pass_nodes) {
|
|
882
|
-
for (const id of pass.resource_reads)
|
|
883
|
-
if (id === node.id) {
|
|
884
|
-
out.add(`P${pass.id} `);
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
out.dedent();
|
|
888
|
-
out.add("} [color=olivedrab3]");
|
|
889
|
-
}
|
|
890
|
-
out.dedent();
|
|
891
|
-
out.add("}");
|
|
892
|
-
return out.build();
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
/**
|
|
897
|
-
* Type check shortcut.
|
|
898
|
-
* @readonly
|
|
899
|
-
* @type {boolean}
|
|
900
|
-
*/
|
|
1
|
+
import { assert } from "../../../../core/assert.js";
|
|
2
|
+
import { float_to_uint8 } from "../../../../core/binary/float_to_uint8.js";
|
|
3
|
+
import LineBuilder from "../../../../core/codegen/LineBuilder.js";
|
|
4
|
+
import { rgb_to_hex } from "../../../../core/color/hex/rgb_to_hex.js";
|
|
5
|
+
import { okhsv_to_linear_srgb } from "../../../../core/color/oklab/okhsv_to_linear_srgb.js";
|
|
6
|
+
import { Signal } from "../../../../core/events/signal/Signal.js";
|
|
7
|
+
import { noop } from "../../../../core/function/noop.js";
|
|
8
|
+
import { graphviz_escape_string } from "../../../../core/graph/format/graphviz/graphviz_escape_string.js";
|
|
9
|
+
import { lerp } from "../../../../core/math/lerp.js";
|
|
10
|
+
import { computeStringHash } from "../../../../core/primitives/strings/computeStringHash.js";
|
|
11
|
+
import { FrameGraphScope } from "./FrameGraphScope.js";
|
|
12
|
+
import { FrameGraphState, frame_graph_state_name } from "./FrameGraphState.js";
|
|
13
|
+
import { FramePassBuilder } from "./FramePassBuilder.js";
|
|
14
|
+
import { FramePassNode } from "./FramePassNode.js";
|
|
15
|
+
import { FramePassResources } from "./FramePassResources.js";
|
|
16
|
+
import { ResourceEntry } from "./ResourceEntry.js";
|
|
17
|
+
import { ResourceNode } from "./ResourceNode.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A high-level rendering architecture system that manages render passes and their resource dependencies.
|
|
21
|
+
* This class schedules frame execution by building a directed graph of resources (textures, buffers) and the passes that read or write to them.
|
|
22
|
+
*
|
|
23
|
+
* By deferring execution until after the graph is compiled, it automatically handles complex tasks such as:
|
|
24
|
+
* - Culling unreferenced resources and passes (dead code elimination for rendering).
|
|
25
|
+
* - Automatic resource lifetime management (allocating transient resources just-in-time and destroying them immediately after their last use).
|
|
26
|
+
* - Visualizing the rendering pipeline via GraphViz DOT exports.
|
|
27
|
+
*
|
|
28
|
+
* The graph is **one-shot**: it is recorded into, then compiled, then executed, and it never goes
|
|
29
|
+
* back. {@link compile} derives reference counts and resource lifetimes from what was recorded, so
|
|
30
|
+
* recording into a compiled graph corrupts them, and compiling twice accumulates resource reference
|
|
31
|
+
* counts instead of resetting them, which disables culling entirely. Build a new graph per frame.
|
|
32
|
+
*
|
|
33
|
+
* @see FrameGraphState
|
|
34
|
+
*
|
|
35
|
+
* @source Based on the Frostbite's GDC paper "FrameGraph: Extensible Rendering Architecture in Frostbite" by Yuriy O'Donnell
|
|
36
|
+
* @example
|
|
37
|
+
* const graph = new FrameGraph("My Graph");
|
|
38
|
+
*
|
|
39
|
+
* const pass_data = {};
|
|
40
|
+
* const pass = graph.add("GBuffer Pass", pass_data, (data, resources, context) => {
|
|
41
|
+
*
|
|
42
|
+
* context.beginRenderPass({
|
|
43
|
+
* depth_attachment: resources.get(data.depth),
|
|
44
|
+
* color_attachments:[
|
|
45
|
+
* resources.get(data.normal),
|
|
46
|
+
* resources.get(data.albedo),
|
|
47
|
+
* ]
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* for (const renderable in renderables){
|
|
51
|
+
* drawMesh(context, renderable.mesh, renderable.material);
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* context.endRenderPass();
|
|
55
|
+
*
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* pass_data.depth = pass.create("depth", {...});
|
|
59
|
+
* pass_data.albedo = pass.create("albedo", {...});
|
|
60
|
+
* pass_data.normal = pass.create("normal", {...});
|
|
61
|
+
*
|
|
62
|
+
* graph.compile();
|
|
63
|
+
* graph.execute(context);
|
|
64
|
+
*
|
|
65
|
+
*/
|
|
66
|
+
export class FrameGraph {
|
|
67
|
+
/**
|
|
68
|
+
* Human-readable name, used for debugging and UI primarily.
|
|
69
|
+
* There is no uniqueness guarantee
|
|
70
|
+
* @type {string}
|
|
71
|
+
*/
|
|
72
|
+
name = "";
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
*
|
|
76
|
+
* @type {Signal<IFrameGraphContext,this>}
|
|
77
|
+
*/
|
|
78
|
+
onExecuted = new Signal();
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
*
|
|
82
|
+
* @type {FramePassNode[]}
|
|
83
|
+
* @private
|
|
84
|
+
*/
|
|
85
|
+
__pass_nodes = [];
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
*
|
|
89
|
+
* @type {ResourceNode[]}
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
__resource_nodes = [];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
*
|
|
96
|
+
* @type {ResourceEntry[]}
|
|
97
|
+
* @private
|
|
98
|
+
*/
|
|
99
|
+
__resource_registry = [];
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Position in the one-shot lifecycle.
|
|
103
|
+
* @type {FrameGraphState}
|
|
104
|
+
*/
|
|
105
|
+
#state = FrameGraphState.Recording;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Innermost open recording scope, or null. The enclosing scopes are its
|
|
109
|
+
* {@link FrameGraphScope.parent} chain, so this one field is the whole stack.
|
|
110
|
+
* @type {FrameGraphScope|null}
|
|
111
|
+
*/
|
|
112
|
+
#scope = null;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Scopes opened so far, which is what gives each one an ID unique within this graph.
|
|
116
|
+
* @type {number}
|
|
117
|
+
*/
|
|
118
|
+
#scope_count = 0;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
*
|
|
122
|
+
* @param {string} name
|
|
123
|
+
*/
|
|
124
|
+
constructor(name = "") {
|
|
125
|
+
assert.isString(name, 'name');
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
this.name = name;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Position in the one-shot lifecycle. Recording is only legal in
|
|
133
|
+
* {@link FrameGraphState.Recording}, and {@link execute} is only legal in
|
|
134
|
+
* {@link FrameGraphState.Compiled}.
|
|
135
|
+
* @returns {FrameGraphState}
|
|
136
|
+
*/
|
|
137
|
+
get state() {
|
|
138
|
+
return this.#state;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Innermost open recording scope, or null when recording outside of one.
|
|
143
|
+
* @returns {FrameGraphScope|null}
|
|
144
|
+
*/
|
|
145
|
+
get scope() {
|
|
146
|
+
return this.#scope;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Open a recording scope. Every pass and resource node created until the matching
|
|
151
|
+
* {@link pop_scope} is stamped with it, nested scopes included.
|
|
152
|
+
*
|
|
153
|
+
* This is how the graph attributes work to whoever recorded it. Node names come from the
|
|
154
|
+
* helpers that happened to build them and say nothing about the caller, so without a scope a
|
|
155
|
+
* DOT export cannot group a frame by the thing each group belongs to, and a diagnostic cannot
|
|
156
|
+
* ask whether everything one caller recorded was culled.
|
|
157
|
+
*
|
|
158
|
+
* @param {string} name
|
|
159
|
+
* @returns {FrameGraphScope} the scope opened, for later queries against node stamps
|
|
160
|
+
*/
|
|
161
|
+
push_scope(name) {
|
|
162
|
+
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': scopes can only be opened while recording, graph is ${frame_graph_state_name(this.#state)}`);
|
|
163
|
+
assert.isString(name, 'name');
|
|
164
|
+
|
|
165
|
+
const scope = new FrameGraphScope();
|
|
166
|
+
|
|
167
|
+
scope.name = name;
|
|
168
|
+
scope.id = this.#scope_count++;
|
|
169
|
+
scope.parent = this.#scope;
|
|
170
|
+
|
|
171
|
+
this.#scope = scope;
|
|
172
|
+
|
|
173
|
+
return scope;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Close the innermost open recording scope, returning to the one that encloses it.
|
|
178
|
+
*/
|
|
179
|
+
pop_scope() {
|
|
180
|
+
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': scopes can only be closed while recording, graph is ${frame_graph_state_name(this.#state)}`);
|
|
181
|
+
assert.notNull(this.#scope, `FrameGraph '${this.name}': pop_scope() without a scope open`);
|
|
182
|
+
|
|
183
|
+
this.#scope = this.#scope.parent;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
*
|
|
188
|
+
* @param {number} id Resource Node ID
|
|
189
|
+
* @returns {ResourceEntry}
|
|
190
|
+
*/
|
|
191
|
+
getResourceEntry(id) {
|
|
192
|
+
const node = this.getResourceNode(id);
|
|
193
|
+
|
|
194
|
+
assert.defined(node, 'node');
|
|
195
|
+
|
|
196
|
+
const registry = this.__resource_registry;
|
|
197
|
+
|
|
198
|
+
const entry = registry[node.resource_id];
|
|
199
|
+
|
|
200
|
+
assert.defined(entry, 'entry');
|
|
201
|
+
|
|
202
|
+
return entry;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
*
|
|
207
|
+
* @param {number} id
|
|
208
|
+
* @returns {ResourceNode}
|
|
209
|
+
*/
|
|
210
|
+
getResourceNode(id) {
|
|
211
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
212
|
+
|
|
213
|
+
const resource = this.__resource_nodes[id];
|
|
214
|
+
|
|
215
|
+
if (resource === undefined) {
|
|
216
|
+
throw new Error(`Resource Node ${id} not found`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
assert.equal(resource.id, id, 'resource.id does not match');
|
|
220
|
+
|
|
221
|
+
return resource;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @template T
|
|
226
|
+
* @param {number} id resource ID
|
|
227
|
+
* @returns {T}
|
|
228
|
+
*/
|
|
229
|
+
getDescriptor(id) {
|
|
230
|
+
return this.getResourceEntry(id).resource_descriptor;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* @template T
|
|
235
|
+
* @param {string} name
|
|
236
|
+
* @param {T} descriptor
|
|
237
|
+
* @returns {number}
|
|
238
|
+
*/
|
|
239
|
+
create_resource(name, descriptor) {
|
|
240
|
+
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': resources can only be created while recording, graph is ${frame_graph_state_name(this.#state)}`);
|
|
241
|
+
assert.isString(name, 'name');
|
|
242
|
+
assert.defined(descriptor, 'descriptor');
|
|
243
|
+
assert.notNull(descriptor, 'descriptor');
|
|
244
|
+
assert.isObject(descriptor, 'descriptor');
|
|
245
|
+
|
|
246
|
+
const resource = this._createResourceEntry(descriptor);
|
|
247
|
+
|
|
248
|
+
return this._createResourceNode(name, resource.resource_id).id;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @template T
|
|
253
|
+
* @param {T} descriptor
|
|
254
|
+
* @return {ResourceEntry}
|
|
255
|
+
* @private
|
|
256
|
+
*/
|
|
257
|
+
_createResourceEntry(descriptor) {
|
|
258
|
+
assert.defined(descriptor, 'descriptor');
|
|
259
|
+
assert.notNull(descriptor, 'descriptor');
|
|
260
|
+
|
|
261
|
+
const entry = new ResourceEntry();
|
|
262
|
+
|
|
263
|
+
entry.resource_id = this.__resource_registry.length;
|
|
264
|
+
entry.resource_descriptor = descriptor;
|
|
265
|
+
|
|
266
|
+
this.__resource_registry.push(entry);
|
|
267
|
+
|
|
268
|
+
return entry;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
*
|
|
273
|
+
* @param {string} name
|
|
274
|
+
* @param {number} resource_id
|
|
275
|
+
* @return {ResourceNode}
|
|
276
|
+
* @private
|
|
277
|
+
*/
|
|
278
|
+
_createResourceNode(name, resource_id) {
|
|
279
|
+
assert.isString(name, 'name');
|
|
280
|
+
assert.isNonNegativeInteger(resource_id, 'resource_id');
|
|
281
|
+
|
|
282
|
+
const n = new ResourceNode();
|
|
283
|
+
|
|
284
|
+
const id = this.__resource_nodes.length;
|
|
285
|
+
|
|
286
|
+
n.id = id;
|
|
287
|
+
n.name = name;
|
|
288
|
+
n.resource_id = resource_id;
|
|
289
|
+
n.scope = this.#scope;
|
|
290
|
+
|
|
291
|
+
this.__resource_nodes[id] = n;
|
|
292
|
+
|
|
293
|
+
return n;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
*
|
|
298
|
+
* @param {number} node_id
|
|
299
|
+
* @returns {number} ID of the cloned resource node
|
|
300
|
+
*/
|
|
301
|
+
clone_resource(node_id) {
|
|
302
|
+
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': resources can only be cloned while recording, graph is ${frame_graph_state_name(this.#state)}`);
|
|
303
|
+
|
|
304
|
+
const node = this.getResourceNode(node_id);
|
|
305
|
+
|
|
306
|
+
const entry = this.__resource_registry[node.resource_id];
|
|
307
|
+
|
|
308
|
+
entry.resource_version++;
|
|
309
|
+
|
|
310
|
+
const clone_node = new ResourceNode();
|
|
311
|
+
clone_node.id = this.__resource_nodes.length;
|
|
312
|
+
clone_node.name = node.name;
|
|
313
|
+
clone_node.resource_id = node.resource_id;
|
|
314
|
+
clone_node.version = entry.resource_version;
|
|
315
|
+
clone_node.scope = this.#scope;
|
|
316
|
+
|
|
317
|
+
this.__resource_nodes.push(clone_node);
|
|
318
|
+
|
|
319
|
+
return clone_node.id;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* @template T
|
|
324
|
+
* @param {string} name
|
|
325
|
+
* @param {ResourceDescriptor<T>} description
|
|
326
|
+
* @param {T} resource
|
|
327
|
+
* @returns {number}
|
|
328
|
+
*/
|
|
329
|
+
import_resource(name, description, resource) {
|
|
330
|
+
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': resources can only be imported while recording, graph is ${frame_graph_state_name(this.#state)}`);
|
|
331
|
+
|
|
332
|
+
const record = this._createResourceEntry(description);
|
|
333
|
+
|
|
334
|
+
record.resource = resource;
|
|
335
|
+
|
|
336
|
+
record.imported = true;
|
|
337
|
+
|
|
338
|
+
return this._createResourceNode(name, record.resource_id).id;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* @returns {boolean}
|
|
343
|
+
* @param id
|
|
344
|
+
*/
|
|
345
|
+
is_valid_resource(id) {
|
|
346
|
+
const node = this.getResourceNode(id);
|
|
347
|
+
const record = this.getResourceEntry(id);
|
|
348
|
+
|
|
349
|
+
return node.version === record.resource_version;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Add a new pass to the graph.
|
|
354
|
+
* @template T
|
|
355
|
+
* @param {string} name
|
|
356
|
+
* @param {T} data
|
|
357
|
+
* @param {(data: T, resources: FramePassResources, context: IFrameGraphContext) => void} execute
|
|
358
|
+
* @returns {FramePassBuilder}
|
|
359
|
+
*/
|
|
360
|
+
add(name, data, execute) {
|
|
361
|
+
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': passes can only be added while recording, graph is ${frame_graph_state_name(this.#state)}`);
|
|
362
|
+
assert.isString(name, 'name');
|
|
363
|
+
|
|
364
|
+
assert.defined(data, 'data');
|
|
365
|
+
assert.notNull(data, 'data');
|
|
366
|
+
assert.isObject(data, 'data');
|
|
367
|
+
|
|
368
|
+
assert.isFunction(execute, 'execute');
|
|
369
|
+
|
|
370
|
+
const pass_nodes = this.__pass_nodes;
|
|
371
|
+
|
|
372
|
+
const builder = new FramePassBuilder();
|
|
373
|
+
const node = new FramePassNode();
|
|
374
|
+
|
|
375
|
+
node.id = pass_nodes.length;
|
|
376
|
+
node.name = name;
|
|
377
|
+
node.execute = execute;
|
|
378
|
+
node.scope = this.#scope;
|
|
379
|
+
|
|
380
|
+
pass_nodes.push(node);
|
|
381
|
+
|
|
382
|
+
builder.init(this, node);
|
|
383
|
+
|
|
384
|
+
node.data = data;
|
|
385
|
+
|
|
386
|
+
return builder;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Check the recorded graph for the two ways a read can silently return the wrong contents.
|
|
391
|
+
*
|
|
392
|
+
* {@link execute} runs passes in **insertion order** and never sorts, so a pass that reads a
|
|
393
|
+
* resource written by a pass recorded after it reads whatever that resource happened to hold.
|
|
394
|
+
* And a {@link ResourceEntry} owns exactly one concrete resource — a superseded
|
|
395
|
+
* {@link ResourceNode} is not a snapshot of the old contents, it is the same resource holding
|
|
396
|
+
* the new contents — so reading an out-of-date node is reading the wrong thing.
|
|
397
|
+
*
|
|
398
|
+
* One shape is deliberately **not** a problem: reading a transient that no pass writes is
|
|
399
|
+
* fine, because a resource can be created by a pass that only reserves it, and the reader is
|
|
400
|
+
* then the pass about to fill it.
|
|
401
|
+
*
|
|
402
|
+
* Accumulating into a shared target is **not** an exemption. {@link FramePassBuilder.write}
|
|
403
|
+
* records a read of the handle it replaces, so a pass re-attaching a target it goes on to write
|
|
404
|
+
* is a reader of that target, and it has to re-attach the handle its predecessor produced
|
|
405
|
+
* rather than the one it was originally handed. Several passes rasterizing into one render
|
|
406
|
+
* target do exactly this, and each of them threads the handle forward.
|
|
407
|
+
*
|
|
408
|
+
* Describes what was recorded rather than what survives culling, so it runs before culling is
|
|
409
|
+
* computed — {@link compile} calls it.
|
|
410
|
+
*
|
|
411
|
+
* @param {function(string):*} [problem_consumer] invoked once per problem found
|
|
412
|
+
* @returns {boolean} whether the graph is free of problems
|
|
413
|
+
*/
|
|
414
|
+
validate(problem_consumer = noop) {
|
|
415
|
+
|
|
416
|
+
const pass_nodes = this.__pass_nodes;
|
|
417
|
+
const pass_node_count = pass_nodes.length;
|
|
418
|
+
|
|
419
|
+
const resource_nodes = this.__resource_nodes;
|
|
420
|
+
|
|
421
|
+
let valid = true;
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Highest resource node version of a given resource entry written so far, by entry ID.
|
|
425
|
+
* @type {Int32Array}
|
|
426
|
+
*/
|
|
427
|
+
const current_version = new Int32Array(this.__resource_registry.length);
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Index of the pass that wrote {@link current_version}, by resource entry ID, or -1.
|
|
431
|
+
* @type {Int32Array}
|
|
432
|
+
*/
|
|
433
|
+
const current_version_writer = new Int32Array(this.__resource_registry.length).fill(-1);
|
|
434
|
+
|
|
435
|
+
for (let i = 0; i < pass_node_count; i++) {
|
|
436
|
+
const pass = pass_nodes[i];
|
|
437
|
+
|
|
438
|
+
for (const id of pass.resource_reads) {
|
|
439
|
+
const node = resource_nodes[id];
|
|
440
|
+
|
|
441
|
+
const producer = node.producer;
|
|
442
|
+
|
|
443
|
+
if (producer !== null && producer.id > i) {
|
|
444
|
+
problem_consumer(`Pass ${i} '${pass.name}' reads '${node.name}', which is written by pass ${producer.id} '${producer.name}'. Passes execute in the order they were added, so the read happens before the write.`);
|
|
445
|
+
|
|
446
|
+
valid = false;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const version = current_version[node.resource_id];
|
|
450
|
+
|
|
451
|
+
if (node.version < version) {
|
|
452
|
+
const writer_index = current_version_writer[node.resource_id];
|
|
453
|
+
|
|
454
|
+
problem_consumer(`Pass ${i} '${pass.name}' reads version ${node.version} of '${node.name}', which pass ${writer_index} '${pass_nodes[writer_index].name}' has already superseded with version ${version}. Both versions name one resource, so the read returns the newer contents.`);
|
|
455
|
+
|
|
456
|
+
valid = false;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
for (const id of pass.resource_writes) {
|
|
461
|
+
const node = resource_nodes[id];
|
|
462
|
+
|
|
463
|
+
if (node.version > current_version[node.resource_id]) {
|
|
464
|
+
current_version[node.resource_id] = node.version;
|
|
465
|
+
current_version_writer[node.resource_id] = i;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return valid;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
compile() {
|
|
474
|
+
assert.equal(this.#state, FrameGraphState.Recording, `FrameGraph '${this.name}': compile() is one-shot, a second compile accumulates resource reference counts instead of resetting them and culling stops working, graph is ${frame_graph_state_name(this.#state)}`);
|
|
475
|
+
|
|
476
|
+
assert.equal(this.#scope, null, `FrameGraph '${this.name}': recording finished with scope '${this.#scope}' still open, every push_scope() needs its pop_scope()`);
|
|
477
|
+
assert.ok(this.validate(console.error), `FrameGraph '${this.name}': the recorded graph is invalid, the problems are logged above`);
|
|
478
|
+
|
|
479
|
+
this.#state = FrameGraphState.Compiled;
|
|
480
|
+
|
|
481
|
+
const pass_nodes = this.__pass_nodes;
|
|
482
|
+
|
|
483
|
+
// wire resources of passes
|
|
484
|
+
const pass_node_count = pass_nodes.length;
|
|
485
|
+
const resource_nodes = this.__resource_nodes;
|
|
486
|
+
|
|
487
|
+
for (let i = 0; i < pass_node_count; i++) {
|
|
488
|
+
const pass = pass_nodes[i];
|
|
489
|
+
|
|
490
|
+
// mask pass as having number of references equal to number of resources it writes to
|
|
491
|
+
pass.ref_count = pass.resource_writes.length;
|
|
492
|
+
|
|
493
|
+
for (const id of pass.resource_reads) {
|
|
494
|
+
const read_node = resource_nodes[id];
|
|
495
|
+
|
|
496
|
+
read_node.ref_count++;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// perform culling
|
|
501
|
+
/**
|
|
502
|
+
*
|
|
503
|
+
* @type {ResourceNode[]}
|
|
504
|
+
*/
|
|
505
|
+
const unreferenced_resources = [];
|
|
506
|
+
|
|
507
|
+
for (const node of resource_nodes) {
|
|
508
|
+
if (node.ref_count === 0) {
|
|
509
|
+
unreferenced_resources.push(node);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
while (unreferenced_resources.length > 0) {
|
|
514
|
+
const unreferenced_resource = unreferenced_resources.pop();
|
|
515
|
+
|
|
516
|
+
const producer = unreferenced_resource.producer;
|
|
517
|
+
|
|
518
|
+
if (producer === null || producer.has_side_effects) {
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
assert.greaterThanOrEqual(producer.ref_count, 1, 'must have at least one reference'); // Why?
|
|
523
|
+
|
|
524
|
+
// disconnect unreferenced resource from producer
|
|
525
|
+
producer.ref_count--;
|
|
526
|
+
|
|
527
|
+
if (producer.ref_count === 0) {
|
|
528
|
+
|
|
529
|
+
// producer is no longer referenced
|
|
530
|
+
|
|
531
|
+
for (const id of producer.resource_reads) {
|
|
532
|
+
const node = resource_nodes[id];
|
|
533
|
+
|
|
534
|
+
// disconnect producer from all its reads (upstream)
|
|
535
|
+
node.ref_count--;
|
|
536
|
+
|
|
537
|
+
if (node.ref_count === 0) {
|
|
538
|
+
// resource is no longer referenced
|
|
539
|
+
unreferenced_resources.push(node);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// calculate lifetimes
|
|
546
|
+
for (let i = 0; i < pass_node_count; i++) {
|
|
547
|
+
const pass = pass_nodes[i];
|
|
548
|
+
if (pass.ref_count === 0) {
|
|
549
|
+
// unused pass
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
for (const id of pass.resource_creates) {
|
|
554
|
+
const entry = this.getResourceEntry(id);
|
|
555
|
+
entry.producer = pass;
|
|
556
|
+
entry.last = pass;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
for (const id of pass.resource_writes) {
|
|
560
|
+
this.getResourceEntry(id).last = pass;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
for (const id of pass.resource_reads) {
|
|
564
|
+
this.getResourceEntry(id).last = pass;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
*
|
|
571
|
+
* @param {IFrameGraphContext} context
|
|
572
|
+
*/
|
|
573
|
+
execute(context) {
|
|
574
|
+
assert.equal(this.#state, FrameGraphState.Compiled, `FrameGraph '${this.name}': execute() requires a compiled graph, an uncompiled one has every reference count at 0 and culls itself silently, graph is ${frame_graph_state_name(this.#state)}`);
|
|
575
|
+
assert.defined(context, 'context');
|
|
576
|
+
assert.notNull(context, 'context');
|
|
577
|
+
assert.isObject(context, 'context');
|
|
578
|
+
|
|
579
|
+
this.#state = FrameGraphState.Executed;
|
|
580
|
+
|
|
581
|
+
const pass_nodes = this.__pass_nodes;
|
|
582
|
+
const pass_node_count = pass_nodes.length;
|
|
583
|
+
|
|
584
|
+
for (let i = 0; i < pass_node_count; i++) {
|
|
585
|
+
const node = pass_nodes[i];
|
|
586
|
+
|
|
587
|
+
if (!node.can_execute()) {
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const node_created_resources = node.resource_creates;
|
|
592
|
+
|
|
593
|
+
for (const id of node_created_resources) {
|
|
594
|
+
|
|
595
|
+
// allocate resource
|
|
596
|
+
this.getResourceEntry(id).create(context.resource_manager);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const resources = new FramePassResources();
|
|
600
|
+
resources.init(this, node);
|
|
601
|
+
|
|
602
|
+
// execute pass
|
|
603
|
+
try {
|
|
604
|
+
node.execute(node.data, resources, context);
|
|
605
|
+
} catch (e) {
|
|
606
|
+
// decorate
|
|
607
|
+
const error = new Error(`RenderPass '${node.name}' failed to execute`);
|
|
608
|
+
|
|
609
|
+
error.cause = e;
|
|
610
|
+
|
|
611
|
+
throw error;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
const resource_entries = this.__resource_registry;
|
|
616
|
+
const resource_entry_count = resource_entries.length;
|
|
617
|
+
|
|
618
|
+
for (let entry_index = 0; entry_index < resource_entry_count; entry_index++) {
|
|
619
|
+
const entry = resource_entries[entry_index];
|
|
620
|
+
|
|
621
|
+
if (entry.last === node && entry.isTransient()) {
|
|
622
|
+
// this was the last user of the resource and the resource is transient (no external usage)
|
|
623
|
+
entry.destroy(context.resource_manager);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
this.onExecuted.send2(context, this);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Should only call after {@link compile}
|
|
635
|
+
*
|
|
636
|
+
* One entry in `resources` per resource **node**, that is per version, keyed by node ID. A pass
|
|
637
|
+
* records the node it read or wrote and not the underlying resource, so this is the id space
|
|
638
|
+
* `passes[].reads` and `passes[].writes` point into; a registry-keyed export would leave those
|
|
639
|
+
* references dangling on every version past the first.
|
|
640
|
+
*
|
|
641
|
+
* Can be visualized with this tool: https://skaarj1989.github.io/FrameGraph/
|
|
642
|
+
*
|
|
643
|
+
* @see https://github.com/skaarj1989/FrameGraph/blob/viewer/JsonWriter.hpp
|
|
644
|
+
* @return {{passes: [], resources: []}}
|
|
645
|
+
*/
|
|
646
|
+
exportToJson() {
|
|
647
|
+
|
|
648
|
+
const resources = this.__resource_nodes.map(node => {
|
|
649
|
+
const entry = this.__resource_registry[node.resource_id];
|
|
650
|
+
|
|
651
|
+
const j = {
|
|
652
|
+
id: node.id,
|
|
653
|
+
name: node.name,
|
|
654
|
+
transient: entry.isTransient(),
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
if (node.version > 0) {
|
|
658
|
+
j.version = node.version;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const description = entry.resource_descriptor?.toString();
|
|
662
|
+
|
|
663
|
+
if (description) {
|
|
664
|
+
j.description = description;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (node.producer !== null) {
|
|
668
|
+
j.createdBy = node.producer.id;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const readers = [];
|
|
672
|
+
const writers = [];
|
|
673
|
+
|
|
674
|
+
for (const pass of this.__pass_nodes) {
|
|
675
|
+
if (pass.reads(node.id)) {
|
|
676
|
+
readers.push(pass.id);
|
|
677
|
+
}
|
|
678
|
+
if (pass.writes(node.id)) {
|
|
679
|
+
writers.push(pass.id);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (readers.length > 0) {
|
|
684
|
+
j.readers = readers;
|
|
685
|
+
}
|
|
686
|
+
if (writers.length > 0) {
|
|
687
|
+
j.writers = writers;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return j;
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
return {
|
|
694
|
+
passes: this.__pass_nodes.map(node => {
|
|
695
|
+
return {
|
|
696
|
+
id: node.id,
|
|
697
|
+
name: node.name,
|
|
698
|
+
culled: !node.can_execute(),
|
|
699
|
+
reads: node.resource_reads,
|
|
700
|
+
writes: node.resource_writes,
|
|
701
|
+
}
|
|
702
|
+
}),
|
|
703
|
+
resources
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Export the graph diagram in GraphViz DOT format.
|
|
709
|
+
* Useful for debugging.
|
|
710
|
+
* @see https://en.wikipedia.org/wiki/DOT_(graph_description_language)
|
|
711
|
+
* @return {string}
|
|
712
|
+
*/
|
|
713
|
+
exportToDot() {
|
|
714
|
+
const out = new LineBuilder();
|
|
715
|
+
|
|
716
|
+
out.add("digraph FrameGraph {");
|
|
717
|
+
out.indent();
|
|
718
|
+
out.add("graph [style=invis, rankdir=\"TB\" ordering=out, splines=spline]");
|
|
719
|
+
out.add("node [shape=record, fontname=\"helvetica\", fontsize=10, margin=\"0.2,0.03\"]");
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
// -- Define pass nodes
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* @param {FramePassNode} node
|
|
726
|
+
* @returns {string}
|
|
727
|
+
*/
|
|
728
|
+
const pass_declaration = (node) => `P${node.id} [label=<{ {<B>${node.name}</B>} | {${(node.has_side_effects ? "★ " : "")} Refs: ${node.ref_count}<BR/> Index: ${node.id}} }> style=\"rounded,filled\", fillcolor=${(node.ref_count > 0 || node.has_side_effects) ? "orange" : "lightgray"}]`;
|
|
729
|
+
|
|
730
|
+
out.add("");
|
|
731
|
+
out.add("# Pass Nodes");
|
|
732
|
+
for (const node of this.__pass_nodes) {
|
|
733
|
+
if (node.scope === null) {
|
|
734
|
+
out.add(pass_declaration(node));
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// -- Define resource nodes
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
*
|
|
742
|
+
* @param {string} type
|
|
743
|
+
* @returns {number}
|
|
744
|
+
*/
|
|
745
|
+
function type_to_color(type) {
|
|
746
|
+
|
|
747
|
+
const hash_32 = computeStringHash(type);
|
|
748
|
+
|
|
749
|
+
const hash_16 = (hash_32 & 0xffff) ^ (hash_32 >>> 16);
|
|
750
|
+
|
|
751
|
+
return hash_16 / 0xffff;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
*
|
|
756
|
+
* @param {ResourceEntry} entry
|
|
757
|
+
* @return {string}
|
|
758
|
+
*/
|
|
759
|
+
function entry_to_background_color(entry) {
|
|
760
|
+
|
|
761
|
+
let color_hue = type_to_color(entry.resource_descriptor?.type ?? "");
|
|
762
|
+
|
|
763
|
+
// remap hue away from red
|
|
764
|
+
color_hue = lerp(0.10, 0.90, color_hue);
|
|
765
|
+
|
|
766
|
+
const saturation = entry.isImported() ? 0.2 : 0.07;
|
|
767
|
+
|
|
768
|
+
const color_rgb = [];
|
|
769
|
+
okhsv_to_linear_srgb(color_rgb, color_hue, saturation, 1)
|
|
770
|
+
|
|
771
|
+
const hex = rgb_to_hex(...color_rgb.map(float_to_uint8));
|
|
772
|
+
|
|
773
|
+
return `#${hex}`;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* @param {ResourceNode} node
|
|
778
|
+
* @returns {string}
|
|
779
|
+
*/
|
|
780
|
+
const resource_declaration = (node) => {
|
|
781
|
+
const entry = this.__resource_registry[node.resource_id];
|
|
782
|
+
|
|
783
|
+
const type = graphviz_escape_string(entry.resource_descriptor?.type ?? "");
|
|
784
|
+
|
|
785
|
+
const block_title = `${entry.isImported() ? "↪ " : ""}${node.name}`;
|
|
786
|
+
const block_tooltip = graphviz_escape_string(entry.toString());
|
|
787
|
+
|
|
788
|
+
return `R${entry.resource_id}_${node.version} [label=<{ {<B>${block_title}</B>${node.version > 0 ? ` <FONT>v${(node.version + 1)}</FONT>` : ""}<BR/>${type}} | {Index: ${entry.resource_id}<BR/> Refs : ${node.ref_count} } }> style=filled, fillcolor="${entry_to_background_color(entry)}" tooltip="${block_tooltip}"]`;
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
out.add("");
|
|
792
|
+
out.add("# Resource Nodes");
|
|
793
|
+
for (const node of this.__resource_nodes) {
|
|
794
|
+
if (node.scope === null) {
|
|
795
|
+
out.add(resource_declaration(node));
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// -- Define scoped nodes, inside the cluster of the scope that recorded them
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Scopes that stamped at least one node, and the scopes enclosing those.
|
|
803
|
+
* @type {Set<FrameGraphScope>}
|
|
804
|
+
*/
|
|
805
|
+
const scopes = new Set();
|
|
806
|
+
|
|
807
|
+
for (const node of [...this.__pass_nodes, ...this.__resource_nodes]) {
|
|
808
|
+
for (let scope = node.scope; scope !== null && !scopes.has(scope); scope = scope.parent) {
|
|
809
|
+
scopes.add(scope);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* @param {FrameGraphScope} scope
|
|
815
|
+
*/
|
|
816
|
+
const emit_scope = (scope) => {
|
|
817
|
+
out.add(`subgraph cluster_${scope.id} {`);
|
|
818
|
+
out.indent();
|
|
819
|
+
|
|
820
|
+
// the root graph is drawn invisible, so a cluster has to ask for its own frame
|
|
821
|
+
out.add(`label="${graphviz_escape_string(scope.name)}"`);
|
|
822
|
+
out.add("style=\"rounded,dashed\", color=\"gray55\", fontname=\"helvetica\", fontsize=10, labeljust=\"l\"");
|
|
823
|
+
|
|
824
|
+
for (const node of this.__pass_nodes) {
|
|
825
|
+
if (node.scope === scope) {
|
|
826
|
+
out.add(pass_declaration(node));
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
for (const node of this.__resource_nodes) {
|
|
831
|
+
if (node.scope === scope) {
|
|
832
|
+
out.add(resource_declaration(node));
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
for (const child of scopes) {
|
|
837
|
+
if (child.parent === scope) {
|
|
838
|
+
emit_scope(child);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
out.dedent();
|
|
843
|
+
out.add("}");
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
if (scopes.size > 0) {
|
|
847
|
+
out.add("");
|
|
848
|
+
out.add("# Scopes");
|
|
849
|
+
|
|
850
|
+
for (const scope of scopes) {
|
|
851
|
+
if (scope.parent === null) {
|
|
852
|
+
emit_scope(scope);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// -- Each pass node points to the resource that it writes
|
|
858
|
+
|
|
859
|
+
out.add("");
|
|
860
|
+
out.add("# Resource Writes");
|
|
861
|
+
for (const node of this.__pass_nodes) {
|
|
862
|
+
out.add(`P${node.id} -> {`);
|
|
863
|
+
out.indent();
|
|
864
|
+
for (const id of node.resource_writes) {
|
|
865
|
+
const written = this.__resource_nodes[id];
|
|
866
|
+
out.add(`R${written.resource_id}_${written.version} `);
|
|
867
|
+
}
|
|
868
|
+
out.dedent();
|
|
869
|
+
out.add("} [color=orangered]");
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// -- Each resource node points to the pass where it's consumed
|
|
873
|
+
|
|
874
|
+
out.add("");
|
|
875
|
+
out.add("# Resource Reads");
|
|
876
|
+
|
|
877
|
+
for (const node of this.__resource_nodes) {
|
|
878
|
+
out.add(`R${node.resource_id}_${node.version} -> {`);
|
|
879
|
+
out.indent();
|
|
880
|
+
// find all readers of this resource node
|
|
881
|
+
for (const pass of this.__pass_nodes) {
|
|
882
|
+
for (const id of pass.resource_reads)
|
|
883
|
+
if (id === node.id) {
|
|
884
|
+
out.add(`P${pass.id} `);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
out.dedent();
|
|
888
|
+
out.add("} [color=olivedrab3]");
|
|
889
|
+
}
|
|
890
|
+
out.dedent();
|
|
891
|
+
out.add("}");
|
|
892
|
+
return out.build();
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* Type check shortcut.
|
|
898
|
+
* @readonly
|
|
899
|
+
* @type {boolean}
|
|
900
|
+
*/
|
|
901
901
|
FrameGraph.prototype.isFrameGraph = true;
|